VULNCON CTF 2021 IPS Writeup

VULNCON CTF 2021 IPS Writeup

Introduction

网络上很多<=2020年的kernel pwn writeup里的利用细节已经过时了, 像slub上的一些新的mitigations都没有被考虑进去. 今天学习了一个版本相对较新的kernel pwn, 分享一下分析和调试的过程.

题目下载: ips.tar.gz

IPS
0 solves / 500 points all available (heap) mitigations are on. perf_event_open is removed from the syscall table. get root.

Linux (none) 5.14.16 #2 SMP Mon Nov 22 19:24:06 UTC 2021 x86_64 GNU/Linux

TL;DR: 实现的syscall中计算idx的逻辑有漏洞, 可以拿到一块除了前0x10外都可控的UAF. 用UAF篡改一个msg_msg结构体可进行任意长度的leak, kernel base可以直接在leak中拿到, 而根据题目中存储模式的特点可以计算出slub上的地址, 进而计算出slab random. 最终用一个UAF改掉位于chunk中间的*next拿到任意地址写, 修改modprobe path, 在用户态触发modprobe完成提权.

Analysis

Challenge setting

启动脚本:

#!/bin/bash

cd `dirname $0`
qemu-system-x86_64 \
  -m 256M \
  -initrd initramfs.cpio.gz \
  -kernel bzImage -nographic \
  -monitor /dev/null \
  -s \
  -append "kpti=1 +smep +smap kaslr root=/dev/ram rw console=ttyS0 oops=panic paneic=1 quiet"

开启的保护:
– ktpi: 内核页表隔离. 由于我们是用修改modprobe path的方法, 所以不需要太关注
– smep+smap: 用户态代码不可执行+用户态数据不可访问, 但是没有给qemu传-cpu参数, 所以这两个参数其实是无效的, 因此也就有了ret2user的非预期解法(见下文)
– kaslr: 内核地址随机化, 常规的防护选项, 意味着我们需要leak

解包initramfs.cpio.gz, 查看系统init命令:
继续阅读“VULNCON CTF 2021 IPS Writeup”

vsCTF ezorange writeup

vsCTF ezorange writeup

前言

最近都在做程序分析和写kernel, 好久没玩pwn了, 周日晚上回宿舍刚好看到有一个vsCTF的比赛, 就顺手做了个glibc 2.32的堆题. 题目本身比较简单, 不过考察了一些高版本的libc的特性, 在这里记录一下作为备忘.

初步分析

首先看一下保护:

$ checksec --file=ezorange
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)

Partial RELRO + No PIE, 强烈暗示修改.got.plt.

IDA打开看一眼, 只提供了malloc和edit+show, 没有free, 结合题目名字, 必然是要用House of Orange了.

漏洞点在Modify函数里, 有一个堆上的OOB:

__int64 __fastcall Modify(_BYTE **orange_list)
{
  unsigned int v2; // [rsp+10h] [rbp-10h] BYREF
  unsigned int v3; // [rsp+14h] [rbp-Ch] BYREF
  _BYTE *cur_ptr; // [rsp+18h] [rbp-8h]

  printf("Orange number: ");
  __isoc99_scanf("%u", &v2);
  if ( v2 > 1 || !orange_list[v2] )
  {
    printf("Not allowed!");
    exit(0);
  }
  cur_ptr = orange_list[v2];
  printf("Total %u cell in this orange\n", *((_DWORD *)orange_list[v2] - 2) & 0xFFFFFFF0);
  printf("Cell index: ");
  __isoc99_scanf("%u", &v3);
  printf("Current value: %hhu\n", (unsigned __int8)cur_ptr[v3]);// OOB
  printf("New value: ");
  return __isoc99_scanf("%hhu", &cur_ptr[v3]);
}

限制条件: 只能同时保有两个chunk pointer, malloc参数不能超过0x1000.

题解

