ARM技术论坛
直播中

张杰

7年用户 1282经验值
私信 关注
[问答]

如何利用ARM汇编作为跳板指令执行攻击程序呢

1. 栈 (Stack)
对ARM堆栈的理解
      堆栈严格来说应该叫做栈,栈(Stack)是限定仅在一端进行插入或删除操作的线性表。因此,对栈来说,可以进行插入或删除操作的一端端称为栈顶(top),相应地,另一端称为栈底(bottom)。不含元素的空表称为空栈。由于堆栈只允许在一端进行操作,因而按照后进先出(LIFO-Last In First Out)的原理运作。
      从栈顶的定义来看,栈顶的位置是可变的。空栈时,栈顶和栈底重合;满栈时,栈顶离栈底最远。ARM为堆栈提供了硬件支持,它使用一个专门的寄存器(堆栈指针)指向堆栈的栈顶。而且7种模式都有各自独立的堆栈指针,也就是有各自独立的堆栈空间。但这里的堆栈和uC/OS操作系统的任务堆栈又有区别,uC/OS的每个任务都有自己的堆栈,要是把uC/OS移植到ARM上,可以借助ARM的堆栈指针来实现。存储器堆栈可分为两种:
                    向上生长:向高地址方向生长,称为递增堆栈
                    向下生长:向低地址方向生长,称为递减堆栈
      堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈;堆栈指针指向下一个要放入的空位置,称为空堆栈。这样就有4中类型的堆栈表示递增和递减的满堆栈和空堆栈的各种组合。
      满递增:堆栈通过增大存储器的地址向上增长,堆栈指针指向内含有效数据项的最高地址。指令如LDMFA,STMFA等。
      空递增:堆栈通过增大存储器的地址向上增长,堆栈指针指向堆栈上的第一个空位置。指令如LDMEA,STMEA等。
      满递减:堆栈通过减小存储器的地址向下增长,堆栈指针指向内含有效数据项的最低地址。指令如LDMFD,STMFD等。
      空递减:堆栈通过减小存储器的地址向下增长,堆栈指针指向堆栈下的第一个空位置。指令如LDMED,STMED等。
      为什么说“向上生长”和“向下生长”呢?那是以为,一般画堆栈示意图都是把低地址画在下面,高地址画在上面。
      有一点需要注意的是,虽然ARM处理器核对于两种生长方式的堆栈均支持,但ADS的C语言编译器仅支持一种方式,即从上往下长,并且必须是满递减堆栈。所以STMFD等指令用的最多。
