tmxklab

[pwnable.kr] uaf 본문

War Game/pwnable.kr

[pwnable.kr] uaf

tmxk4221 2020. 12. 2. 15:46

1. 문제 확인

 

[ uaf.c ]

#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 클래스 함수에 give_shell함수가 존재하는데 쉘을 딸 수 있음

이후에 main함수에서 각각의 메뉴를 실행할 수 있음

  • 메뉴 1 : m, w객체의 introduce()실행
  • 메뉴 2 : new를 통해 객체 생성 및 read(argv[2], data, argv[1])
  • 메뉴 3 : delete연산자를 통해 m, w객체 메모리 해제시킴

2. 접근 방법

 

딱 봐도 use after free취약점을 이용해서 문제를 풀면 될 것 같고 introduce()를 실행시키는 것 대신에 give_shell()을 실행시켜야 할 것 같다.

 

자세한 것은 디버깅을 통해 확인해보자.

참고로 보기 편하기 위해서 코드 긁어와서 다시 컴파일 한 것이므로 실제 서버에서 디버깅한 값과 많이 다름

m → w객체 순서대로 메모리 할당이 되어있고 size값은 헤더포함 0x40이다. (0x41의 1은 PREV_INUSE bit임)

0x601940과 0x601920은 vtable주소이며 각각 vtable에는 give_shell(), introduce()주소 값이 있다.

 

 

메뉴 1번을 선택하여 m, w→introduce()를 실행할 때

위와 같은 과정을 거치는데

 

 

이 때, rax값에 0x8을 더하여 introduce()주소를 가리키게 한다.

 

 

그럼 이제 끝났다. uaf를 이용하려면 먼저 m, w객체를 free. 각각의 size가 0x40이니깐 (여기서 glibc버젼이 2.23임)

uaf@pwnable:~$ getconf -a |grep glibc
GNU_LIBC_VERSION                   glibc 2.23

fast bin에 들어가고 consolidation은 되지 않는다. → bin에서 관리하니깐 그대로 남아있는다.

 

이제 case 2를 통해 다시 동일한 크기를 할당요청을 하면 m객체를 할당받을 것이고 첫 바이트부터 데이터 쓰기가 가능하니 8byte의 (vtable주소 - 0x8)주소 값을 쓰면 될 듯하다.

 


3. 문제 풀이

 

원격에서 직접 gdb로 디버깅했을 때 상황이고 다음과 같이 m, w객체가 힙 영역에 할당받은 것을 알 수 있다.

뭐 위에 로컬에서 디버깅했을 때랑 조금 다르긴한데 어차피 introduce()주소가 첫 바이트에 자리잡은 것을 알 수 있다.

참고로 free메뉴를 선택한 경우 m객체 다음으로 w객체의 힙 영역이 free되고 fastbin에 들어가게 된다. 따라서, fastbin은 LIFO구조이므로 fastbin : w → m 형식으로 되어 있을 것이다. 그래서 할당할 경우 w객체 먼저 재할당해주고 m객체를 재할당하게 해준다.

 

로컬 환경에서 쉽게 보면

참고로 glibc 2.27버젼이라 tcache적용되어 있는데 어차피 tcache든 fastbin이든 LIFO구조로 동일하므로 w → m 객체 순으로 들어갔다고 생각만 하면 된다.

  • m객체 : 0x613e60
  • w객체 : 0x613ea0

위와 같은 구조로 되어있으므로 재할당할 때 w객체, m객체 순으로 재할당해줌

 

갑자기 이런 얘기를 왜하냐?

free해주고 after해서 할당한다음에 바로 use하면 세그먼테이션 폴트가 뜨기 때문이다.

왜냐하면 use에서는 m→introduce()를 먼저 실행하는데 지금 w객체만 재할당하는데 사용되었고

m객체의 introduce()부분은 현재 널 값이기 때문이다. (왜 널 값인지는 모르면 디버깅해서 확인 ㄱㄱ)

 

따라서, 한번더 m객체를 할당받아야 하니깐 after를 한 번더 실행해주자

성공..


4. 몰랐던 개념

 

'War Game > pwnable.kr' 카테고리의 다른 글

[pwnable.kr] asm  (0) 2020.12.02
[pwnable.kr] memcpy  (0) 2020.12.02
[pwnable.kr] cmd2  (0) 2020.12.02
[pwnable.kr] cmd1  (0) 2020.12.02
[pwnable.kr] lotto  (0) 2020.12.02
Comments