[pwnable.kr] Toddler’s Bottle - uaf
UAF (Use After Free) 문제입니다. 코드가 cpp이여서 c++ 공부와 UAF 공부를 했습니다.
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
#include <fcntl.h> #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> using namespace std;
class Human{ private: virtual void give_shell(){ system("/bin/sh"); } protected: int age; string name; public: virtual void introduce(){ cout << "My name is " << name << endl; cout << "I am " << age << " years old" << endl; } };
class Man: public Human{ public: Man(string name, int age){ this->name = name; this->age = age; } virtual void introduce(){ Human::introduce(); cout << "I am a nice guy!" << endl; } };
class Woman: public Human{ public: Woman(string name, int age){ this->name = name; this->age = age; } virtual void introduce(){ Human::introduce(); cout << "I am a cute girl!" << endl; } };
int main(int argc, char* argv[]){ Human* m = new Man("Jack", 25); Human* w = new Woman("Jill", 21);
size_t len; char* data; unsigned int op; while(1){ cout << "1. use\n2. after\n3. free\n"; cin >> op;
switch(op){ case 1: m->introduce(); w->introduce(); break; case 2: len = atoi(argv[1]); data = new char[len]; read(open(argv[2], O_RDONLY), data, len); cout << "your data is allocated" << endl; break; case 3: delete m; delete w; break; default: break; } }
return 0; } |
Man, Woman Class는 Human Class를 상속 받습니다.
introduce() 함수와 age, name 멤버 변수를 가지고 있습니다.
각 생성자는 이름과 나이를 입력 받아서 멤버 변수에 저장해주는 기능을 가지고 있습니다.
궁극적 목표는 give_shell 함수를 실행하는 것이겠죠.
메뉴 1. use, 2. after, 3. free 가 있습니다.
1번 use 메뉴는 m 객체의 introduce, w 객체의 introduce를 순서대로 실행합니다.
2번 after 메뉴는 argv[1]을 정수로 변환해서 그만큼 동적 할당을 하고 argv[2] 경로의 파일을 ReadOnly로 열어서 argv[1]만큼 동적 할당한 data에 읽어옵니다.
3번 free 메뉴는 m과 w 객체를 해제하는 기능을 합니다.
그럼 give_shell 함수는 어디에 있을까요?
아까 64bit라고 말하지 않았지만, 여기서 알 수 있습니다! ..ㅎ
main을 분석해보면, 핸드레이를 연습한 덕분에 저 부분이 switch-case문이라는 걸 알 수 있습니다.
저 1번 메뉴를 보면 rbp-0x38이 m 객체이고, 거기에 *를 해서 다시 rax에 담습니다.
그리고 그 값에 8을 더하고, 다시 *를 해서 rdx에 담습니다.
그리고 rdx를 call합니다.
마찬가지로, w 객체는 rbp-0x30입니다.
일단 m만 따라가보면서 레지스터를 보겠습니다.
rax = *(rbp-0x38) 합니다.
rax = *rax 합니다.
잘 보면 *rax는 give_shell 주소입니다.
rax += 8 합니다.
rdx = *rax 합니다.
아까 그 주소는 Man의 introduce 함수입니다.
rax에 8을 더하고 *하니까, 원래 rax 값이 8만큼 작다면 8을 더했을 때 가리키고 있는 값은 give_shell의 주소일 것입니다.
그렇다면 일단 free하고 use해보겠습니다.
보시면 rax에 처음 들어온 주소가 가진 값이 NULL입니다.
그 값을 rax에 넣고, 8을 더했습니다.
그리고 그 주소에 접근해서 값을 가져오려 하는데 펑. 세폴이 납니다.
그렇다면 m 객체와 w 객체가 가리키고 있는 힙 메모리를 관찰해봅시다.
일단 값의 변화를 관찰하기 위해서 /tmp/jaemin1/input 이라는 파일에 AAAA를 저장해 놓았습니다.
(gdb) r 16 /tmp/jaemin1/input
다시 use로 가겠습니다.
힙을 정말 모르지만, 빨간색 1번이 chunk이고, 파란색 1번이 저 주소에 더하고 *하고 호출하는 걸 보니 함수의 Base 같은 느낌..?
빨간색 2번이 age, 파란색 2번이 name입니다.
보이는 *0x401570, *0x401550이 give_shell 함수이고, *(0x401570+8), *(0x401550+8)이 introduce 함수일 것입니다. 확인해보겠습니다.
정답입니다. 그렇다면 free하고 2번으로 재할당 할 때 어떻게 값이 변하는지 보겠습니다.
일단 확인을 위해서 0x1395c50을 기억합시다.
free후 after한 상태입니다.
나중에 free된 w객체에 값이 써졌습니다. 하지만 나중에 use해서 UAF를 터뜨릴 때 m->introduce가 먼저 호출되니 m객체에 값을 쓰기 위해서 한번 더 after 하겠습니다.
생각대로 잘 변조되었습니다. 저 주소에 +8해서 *하기 때문에 8 작은 값으로 변조해야 합니다.
0x401570-8인 0x401568을 넣어주면 give_shell 함수가 실행될 것입니다.
64bit 바이너리이기 때문에,
python ‘print “\x68\x15\x40\x00\x00\x00\x00\x00”’ > /tmp/jaemin1/input
8바이트 주소로 해줘야 합니다. (개행 문자 \x0a 때문에 안될 수 있음)
flag를 얻었습니다.
'System&Write up > Pwnable.kr' 카테고리의 다른 글
[pwnable.kr] Toddler's Bottle - memcpy (0) | 2018.02.27 |
---|---|
[pwnable.kr] Toddler's Bottle - asm (0) | 2018.01.31 |
[pwnable.kr] Toddler's Bottle - input (0) | 2018.01.20 |
[pwnable.kr] Toddler's Bottle - leg (0) | 2018.01.15 |
[pwnable.kr] Toddler's Bottle - coin1 (2) | 2017.12.31 |