总结一下,最常见的ARM 处理器使用的是 满递减(Full-Descend Stack),即栈指针总是指向最后压入栈的有效数据项,且栈是从高地址向低地址增长。
2. ARM 汇编
main.c
#include
#include
static int func_test(char *buf)
{
char buffer[10];
strcpy(buffer, buf);
return 0;
}
void main(int argc, char *argv[])
{
printf("hello world ! n");
sleep(60);
func_test("abcdefghigklmnzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
}
$ arm-linux-androideabi-gcc --sysroot=/home/android/android-ndk-r9/platforms/android-18/arch-arm -o main main.c -g
$ arm-linux-androideabi-objdump -d -S main > main_objdump.s
结合 main_objdump.s 重点看下 func_test() 这个函数对应的汇编操作,以及栈上的内存使用使用情况。
main:     file format elf32-littlearm
Disassembly of section .plt:
00008270 <__libc_init@plt-0x14>:
    8270: e52de004 .word 0xe52de004
    8274: e59fe004 .word 0xe59fe004
    8278: e08fe00e .word 0xe08fe00e
    827c: e5bef008 .word 0xe5bef008
    8280: 00001d60 .word 0x00001d60
00008284 <__libc_init@plt>:
    8284: e28fc600 .word 0xe28fc600
    8288: e28cca01 .word 0xe28cca01
    828c: e5bcfd60 .word 0xe5bcfd60
00008290 <__cxa_atexit@plt>:
    8290: e28fc600 .word 0xe28fc600
    8294: e28cca01 .word 0xe28cca01
    8298: e5bcfd58 .word 0xe5bcfd58
0000829c :
    829c: e28fc600 .word 0xe28fc600
    82a0: e28cca01 .word 0xe28cca01
    82a4: e5bcfd50 .word 0xe5bcfd50
000082a8 :
    82a8: e28fc600 .word 0xe28fc600
    82ac: e28cca01 .word 0xe28cca01
    82b0: e5bcfd48 .word 0xe5bcfd48
000082b4 :
    82b4: e28fc600 .word 0xe28fc600
    82b8: e28cca01 .word 0xe28cca01
    82bc: e5bcfd40 .word 0xe5bcfd40
Disassembly of section .text:
000082c0 <_start>:
    82c0: e59fc05c ldr ip, [pc, #92]; 8324 <_start+0x64>
    82c4: e92d4800 push {fp, lr}
    82c8: e59f3058 ldr r3, [pc, #88]; 8328 <_start+0x68>
    82cc: e28db004 add fp, sp, #4
    82d0: e24dd010 sub sp, sp, #16
    82d4: e08fc00c add ip, pc, ip
    82d8: e79c3003 ldr r3, [ip, r3]
    82dc: e50b3014 str r3, [fp, #-20]
    82e0: e59f3044 ldr r3, [pc, #68]; 832c <_start+0x6c>
    82e4: e28b0004 add r0, fp, #4
    82e8: e79c3003 ldr r3, [ip, r3]
    82ec: e50b3010 str r3, [fp, #-16]
    82f0: e59f3038 ldr r3, [pc, #56]; 8330 <_start+0x70>
    82f4: e3a01000 mov r1, #0
    82f8: e79c3003 ldr r3, [ip, r3]
    82fc: e50b300c str r3, [fp, #-12]
    8300: e59f302c ldr r3, [pc, #44]; 8334 <_start+0x74>
    8304: e79c3003 ldr r3, [ip, r3]
    8308: e50b3008 str r3, [fp, #-8]
    830c: e59f3024 ldr r3, [pc, #36]; 8338 <_start+0x78>
    8310: e79c2003 ldr r2, [ip, r3]
    8314: e24b3014 sub r3, fp, #20
    8318: ebffffd9 bl 8284 <__libc_init@plt>
    831c: e24bd004 sub sp, fp, #4
    8320: e8bd8800 pop {fp, pc}
    8324: 00001d04 .word 0x00001d04
    8328: ffffffec .word 0xffffffec
    832c: fffffff0 .word 0xfffffff0
    8330: fffffff4 .word 0xfffffff4
    8334: fffffff8 .word 0xfffffff8
    8338: fffffffc .word 0xfffffffc
0000833c :
    833c: e59f2008 ldr r2, [pc, #8]; 834c
    8340: e3a01000 mov r1, #0
    8344: e08f2002 add r2, pc, r2
    8348: eaffffd0 b 8290 <__cxa_atexit@plt>
    834c: 00001cb4 .word 0x00001cb4
00008350 :
#include
#include
static int func_test(char *buf)
{
    8350: e92d4800 push {fp, lr}
    8354: e28db004 add fp, sp, #4
    8358: e24dd018 sub sp, sp, #24
    835c: e50b0018 str r0, [fp, #-24]
char buffer[10];
strcpy(buffer, buf);
    8360: e24b3010 sub r3, fp, #16
    8364: e1a00003 mov r0, r3
    8368: e51b1018 ldr r1, [fp, #-24]
    836c: ebffffca bl 829c
return 0;
    8370: e3a03000 mov r3, #0
}
    8374: e1a00003 mov r0, r3
    8378: e24bd004 sub sp, fp, #4
    837c: e8bd8800 pop {fp, pc}
00008380
:
void main(int argc, char *argv[])
{
    8380: e92d4800 push {fp, lr}
    8384: e28db004 add fp, sp, #4
    8388: e24dd008 sub sp, sp, #8
    838c: e50b0008 str r0, [fp, #-8]
    8390: e50b100c str r1, [fp, #-12]
printf("hello world ! n");
    8394: e59f3028 ldr r3, [pc, #40]; 83c4
    8398: e08f3003 add r3, pc, r3
    839c: e1a00003 mov r0, r3
    83a0: ebffffc0 bl 82a8
sleep(60);
    83a4: e3a0003c mov r0, #60; 0x3c
    83a8: ebffffc1 bl 82b4
func_test("abcdefghigklmnzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz");
    83ac: e59f3014 ldr r3, [pc, #20]; 83c8
    83b0: e08f3003 add r3, pc, r3
    83b4: e1a00003 mov r0, r3
    83b8: ebffffe4 bl 8350
}
    83bc: e24bd004 sub sp, fp, #4
    83c0: e8bd8800 pop {fp, pc}
    83c4: 00000044 .word 0x00000044
    83c8: 0000003c .word 0x0000003c
为了方便分析,把这一段摘出来:
static int func_test(char *buf)
{
// 指令地址 二进制指令反汇编得到的ARM 汇编指令
    8350:e92d4800 push{fp, lr}
// 两条指令地址相差为4, 所以是32bits 指令,也就是 ARM 指令,Thumb 指令为16 bits 将 fp(r11) 和 lr(r14) 压入栈中,为啥呢? ARM core registers 核心寄存器就那么些,fp, lr 每种工作模式下面都只有一个,但是函数调用套函数调用,以至于嵌套调用很深,那怎么办,
只能将 fp, lr 压入到栈中。注意的是,lr 寄存器的值在什么时候更新呢? 像执行 bl 这样的跳转链接指令的时候, ARM 处理器会将跳转指令之前的那条指令的地址放入到lr 中。这点理解清楚很重要,当压入栈的lr 被破坏掉之后,就可以依靠 lr 寄存器中的值尝试恢复 callstack。同时,需要记住 ARM 的核心寄存器都是 32 bits , fp, lr 当然也不例外。
说完了 ARM 寄存器,再说下 栈, 栈是一块内存区域( RAM/ Memory area)。ARM 处理器有一个寄存器(SP)专门存放栈指针,换句话说 sp 寄存器的值就是最后入栈的数据的内存地址。
言归正传, push ARM 汇编指令,执行压栈操作,将 fp, lr 存入到栈中,sp 会自动更新。
    Memory
1.jpg
8354:e28db004 add fp, sp, #4
// 将 sp + 4 (#4 表示立即数),得到的结果存入到fp 中,为什么 fp 是这么算出来的呢?
首先, fp 指向于栈顶。 栈是从高地址向低地址增长,因此,在执行完 fp, lr 的压栈操作之后, sp 的值比之前小了 4 (2个 4 bytes 寄存器占用 8 bytes 的内存空间)。
    8358:e24dd018 sub sp, sp, #24
// 将 sp 的值减去 24, 并将结果存入到 sp 中。这个是干什么用? 预留内存空间用于保存函数传入的参数,以及函数内部定义的数组。
    835c:e50b0018 str r0, [fp, #-24]
// 将r0 寄存器中的值存入到 fp 偏移 -24 在内存中地址。 其中r0 保存着main() 函数调用func_test() 函数时传入的 字符串的地址。指针类型size 为 4bytes.?? 相比于下面为数组进行的内存4 bytes 对齐,这里给 char * 的指针预留了 24 - 16 = 8 bytes
的内存空间不是很明白。哪位大神看到了知道答案的话还请赐教!!!
char buffer[10];
strcpy(buffer, buf);
    8360:e24b3010 subr3, fp, #16
// 把 fp 减去 16,得到的结果存入到 r3 中。
这里定义的数组为 char buffer[10]; 申请了10 bytes 的连续内存,为什么实际分配的是 16-4 = 12 bytes 呢? 估计是内存对齐,4 字节对齐。测试了一下, 改成 char buffer[12]; objdump 出来的汇编代码是完全一样的。
    8364:e1a00003 mov   r0, r3
// 将r3 中的值传送到 r0 中
    8368:e51b1018 ldrr1, [fp, #-24]
// 将fp 偏移 -24 的值在内存memory 地址中存放的内容放入到r1 中,这个其实就是 main() 调用 func_test() 函数时传入的参数,字符串指针。这两步完成将调用  strcpy() 函数的参数分别放入r0, r1 中。
    836c:ebffffca bl829c
return 0;
    8370:e3a03000 mov r3, #0
}
    8374:e1a00003 mov r0, r3
// 将返回值 写到 r0 中
    8378:e24bd004 subsp, fp, #4
// 有 fp 的好处就是算上个函数的sp 特别方便,都不需要关心在这个“子函数” func_test() 中到底用了多少栈空间,每个C 函数都要开栈,在这个函数执行完毕返回的时候,这个函数的栈空间不再有意义。
    837c:e8bd8800 pop{fp, pc}
// 将函数调用开始时,上个函数的 fp , lr 出栈,存入到 fp 和 pc 寄存器。
3. Android 执行分析
将上面写的测试代码放到 Android 手机上运行,我这里用的是 milestone (umts_sholes)。
$ adb push main /data/main && adb shell
#cd /data
#./main
在程序运行过程中,再打开一个adb shell 看下这个进程的map
$ adb shell
# ps |grep main
root      2225  2223  544    236   c007acb4 afd0c03c S ./main
# cat /proc/2225/maps
00008000-00009000 r-xp 00000000 1f:08 522        /data/main
00009000-0000a000 rw-p 00000000 1f:08 522        /data/main
0000a000-0000c000 rw-p 00000000 00:00 0          [heap]
40000000-4000b000 r--s 00000000 00:0b 233        /dev/__properties__ (deleted)
4000b000-4000c000 r--p 00000000 00:00 0
afd00000-afd40000 r-xp 00000000 1f:06 782        /system/lib/libc.so
afd40000-afd43000 rw-p 00040000 1f:06 782        /system/lib/libc.so
afd43000-afd4e000 rw-p 00000000 00:00 0
b0001000-b0009000 r-xp 00001000 1f:06 470        /system/bin/linker
b0009000-b000a000 rw-p 00009000 1f:06 470        /system/bin/linker
b000a000-b0015000 rw-p 00000000 00:00 0
be926000-be93b000 rw-p 00000000 00:00 0          [stack]
#
sleep 60s 之后程序调用func_test() 会出现 android native crash, 将 生成的tombstones log文件adb pull 出来分析一下
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'MOTO_RTEU/umts_sholes/umts_sholes/sholes:2.2.1/SHOLS_U2_05.26.3/296482885:user/release-keys'
pid: 2225, tid: 2225  >>> ./main <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 7a7a7a78
r0 00000000  r1 00008430  r2 feff0101  r3 00000000
r4 00008380  r5 be93ac84  r6 00000001  r7 be93ac8c
r8 00000000  r9 00000000  10 00000000  fp 7a7a6e6d
ip be93ac67  sp be93ac40  lr 00008370  pc 7a7a7a78  cpsr 400f0010
d0  6472656767756265  d1  000000000000006c
d2  0000000000000064  d3  0000000000000020
d4  0000000000000000  d5  0000000000000000
d6  0000000000000000  d7  0000000000000000
d8  0000000000000000  d9  0000000000000000
d10 0000000000000000  d11 0000000000000000
d12 0000000000000000  d13 0000000000000000
d14 0000000000000000  d15 0000000000000000
d16 0000000000000000  d17 0000000000000000
d18 0000000000000000  d19 0000000000000000
d20 0000000000000000  d21 0000000000000000
d22 0000000000000000  d23 0000000000000000
d24 0000000000000000  d25 0000000000000000
d26 0000000000000000  d27 0000000000000000
d28 0000000000000000  d29 0000000000000000
d30 0000000000000000  d31 0000000000000000
scr 00000000
         #00  pc 7a7a7a78  
         #01  lr 00008370  /data/main
code around pc:
7a7a7a58 ffffffff ffffffff ffffffff ffffffff
7a7a7a68 ffffffff ffffffff ffffffff ffffffff
7a7a7a78 ffffffff ffffffff ffffffff ffffffff
7a7a7a88 ffffffff ffffffff ffffffff ffffffff
7a7a7a98 ffffffff ffffffff ffffffff ffffffff
code around lr:
00008350 e92d4800 e28db004 e24dd018 e50b0018
00008360 e24b3010 e1a00003 e51b1018 ebffffca
00008370 e3a03000 e1a00003 e24bd004 e8bd8800
00008380 e92d4800 e28db004 e24dd008 e50b0008
00008390 e50b100c e59f3028 e08f3003 e1a00003
stack:
    be93ac00  00000000  
    be93ac04  00000000  
    be93ac08  be93ac4c  
    be93ac0c  afd19aad  /system/lib/libc.so
    be93ac10  4000b00c  
    be93ac14  000083e4  /data/main
    be93ac18  00008380  /data/main
    be93ac1c  be93ac84  
    be93ac20  00000001  
    be93ac24  000083f4  /data/main
    be93ac28  00008380  /data/main
    be93ac2c  64636261  
    be93ac30  68676665  
    be93ac34  6c6b6769  
    be93ac38  df002777  
    be93ac3c  e3a070ad  
#00 be93ac40  7a7a7a7a  
    be93ac44  7a7a7a7a  
    be93ac48  7a7a7a7a  
    be93ac4c  7a7a7a7a  
    be93ac50  7a7a7a7a  
    be93ac54  7a7a7a7a  
    be93ac58  7a7a7a7a  
    be93ac5c  7a7a7a7a  
    be93ac60  7a7a7a7a  
    be93ac64  00007a7a  
    be93ac68  00009ef4  
    be93ac6c  00009ee4  
    be93ac70  00009edc  
    be93ac74  00009eec  
    be93ac78  00000000  
    be93ac7c  ffffffe0  
    be93ac80  00000001  
    be93ac84  be93ad78  
结合 objdump 出来的ARM 汇编代码地址, pc ,lr ,sp 以及 lr 地址附近的指令和上面的分析一致。

原作者:windtakers


回帖(1)

刘燕

2022-5-14 11:46:35
举报

更多回帖

发帖
×
20
完善资料,
赚取积分