太长不看版:

  1. 利用OOB+House of Orange来把top chunk送进unsorted bin, 从而leak libc和heap的基址
  2. 同上, 但是在House of Orange的最后一步之前, 将top chunk的大小缩减到tcache的范围, 这样新的top chunk就会被丢到tcache里
  3. 重复2, 拿到第2个tcache, 然后利用OOB构造fake chunk, malloc两次拿到fake chunk
  4. 覆写exit@got.plt为one gadget, 然后给一个非法输入触发get shell

逐步分析

继续阅读“vsCTF ezorange writeup”

SUSCTF 2022 tttree writeup

SUSCTF 2022 tttree writeup

前言

SUSCTF 2022 的 tttree 这道题目使用了2021 KCTF 春季赛一位师傅提出的混淆思路, 但是网上现有的公开WP(包括官方的)和混淆器的原作者都没有很好地讲清楚应该怎么去混淆. 比赛期间时间比较紧张, 很多人也来不及理清思路, 一些师傅甚至直接手撕汇编解题(orz). 综合了多位师傅的解题思路之后, 在这里总结出一份相对比较完善的去混淆思路(完整代码见文末), 希望能对读者有所帮助, 如有更好的思路, 欢迎与我交流.

0x00 初步分析

给了一个x64的Windows命令行程序:

tttree2.exe: PE32+ executable (console) x86-64, for MS Windows 

直接运行, 提示输入flag:, 随便输入之后返回error!.

IDA加载, 找到start函数, 发现是一个很短的汇编函数:

进一步发现, 几乎整个代码段都是相似的模式. 根据计算地址后是否直接retn可以将混淆模式分为两种, 第一种模式如下:

... ; 原来的汇编代码
push    rax
push    rax
pushfq
call    $+5
pop     rax
add/xor     rax, some_imm
mov     [rsp+40h+var_30], rax
popfq
pop     rax
retn

不难发现, 该段汇编代码的作用就是将call $+5的下一条指令的地址add或者xor上某个立即数, 再通过retn跳转到计算出来的新地址, 因此这种模式可以看作是一种jmp, 其通过将原来的线性汇编代码分割成多个小块, 并且随机打乱了顺序来进行混淆.

第二种模式如下:

push    rdx
push    rbx
pop     rbx
pop     rdx
push    rax
push    rax
pushfq
call    $+5
pop     rax
add     rax, 4A8Ch
mov     [rsp+10h], rax
popfq
pop     rax
push    rax
push    rax
pushfq
call    $+5
pop     rax
add     rax, 0FFFFFFFFFFFFCBEFh
mov     [rsp+10h], rax
popfq
pop     rax
retn

这里可以看作是两次JMP模式的组合. 区别在于, 第一次JMP模式中, 计算完跳转地址后没有立即用retn跳转, 而是又重新开始了新的一次JMP模式. 仔细一想就会发现这个模式等价于做了一次call, 其中第一次放到返回地址里的是call所在的上下文中的下一条指令, 而第二次放进去的是call所调用的函数的地址.

此外还有一些比较简单的无效指令混淆, 目的应该是增加动态调试的难度:

push    rax
pop     rax
push    rbx
pop     rbx
push    rcx
push    rdx
pop     rdx
pop     rcx

有了以上的分析基础, 就可以着手一步步来去除各种混淆了.

0x01 控制流重建

继续阅读“SUSCTF 2022 tttree writeup”

Insomni’hack teaser 2022 Pwn writeup

前言

偶然在推特看到的这个比赛, 搜了下似乎国内没什么人参加, 但是往年题目质量还不错(?), 摸鱼打了打, 4道pwn只做了2个, 剩下一个是Windows Pwn一个是GameBoy, 都不太熟= =.

onetestament

glibc 2.23的heap菜单题, some old tricks 😀

限制因素:

  • 能申请的mem size只有四种: 0x18 0x30 0x60 0x7c
  • 只有在add的时候可以leak信息, show函数是没用的
  • 用的是calloc, 会把申请到的chunk清零
  • 最多只允许调用11次calloc (0-10)
  • delete里用了一个标志变量判断是否有double-free

漏洞点:

  • editoff-by-one, 在可以改下一个chunk headersize
  • 读入菜单选项的函数存在一个字节的溢出, 可以把第4块chunk(编号从0开始)是否被free过的标志变量覆写, 从而达到double free

