Self-Improvement
[pwnable.kr] 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;
}
|
cs |
소스코드를 보기 앞서서 UAF에 대해서 정리를 해두었다. heap 영역의 취약점으로 보면 되겠다. https://johyungen.tistory.com/443
소스코드를 분석해보자면 C++로 멤버함수포인터?를 사용하고 있다. 1번을 선택할 시 m, w 객체?를 사용하고 2번 선택 시 인자에서 입력한 파일안의 내용을 읽어서 쓰고 있으며 3번을 선택할 시 delete로 m, w객체를 정리를 해주고 있다. 그리고 친절하게도 give_shell()에서 쉘을 주고 있다.
결론적으로 UAF가 힌트임으로 생각엔 1번 -> 3번 -> 2번(give_shell()) -> 1번 ?? 이런식으로 사용하면 될것같다.
풀이
switch 문은 아래와 같이 진행되고 있다.
- Case 1
main+286, main+309는 각 introduce() 함수를 수행하는 곳이다.
"m->introduce();" 부분을 보자면
main+265의 rbp-0x38의 값을 rax에
rax의 값을 rax 주소에
※ 0x0040117a는 give_shell()이다!!!
rax 주소에 +0x8을 더한다
rax의 값을 rdx 주소에 넣어주며 rdx는 introduce 함수로 되어진다.
이렇게 Case 1번이 이루어지며
- Case 3
3번을 수행할 시엔 m, w 객체가 delete가 되어진다. (Case1번에서는 0xf7ac50에는 0x00401570이 있었지만 사라졌다)
- Case 2 (case3을 수행한 뒤에 case2를 수행 해본다)
Case2에서는 인자를 2개를 받고 있으면 첫번째 인자는 read함수의 길이, 두번쨰 인자는 읽어들일 파일의 내용이다.
미리 /tmp/ababb에 "AAAA"를 넣고 수행하면 어떻게 되는지 확인해 본다.
read함수가 제일 중요함으로 b *main+399로 잡고 read함수로 넘어가는 인자를 확인해본다.
(어디 주소에 써지는지 확인하기 위해)
인자는 총 3개로 64bit 환경에서는 스택이 아닌 레지스터로 넘어가게 되어있다.
rsi, rdi, rdx 각 인자를 확인해보자
rsi는 파일의 내용이 쓰여질 주소이다, rdi는 open()함수의 리턴 값으로 fd 값이다, rdx는 인자1(argv[1])이다.
read함수를 수행하고 rsi 값을 확인해보면 /tmp/ababb 파일의 내용인 "AAAA"가 쓰여졌다.
※ 처음엔 rbp-0x38 주소에 값이 안쓰여지는거 보고 아닌줄 알았는데 한번더 실행하니 rbp-0x38 에 쓰여지는 걸 확인했다. (재실행을 하여서 case 1에서 본 주소가 다를수 있습니다.)
종합
이렇게 되면 파일의 내용엔 give_shell()함수의 주소를 넣어두고 after을 2번 수행 후 case 1을 수행하면 쉘이 떨어질 것이다.
give_shell()을 구하는 방식은 기존에 case 1하면서 알게 되었고 아래와 같이해도 구해진다.
(gdb) disassemble _ZN5Human10give_shellEv
0x40117a를 넣으면 될거라고 생각하였지만 case 1을 다시 보면 +0x8을 수행하고 있음으로 0x40117a-0x8의 값을 파일의 내용에 넣으면 될 것이다.
실행 순서는 3(free) ---> 2(after) ----> 2(after) ----> 1(use) 이다.
'리버싱 기초 > pwnable.kr' 카테고리의 다른 글
[pwnable.kr] cmd2 풀이 (0) | 2020.06.24 |
---|---|
[pwnable.kr] cmd1 풀이 (0) | 2020.06.24 |
[pwnable.kr] lotto 풀이 (0) | 2020.06.24 |
[pwnable.kr] blackjack 풀이 (0) | 2020.06.24 |
[pwnable.kr] shellshock 풀이 (쉘숔 CVE-2014-6271) (0) | 2020.06.24 |