[pwnable.kr] uaf(8 pts) :: Write-Up

두비니

·

2020. 7. 20. 04:10

 

 

 

 

 

Mommy, what is Use After Free bug?

ssh uaf@pwnable.kr -p2222 (pw: guest)

 

 

 

 

Use After Free라.... bof만 다루다가 바로 heap문제가 나와서 당황스럽지만, 아무튼 해봅시다.

Use After Free에 대한 글: https://dokhakdubini.tistory.com/35?category=809542

 

[Heap Exploit] UAF(Use After Free)기법 이론설명

Heap Exploit UAF(Use After Free)기법 이론설명 당분간은 Heap의 취약점 분석을 할 것인데요, 오늘은 UAF(Use After Free)기법입니다. UAF기법을 들어가기 전에, Heap구조가 무엇인지 알아야겠죠? 모른다면, 공부.

dokhakdubini.tistory.com

 

 

소스코드를 확인해봅시다.

 

#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;	
}

 

소스코드를 분석해보자.

일단 human이라는 클래스와 이를 상속받는 Man과 Woman이라는 클래스가 선언되어있고, 다음과 같이 초기화가 되어있다.

	Human* m = new Man("Jack", 25);
	Human* w = new Woman("Jill", 21);

 

그리고 선택지가 1, 2, 3이 있다.

1은 내가 입력한 값을 출력시켜주고, 2번은 새로운 값을 할당해 주는 곳이고, 3번은 기존의 m과 w를 free해준다.

 

 

일단 공격시나리오는 다음과 같다.

할당이 되어있던 m과 w를 free한다.

그 후, m과 w와 같은 크기인 새로운 공간을 할당시키면, 원래 m과 w를 가리키고 있던 공간을 할당시켜준다.

그 뒤에 기존의 m과 w에 대한 관련 instruction을 실행시킬 경우, m과 w를 가리키고 있던 공간은 이제 다른 새로운 공간임에도 불구하고 그 주소로 접근하게 된다.

따라서 우리가 이 새로운 공간을 이용해서 다른 값으로 변경할 경우, 그 값에 대해서도 접근이 가능해지는 것이다.

 

그러면 일단 human클래스 안의 give_shell()함수의 주소를 알아낸 후, UAF를 이용해 introduce()함수 대신에 give_shell()함수가 실행될 수 있도록 하면 플래그를 얻을 수 있을 것 같다.

 

give_shell()함수는 간단하게 info func로 알아내자.

(gdb) info func
All defined functions:

Non-debugging symbols:
0x0000000000400c28  _init
0x0000000000400c50  std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()@plt
0x0000000000400c60  std::ostream::operator<<(int)@plt
...(생략)
0x000000000040117a  Human::give_shell()
0x0000000000401192  Human::introduce()
0x0000000000401210  Human::Human()
0x0000000000401210  Human::Human()
...(생략)
(gdb) 

보면 Human::give_shell()의 함수주소가 0x0040117a인 것을 볼 수 있다.

그럼 이걸 덮어쓸 방법을 찾아보자.

제일 만만하게 덮어씌울 수 있는 값은 introduce()인 것 같다. 이제 introduce()가 어떻게 호출되는지 파악하여 give_shell()의 주소로 덮자.

 

그럼 introduce()함수가 호출되기 전 상황을 봐야하니 그 주변 어셈을 보자.

다음은 main함수에서 switch case문에서 1인 경우에 해당하는 어셈이다.

다음은 case 1의 c++코드이다.

 

			case 1:
				m->introduce();
				w->introduce();
				break;

단순히 introduce를 두번 호출한다. 호출하기 전 과정은

 

1. rbp-0x38안의 값을 rax에 복사

2. rax안의 값을 다시 복사

3. rax에 8 더하기

4. 3의 값을 rdx에 복사

 

1, 2과정은 단순히 값을 불러오는 과정이기 때문에 3번 전에 breakpoint를 걸어줍시다.

 

 