这里有一个小知识点, 在glibc-2.23/malloc/malloc.c__libc_calloc函数中(3259行):

  /* Two optional cases in which clearing not necessary */
  if (chunk_is_mmapped (p))
    {
      if (__builtin_expect (perturb_byte, 0))
        return memset (mem, 0, sz);

      return mem;
    }

可以看到calloc 函数不会把 mmap 系统调用拿到的内存清零, 这是因为mmap系统调用拿到的内存本身就是清零的, 为了节省性能开销这里就不再调用memset清了.

因此如果能覆写chunk的IS_MMAPED位, 就可以绕过calloc的清零操作. 参考chunk结构图可以发现当一个chunk的data部分大小为0x18时, 其利用edit中的off-by-one就正好可以覆盖下一个chunk的IS_MMAPED位:

那么现在思路其实很明确了, 主要步骤:

  1. 用0x18的chunk作为辅助编辑块, 负责修改其下一个chunk的IS_MMAPED绕过calloc的清零
  2. 0x7c大小的unsorted bin chunk泄露libc基址
  3. 0x60 大小的chunk做fastbin attack, 拿到一块指向__malloc_hook的fake chunk
  4. 覆写__malloc_hookone gadget, 调用一次add拿到shell
    继续阅读“Insomni’hack teaser 2022 Pwn writeup”

VSCode IDAPython 开发环境配置

VSCode IDAPython 开发环境配置

开发工具 版本
OS Windows 10
IDA Version IDA Pro 7.5
IDACode(VSCode插件) 0.3.0

0x00 安装IDACode插件

仓库地址 https://github.com/ioncodes/idacode

VSCode中的安装

直接在vscode的插件商店中搜索idacode安装即可

IDA中的安装

  1. 将代码仓库中的/ida目录下所有文件复制到本地IDA的plugins目录下,例如我的是C:\Program Files\IDA 7.5\plugins
  2. 修改上述文件夹中的idacode_utils/settings.py, 端口如无特殊需求保持默认即可, PYTHON设置为本地IDA所使用的python解释器, 例如我使用的是conda的默认解释器: PYTHON = "C:\\ProgramData\\Miniconda3\\python.exe"
  3. 给本地IDA所使用的python解释器安装依赖:python -m pip install --user debugpy tornado
  4. 此时重启IDA, 在插件目录中应该可以看到IDACode这一项, 单击可以看到output栏中有[IDACode] Listening on 127.0.0.1:7065的输出, 说明安装成功

0x01 配置VSCode开发环境

自动补全配置

在vscode的settings(json)中把本地IDA Python库的位置添加进去, 例如我的是 C:\Program Files\IDA 7.5\python\3, 则settings中添加的项为:

"python.autoComplete.extraPaths": [
   "C:\\Program Files\\IDA 7.5\\python\\3"
],
"python.analysis.extraPaths": [
   "C:\\Program Files\\IDA 7.5\\python\\3"
],

此时把ida相关的包import进来就可以用自动补全了:

image.png

连接VSCode与IDA

继续阅读“VSCode IDAPython 开发环境配置”

L3HCTF 2021 Reverse 两道题解 (IDAAAAAA double-joy)

这波reverse全军覆没了属于是,最后1/7。。然后队友依然打到了rank3 orz orz

自己全场只看了两个题,最后0输出,麻了。

IDAAAAAA

这题tmd是个misc题吧

tag: 细节+脑洞

错误的分析方法:dump出来elf然后gdb调试

只给了idb,没有elf,直接打开发现是一个将输入解析为按照+号和-号分隔开的三个数字,比如输入-123+5+6,就会得到-123,5,6三个数字。然后这三个数字直接静态看的话必须满足5个约束条件:

image.png

但是直接在2^32的域上求解这5个方程是求不出解的,然后大家就都懵逼了,甚至开始尝试从中选三个出来解。但是这样即使把所有的情况都解出来也是错误的

正确的分析方法:看给的idb

