# CAOV record pwnable.tw 中的 CAOV 一題,因為有附 cpp 檔案,所以我想說會不會比較好做一點...事實證明我錯了 ## 時間: 2019/07/28 ## 分析 程式不大,總的來說他實做了一個 Date class ,先不管 method 只看 member: ```cpp= class Data { private: char *key; // store string long value; // store value long change_count; // store edit count int year; // store time int month; int day; int hour; int min; int sec; void init_time() { year = 0; month = 0; day = 0; hour = 0; min = 0; sec = 0; } }; ``` 光是 constructor 就有三個: ```cpp // 1 Data():key(NULL) , value(0), change_count(0){ init_time(); } // 2 Data(string k, int v) { key = new char[k.length() + 1]; strcpy(key, k.c_str()); value = v; change_count = 0; update_time(); } // 3 Data(const Data &obj) { key = new char[strlen(obj.key)+1]; strcpy(key, obj.key); value = obj.value; change_count = obj.change_count; year = obj.year; month = obj.month; day = obj.day; hour = obj.hour; min = obj.min; sec = obj.sec; } ``` 另外還有一個 operator overloading ```cpp Data operator=(const Data &rhs) { key = new char[strlen(rhs.key)+1]; strcpy(key, rhs.key); value = rhs.value; change_count = rhs.change_count; year = rhs.year; month = rhs.month; day = rhs.day; hour = rhs.hour; min = rhs.min; sec = rhs.sec; } ``` 跟修改比較有關系的是: ```cpp void edit_data() { if(change_count == 10) { cout << "You can only edit your data 10 times at most." << endl; cout << "Bye ._.\\~/" << endl; exit(0); } int old_len = strlen(key); unsigned int new_len = 0; cout << "New key length: "; cin >> new_len; getchar(); if(new_len == 0 || new_len > 1000) { cout << "Invalid key length" << endl; return; } if (new_len > old_len) key = new char[new_len+1]; set_data(new_len); change_count += 1; } ``` 上面是修改 class 用的,外面還有個 wrapper 之類的東西: ```cpp void edit() { Data old; old = *D; D->edit_data(); cout << "\nYour data info before editing:" << endl; old.info(); cout << "\nYour data info after editing:" << endl; D->info(); } ``` destructor ```cpp ~Data() { delete[] key; key = nullptr; value = 0; change_count = 0; init_time(); } ``` 說不大,但全部貼上來還是滿大的,我只截錄幾個我覺得可疑的 全域變數: ```cpp Data *D; char name[160]; ``` 讀 name 的部分特別實作一個 function: ```cpp void set_name() { char tmp[160]={}; char c; cout << "Enter your name: "; int cnt = 0; while(1) { int len = read(0, &c, 1); if(len != 1) { cout << "Read error" << endl; exit(-1); } tmp[cnt++] = c; if(c == '\n' || cnt == 150) { tmp[cnt-1] = '\0'; break; } } memcpy(name, tmp, cnt); } ``` ## 漏洞: 因為太廢 source review 看不出來,我直接用 fuzz 找到在 edit 的 operator overloading 會把 stack 上的殘留值當作 object ,然後根據 destructor: ```cpp= ~Data() { delete[] key; key = nullptr; value = 0; change_count = 0; init_time(); } ``` line 3 會把 stack 存的殘留當作 key free 掉,這邊有個任意地址 free 漏洞,因為前一個用到這空間的是 set_name 中的 tmp ,所以地址可控 根據任意地址 free 只想到 house of spirit ,但需要知道要 free 掉空間地址,好像只有 global 符合 ## 進度 目前成功把 global 空間當作 heap 送進 free list ,但下一步怎麼利用還沒想到... ### 筆記 1. 目前能做到任意 free ,要想如何切換到任意讀取寫入漏洞 2. 能 allocate 的好像只有 key 這個陣列,而只有在 constructor 和 operator overloading 的部分才有 allocate 和 free **假設**: 如果可以藉由 double free 改 fastbin 讓 chunk overlap 來洩漏 libc 呢? ### 結果 1. 首先先偽造一個 0x70 大小的 chunk 在 name 上,然後透過任意 free 漏洞將其放入 fastbin 2. edit 前面的 set_name 可以修改已經在 fastbin 中的 fake chunk 的 fd ,將其改到同樣位於 global 中的 stdout/stdin (選一個),因為大小為 0x7f ,故 size 符合 3. 再 edit 以 key 的身分將其領出,並覆蓋掉後面的 class obj pointer 為 name array 中的某地址 A ,此地址 A 為 fake obj ptr 4. 再 edit 將 A 指向的地址( key array )改成 got 地址 5. show() 洩漏出 glibc address 6. 再 edit 修改 name 時改掉被 A 指向的地址的地址為 _IO_2_1_stderr_ 結構的 fp ,改為 "sh" 7. edit 修改 name 時改掉被 A 指向的地址的地址為 write_base 為 0 (要 bypass 撿查,觸發 _IO_fflush) 8. 操作基本同上,但這次改成 write_ptr 為 1 ,一樣要 bypass check 9. 操作基本同上,但這次改成 vtable ,將其改為 name array 某地址 10. edit 的時候對剛剛 vtable 的地址 + IO_overflow 偏移改為 system 11. 之後會有 double free 觸發 overflow() 12. get shell ## 困難 做到任意地址寫入後,我想過修改 free_hook : 將 free_hook 前面的空間當 chunk 放入 fastbin 中再領出使用 => 結果 free_hook 前面空間的值會改變 = = 全部弄好後我想說本地實驗一下,結果在 time() 的時候就掛掉了,就直接 remote 過去
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up