스샷이 짤렸네요. b * main+272해주었습니다.

 

그리고 여기서 한참을 헤맸는데,

 

rax의 상황을 보면 엥?

give_shell()함수의 값이 들어가있습니다.

rdx에 넣어주는 0x400fd8까지 진행하고 보겠습니다.

 

 

엥?? rax안에 introduce()함수의 주소값이 들어가있네요. 이제 저게 rdx로 들어가겠죠.

일부러 문제설계를 이렇게한건지, 저렇게 코드를 작성하면 원래 저렇게 프로그램이 빌드가 되는건진 모르겠지만 그러면 rax에 give_shell()이 들어가 있는 주소(0x401470) 에서 8을 뺀 값이 들어가 있다면 introduce대신에 give_shell()함수가 들어가겠죠?

 

아 쓸데없이 엄청 헤맸는데, rax의 주소는 0x401570이고, 여기 안에 0x40117a라는 주소값이 들어가 있는 것입니다.

그러면 rax + 8을 하면 주소는 0x401578이고, 그 안에 있는 값은 0x4012d2가 들어가있습니다.

쓸데없이 0x40117a+8 = 0x4012d2???? 이럴수가 있나?????? 하고 있었네요. 바보.

 

이걸 이제 파일에 넣어서 진행해줍시다.

 

일단 플래그는 구했는데, 질문을 가질 수 있는 부분을 답해보겠습니다.

 

 

1. 왜 4바이트가 할당이 되나요? Jack과 Jill의 크기는 최소 8이상일텐데...

 

일단 heap은 32bit기준 8byte 단위, 64bit기준 16byte단위로 할당되게 됩니다.

이 문제는 64bit이니, 그렇다는 것은 내가 3바이트를 부탁하나, 15바이트를 부탁하나 결국 16byte가 할당되게 된다는 것이겠죠.

 

그래서 실제로 3 ~ 24사이 값 아무거나 넣어도 됩니다.

3~16까지는 같은 값이 할당되니까 그렇다고 치고,

여기서 왜 17~24까지는 또 되냐, 뒷부분의 prev_size까지 써서 그렇습니다.

heap chunk에서 prev_size는 앞의 free된 chunk의 크기를 기록합니다. 즉, prev_size는 chunk가 free되기 전까지는 쓸모가 없습니다. 따라서, free되기 전까지는 필요에 따라 다음 chunk의 prev_size까지 그냥 data로 이용해버립니다.

 

다음 사진을 보면 이해에 도움이 될 것입니다.

 

 

이게 이해가 잘 안된다면 다음 문서를 참고합시다. https://d41jung0d.tistory.com/108

 

Heap 이해하기

1. Introduce 현재 heap공부를 하고 있는데, 몇 주째 잡고있는데도 실력이 늘지 않는 것 같아서 직접 읽어본 자료들을 참고해서 malloc, free를 통해 chunk가 어떻게 할당되고 해제되는지, 그때의 chunk는 ��

d41jung0d.tistory.com

 

 

 

2. 왜 allocation을 2번하나요?

다음은 free에 대한 코드입니다.

			case 3:
				delete m;
				delete w;
				break;

보면 m과 w를 둘다 free하죠? 그럼 일단 fastbin에 m << w 순으로 쌓여있을 겁니다.

그러면 이상태에서 2로 allocation을 할 경우 LIFO구조에 의하여 w에 allocation 될 것입니다.

이 상태에서 1번을 실행시킨다면?

			case 1:
				m->introduce();
				w->introduce();
				break;

지금 free된 상태인 m의 introduce를 부르니 당연히 segmentation fault다 발생하겠죠?

그래서 allocation을 2번 합니다.

 

 

끝!

 

 

원래는 함수주소를 overwrite를 해서 풀어보려고했는데, 뭐 이렇게 풀려버렸네요.

의도된건진 모르겠지만, 아무튼 좋습니다.

heap....어렵다.....................