这个idb其实一打开可以发现有一个断点,我当时脑子里大概有一秒钟疑惑了下为啥有个断点,然后就没管他。。然后就错过了

后面我直接用IDA连gdbserver调试的时候,会发现每次执行完scanf都会直接运行结束,当时以为是IDA抽风了。。没想到是故意的,但凡多想一下这个flag也到手了

然后进入正题,这个断点右键编辑,会发现里面的condition是有idapython脚本的(脚本很长,打开这个断点的时候IDA会卡一会儿):

image.png

撸出来之后:

global jIS40A
jIS40A = [...] # 一个长度1000的list,每个item是一堆bytes
N4QKUt = 0
EpUdLx = 4728923
idaapi.add_bpt(EpUdLx)
uwGgnM = idaapi.bpt_t()
idaapi.get_bpt(EpUdLx, uwGgnM)
uwGgnM.elang = "Python"
uwGgnM.condition = "N4QKUt = {}\n".format(N4QKUt) + 'VLzxDy = idaapi.get_byte(5127584 + N4QKUt)\nVLzxDy -= ord(\'a\')\nif VLzxDy == 0:\n    bYsMTa = 287\n    LjzrdT = b\'lqAT7pNI3BX\'\nelif VLzxDy == 1:\n    bYsMTa = 96\n    LjzrdT = b\'z3Uhis74aPq\'\nelif VLzxDy == 2:\n    bYsMTa = 8\n    LjzrdT = b\'9tjseMGBHR5\'\nelif VLzxDy == 3:\n    bYsMTa = 777\n    LjzrdT = b\'FhnvgMQjexH\'\nelif VLzxDy == 4:\n    bYsMTa = 496\n    LjzrdT = b\'SKnZ51f9WsE\'\nelif VLzxDy == 5:\n    bYsMTa = 822\n    LjzrdT = b\'gDJy104BSHW\'\nelif VLzxDy == 6:\n    bYsMTa = 914\n    LjzrdT = b\'PbRV4rSM7fd\'\nelif VLzxDy == 7:\n    bYsMTa = 550\n    LjzrdT = b\'WHPnoMTsbx3\'\nelif VLzxDy == 8:\n    bYsMTa = 273\n    LjzrdT = b\'mLx5hvlqufG\'\nelif VLzxDy == 9:\n    bYsMTa = 259\n    LjzrdT = b\'QvKgNmUFTnW\'\nelif VLzxDy == 10:\n    bYsMTa = 334\n    LjzrdT = b\'TCrHaitRfY1\'\nelif VLzxDy == 11:\n    bYsMTa = 966\n    LjzrdT = b\'m26IAvjq1zC\'\nelif VLzxDy == 12:\n    bYsMTa = 331\n    LjzrdT = b\'dQb2ufTZwLX\'\nelif VLzxDy == 13:\n    bYsMTa = 680\n    LjzrdT = b\'Y6Sr7znOeHL\'\nelif VLzxDy == 14:\n    bYsMTa = 374\n    LjzrdT = b\'hLFj1wl5A0U\'\nelif VLzxDy == 15:\n    bYsMTa = 717\n    LjzrdT = b\'H6W03R7TLFe\'\nelif VLzxDy == 16:\n    bYsMTa = 965\n    LjzrdT = b\'fphoJwDKsTv\'\nelif VLzxDy == 17:\n    bYsMTa = 952\n    LjzrdT = b\'CMF1Vk7NH4O\'\nelif VLzxDy == 18:\n    bYsMTa = 222\n    LjzrdT = b\'43PSbAlgLqj\'\nelse:\n    bYsMTa = -1\nif bYsMTa < 0:\n    cpu.rsp -= 8\n    cpu.rdi = 4927649\n    cpu.rax = 0\n    idaapi.patch_qword(cpu.rsp, 4202616)\n    idaapi.del_bpt(cpu.rip)\n    cpu.rip = 4263680\nelse:\n    zaqhdD = 0x486195\n    bYsMTa = jIS40A[bYsMTa]\n\n    idaapi.patch_bytes(5117568, bYsMTa)\n    idaapi.patch_bytes(5117488, LjzrdT)\n\n    cpu.rsp -= 8\n    idaapi.patch_qword(cpu.rsp, zaqhdD)\n    cpu.rdi = 5117568\n    cpu.rsi = len(bYsMTa)\n    cpu.rdx = 5117488\n    cpu.rcx = 11\n    cpu.r8 = 5117568\n    cpu.rax = 5117568\n\n    idaapi.add_bpt(zaqhdD)\n    jQfwUA = idaapi.bpt_t()\n    idaapi.get_bpt(zaqhdD, jQfwUA)\n    jQfwUA.elang = "Python"\n    jQfwUA.condition = "N4QKUt = {}\\nSdjOr3 = {}\\n".format(N4QKUt, len(bYsMTa)) + \'bYsMTa = idaapi.get_bytes(cpu.rax, SdjOr3).decode()\\nzaqhdD = 4767838\\nidaapi.add_bpt(zaqhdD)\\njQfwUA = idaapi.bpt_t()\\nidaapi.get_bpt(zaqhdD, jQfwUA)\\njQfwUA.elang = "Python"\\njQfwUA.condition = "N4QKUt = {}\\\\n".format(N4QKUt+1) + bYsMTa\\nidaapi.del_bpt(zaqhdD)\\nidaapi.add_bpt(jQfwUA)\\nidaapi.del_bpt(cpu.rip)\\ncpu.rsp -= 8\\nidaapi.patch_qword(cpu.rsp, zaqhdD)\\ncpu.rip = 4447160\\n\'\n    idaapi.del_bpt(zaqhdD)\n    idaapi.add_bpt(jQfwUA)\n    idaapi.del_bpt(cpu.rip)\n    cpu.rip = 4201909\n'
idaapi.del_bpt(EpUdLx)
idaapi.add_bpt(uwGgnM)
cpu.rsp -= 8
idaapi.patch_qword(cpu.rsp, EpUdLx)
cpu.rip = 4202096

