본문 바로가기

Pwnable

uaf@pwnable.kr

□ 문제 요약

1. 출제의도 파악 : 할당 후 해제된 객체의 메모리에 임의의 값을 덮어 씌워 쉘 명령어(/bin/sh)를 실행

가. 반복문에서 객체 할당 및 해제에 대한 처리를 잘못할 경우 임의의 값 쓰기 및 실행 가능
* 또는 다른 함수에서 객체를 해제한 것을 인지하지 못하여 해제된 객체를 참조할 경우 취약점 발생

나. 특정 명령어를 가리키는 주소를 객체가 할당받은 메모리에 쓴 후 객체 메소드를 통해 실행하여 악용 가능

2. 풀이방법 (문제 전체 소스 코드는 맨 하단에 첨부)

가. give_shell 함수를 가르키는 주소 값(0x00401568)을 파일에 저장

나. 해당 파일 이름(solve)과 파일 읽기 길이(4바이트)를 인자 값으로 설정 후 바이너리 실행

다. 객체 메소드 호출, 객체 해제, 메모리에 파일 읽기, 메소드 재호출로 /bin/sh 실행

그림 1. 문제 풀이 결과


□ 상세분석

1. 반복문 분석


가. 객체 메소드 호출

1) cpp 소스코드 : m,w 변수에 Man,Woman 객체 할당 후 introduce 메소드 호출

그림 2. 객체 메소드 호출

2) m 변수($rbp-0x38)에 할당된 Man 객체의 introduce 메소드 호출

그림 3. 객체 메소드 호출 과정 GDB 분석

3) Man 객체와 Human 객체, give_shell 메소드 주소

그림 4. 객체 구조

나. 해제된 객체에 값 덮어쓰기

1) Read 함수 CPP 소스코드

그림 5. Read CPP 소스코드


2) 파일 읽기 첫 번째 : data 변수에 할당된 메모리 공간에 4바이트 글자 AAAA 입력

그림 6. Read 함수를 통해 임의의 값 쓰기

3) 파일 읽기 두 번째 : data 변수에, 해제된 객체 m의 주소를 할당 받아 객체 m의 기존 데이터 덮어쓰기

그림 7. m 객체에 임의의 값 쓰기

다. 메소드 호출을 통해 덮어씌워진 메소드 실행

1) m 객체에, give_shell 함수를 가리키는 주소 값에서 0x8만큼 뺀 주소 값을 덮어 씌운 후 메소드 호출, /bin/sh 실행

그림 8. give_shell 함수 호출


□ 문제 소스 코드 전체

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