# cpp primer pluss write up
这是一个简单的多线程资源利用的 bug
# 静态逆向
int __cdecl main(int argc, const char **argv, const char **envp) | |
{ | |
__int64 v3; // rax | |
__int64 v4; // rax | |
__int64 v5; // rax | |
__int64 v6; // rax | |
__int64 v7; // rax | |
__int64 v8; // rax | |
char v10; // [rsp+Bh] [rbp-25h] BYREF | |
int v11; // [rsp+Ch] [rbp-24h] BYREF | |
char buf[24]; // [rsp+10h] [rbp-20h] BYREF | |
unsigned __int64 v13; // [rsp+28h] [rbp-8h] | |
v13 = __readfsqword(0x28u); | |
init_func(); | |
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "WELCOME SI LIBRARY"); | |
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>); | |
Library::Library((Library *)&v10); | |
v4 = std::operator<<<std::char_traits<char>>(&std::cout, "Please enter your choice"); | |
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>); | |
while ( 1 ) | |
{ | |
menu(); | |
std::istream::operator>>(&std::cin, &v11); | |
switch ( v11 ) | |
{ | |
case 1: | |
if ( flag <= 0 ) | |
{ | |
v6 = std::operator<<<std::char_traits<char>>(&std::cout, "Please input bookname"); | |
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>); | |
read(0, buf, 0x14uLL); | |
Library::borrowbook((Library *)&v10, buf); | |
memset(buf, 0, 0x14uLL); | |
} | |
else | |
{ | |
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Please return book first"); | |
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>); | |
} | |
break; | |
case 2: | |
if ( flag <= 0 ) | |
{ | |
v7 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no book need to return"); | |
} | |
else | |
{ | |
Library::returnbook((Library *)&v10); | |
flag = 0; | |
v7 = std::operator<<<std::char_traits<char>>(&std::cout, "Return success!"); | |
} | |
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>); | |
break; | |
case 3: | |
if ( flag <= 0 ) | |
{ | |
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "There is no book need to read"); | |
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>); | |
} | |
else | |
{ | |
vuln(); | |
} | |
break; | |
case 4: | |
show(); | |
break; | |
case 5: | |
exit(0); | |
default: | |
continue; | |
} | |
} | |
} |
在 main 主函数内,提供四个选项
1.borrow a book
2.return a book
3.read a book
4.show book name
unsigned __int64 vuln(void) | |
{ | |
pthread_t newthread; // [rsp+0h] [rbp-10h] BYREF | |
unsigned __int64 v2; // [rsp+8h] [rbp-8h] | |
v2 = __readfsqword(0x28u); | |
std::string::operator=(&bookname[abi:cxx11], "C++_primer_plus"); | |
pthread_create(&newthread, 0LL, readbooks, 0LL); | |
return __readfsqword(0x28u) ^ v2; | |
} |
在 read a book 的时候,会忽略我们自己读取的 bookname , 而是重新设定 bookname 为 “c++_primer_plus”.
我们进 readbooks 函数实现去看看
unsigned __int64 __fastcall readbooks(void *a1) | |
{ | |
const char *v1; // rax | |
FILE *stream; // [rsp+8h] [rbp-88h] | |
char dest[32]; // [rsp+10h] [rbp-80h] BYREF | |
char ptr[88]; // [rsp+30h] [rbp-60h] BYREF | |
unsigned __int64 v6; // [rsp+88h] [rbp-8h] | |
v6 = __readfsqword(0x28u); | |
sleep(1u); | |
v1 = (const char *)std::string::c_str(&bookname[abi:cxx11]); | |
strcpy(dest, v1); | |
stream = fopen(dest, "r"); | |
if ( !stream ) | |
exit(0); | |
fread(ptr, 1uLL, 0x50uLL, stream); | |
puts(ptr); | |
return __readfsqword(0x28u) ^ v6; | |
} |
fopen 时使用的文件名是从 bookname [abi:cxx11] 这个全局变量中获取的,而且主线程创建子线程后,主线程依旧可以操作。
由于子线程 readbooks 函数内,sleep (1), 所以,这就给主线程 1 秒的时间,可以修改全局变量 bookname [abi:cxx11]。..
# 攻击
主界面输入 3,在一秒的等待时间内,输入 2 ,return book, 之后输入 1 ,borrow a book, 此时输入文件名,注意,这里的 flag 要带上 \x00 结尾,因为程序输入的时候不会自动追加,如果不加上就会变成 "flagprimer_plus",foen 失败。