不难发现是在这个脚本里面设置了新的断点,而且在新的断点里面加入了新的condition脚本,然后移动eip到一个能执行到断点的位置,我们condition里面的字节解析出来是:
继续阅读“L3HCTF 2021 Reverse 两道题解 (IDAAAAAA double-joy)”

Intel SGX: 基本概念

SGX是Intel实现的可信执行环境,主要面向服务器和桌面端,提供了内存加密(Memory Encryption)、访问控制(Access Control)、远程认证(Remote Attestation)、本地密封(Sealing)等功能。

0x00 Overview

关于应用程序代码与可信执行环境的基本关系:

  1. 每个application分为两部分:安全(可信)部分和不安全(不可信)的部分
  2. application启动后,会在受保护的可信内存中加载一块飞地(enclave)
  3. application通过把机密数据、代码放到enclave里来保证安全性
  4. enclave为application提供调用接口,当application调用enclave内的函数时,其内部的任何内存仅enclave自身可见
  5. enclave内存即使ring 0的攻击者也看不到,因为是CPU层面的保护。实际上在SGX的安全模型里OS、BIOS等等都可以被认为是不可信的

关于可信执行环境与用户进程的关系:

  1. application 本身包括了自身的代码、数据和enclave
  2. enclave里面也有其自身的代码、数据
  3. SGX保证enclave里面的代码和数据的integrity和confidentiality
  4. enclave的entry points在编译期就确定了
  5. enclave可以访问它所在的application里的内存,但是反过来不行
  6. 支持多线程


继续阅读“Intel SGX: 基本概念”

MIPS PWN 入门

MIPS是一种采取精简指令集(RISC)的指令集架构,突出特点是高性能,广泛被使用在许多电子产品、网络设备、个人娱乐设备与商业设备上,在路由器领域也被广泛应用。虽然今年MIPS所属公司已经宣布放弃对该架构继续进行研发设计,但是其作为x86、arm之后的第三大CPU架构阵营,现在市面上仍有大量的MIPS架构的产品,尤其是路由器芯片。此外,MIPS在学术界也非常受到追捧,很多超算竞赛冠军的设计方案都是MIPS的。就目前来看,MIPS的安全研究还是相对较为有意义的。

MIPS架构基础知识

常用汇编与流水线操作 在MIPS PWN中所常用到的汇编指令如下表所示:

image.png

MIPS架构为精简指令集, 常见的MIPS芯片流水线操作为五级, 如下图

wiki-Fivestagespipeline.png

其中IF =指令提取,ID =指令解码,EX =执行,MEM =存储器访问,WB =寄存器写回. 垂直轴是连续的指令: 横轴是时间. 在图示的情况中,最早的指令处于WB阶段,而最新的指令正在进行指令提取. 对于跳转/分支指令, 当其到达执行阶段且新的程序计数器已经产生时, 紧随其后的下一条指令实际上已经开始执行了. MIPS 规定分支之后的指令总是在分支目标指令之前执行,紧随分支指令之后的位置称为 分支延迟槽. 在没有任何可用操作时,延迟槽将填充空指令(nop)占位. 例如下面这段MIPS汇编代码中,
“`move $a0, $s1“`会在“`jalr“`跳转前执行

.text:0007F944                 move    $t9, $s0
.text:0007F948                 jalr    $t9              
.text:0007F94C                 move    $a0, $s1

这个特性在我们查找gadgets和构造payload的时候要多注意, 这也是MIPS上的PWN相比x86架构来说较为特殊的点之一.

寄存器与调用约定 常用的MIPS寄存器作用如下:

  • “`\$a0“` – “`\$a3“`:函数调用时的参数传递,若参数超过 4 个,则多余的使用堆栈传递
  • “`\$t0“`-“`\$t7“`:临时寄存器
  • “`\$s0“` – “`\$s7“`:保存寄存器,使用时需将用到的寄存器保存到堆栈
  • “`\$gp“`:全局指针,用于取数据(32K访问内);“`\$sp“`:栈指针,指向栈顶
  • “`\$fp“`:栈帧指针;“`\$ra“`:存储返回地址

MIPS的调用约定为被调用者实现堆栈平衡, 参数 1 ~ 4 分别保存在
“`\$a0“` ~ “`\$a3“` 寄存器中,剩下的参数从右往左依次入栈. MIPS的栈布局如下图所示, 某寄存器在堆栈中的位置不是确定的, 例如“`\$ra“`在某函数栈中的偏移是“`\$sp“`+N, 而在另一函数栈中的偏移是“`\$sp“`+M.

image.png

当CPU执行跳转到被调用函数后, 被调用函数将会开辟新的栈帧, 根据本函数内是否还有其他函数调用决定是否将
“`\$ra“` 入栈, 再将“`\$sp“` 入栈. 对于“`\$ra“`, 当本函数为叶子函数(函数内无其他函数调用), 则“`\$ra“`不入栈, 否则将“`\$ra“`入栈. 对于栈溢出攻击而言, 当函数为非叶子函数时, 可以直接通过覆盖栈上的“`\$ra“`来劫持控制流.

缓存非一致性
继续阅读“MIPS PWN 入门”

逆向分析某X视频APP通信协议

声明:破解他人的软件是违法行为,本文的逆向工程仅供学习研究用途。

最近朋友间流行一个国产的X视频App,其特点是使用国内网络便可以自由地观看视频。但是支持国内网络环境的在线服务往往会承担被审查的风险,因此有点好奇他的视频存储和获取是怎么实现的,于是便对其APP客户端进行了逆向分析。本文出发点仅仅是技术学习,因此所有与该App有关的信息都将打码。在下文中将称该App为JK,其对应的几个关键api接口部分字符使用x替换。

  • 目标平台:Android
  • 分析对象:JK.apk,Version=3.13.2
  • 分析工具:Charles,Jadx,IDA Pro,Frida

TL;DR 最终对JK App的视频请求协议逆向分析结果如下图所示:


APP视频获取协议逆向结果

继续阅读“逆向分析某X视频APP通信协议”