美文网首页我爱编程
物联网上的 ARM 漏洞利用

物联网上的 ARM 漏洞利用

作者: 看雪学院 | 来源:发表于2017-08-24 18:32 被阅读61次

    原文出自看雪论坛


    发文动机

    几周前我参加某个会议的时候,有个“物联网上的 ARM 漏洞利用课程”的议题我觉得很多干货,我也决定自己写一篇,给去不了现场的同学发些福利。我打算分为三个部分来写。

    当然我的文章没办法和现场的 course 相比,我还是想为大家做一些微小的工作。

    这三个部分是:

    第一部分:逆向 ARM 应用

    第二部分:编写 ARM shellcode

    第三部分:ARM 漏洞利用

    第一部分:逆向 ARM 应用

    环境:树莓派3

    我选了这个又便宜又好配置的环境,Android 也是个不错的选择。

    硬件

    具体型号:>树莓派3 型号B ARM-Cortex-A53架构

    软件

    这是些软件信息,接下来三部分都会用到。

    root@raspberrypi:/home/pi# cat /etc/os-release

    PRETTY_NAME="Raspbian GNU/Linux 8 (jessie)"

    NAME="Raspbian GNU/Linux"

    VERSION_ID="8"

    VERSION="8 (jessie)"

    ID=raspbian

    ID_LIKE=debian

    HOME_URL="http://www.raspbian.org/"

    SUPPORT_URL="http://www.raspbian.org/RaspbianForums"

    BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

    root@raspberrypi:/home/pi# cat /etc/rpi-issue

    Raspberry Pi reference 2017-03-02

    Generated using pi-gen, https://github.com/RPi-Distro/pi-gen, f563e32202fad7180c9058dc3ad70bfb7c09f0fb, stage2

    操作系统的安装请看:https://www.raspberrypi.org/documentation/installation/installing-images/linux.md

    配置远程 ssh 请看:https://www.raspberrypi.org/documentation/remote-access/ssh/

    编译器

    我们用到的所有 C、C++、汇编代码都会用树莓派自带的 GCC 编译器。

    版本如下:

    root@raspberrypi:/home/pi/arm/episode1# gcc --version

    gcc (Raspbian 4.9.2-10) 4.9.2

    Copyright (C) 2014 Free Software Foundation, Inc.

    This is free software; see the source for copying conditions. There is NO

    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    还有一点是 GCC 的汇编指令和其他的编译器不同,最好先看一下这些指令:http://www.ic.unicamp.br/~celio/mc404-2014/docs/gnu-arm-directives.pdf

    源码

    本部分我用到的源码都放在这里了:https://github.com/invictus1306/ARM-episodes/tree/master/Episode1

    编译选项

    这一节我会介绍三个选项,并附带例子。

    这是用到的源码:

    #include #include static char password[] = "compiler_options";

    int main()

    {

    char input_pwd[20]={0};

    fgets(input_pwd, sizeof(input_pwd), stdin);

    int size = sizeof(password);

    if(input_pwd[size] != 0)

    {

    printf("The password is not correct! \n");

    return 0;

    }

    int ret = strncmp(password, input_pwd, size-1);

    if (ret==0)

    {

    printf("Good done! \n");

    }

    else

    {

    printf("The password is not correct! \n");

    }

    return 0;

    }

    调试符号

    -g 选项会在编译时向可执行文件中加入调试信息(符号表)。

    编译带 -g 和不带 -g 选项的两个 ELF 文件,比较大小:

    root@raspberrypi:/home/pi/arm/episode1# gcc -o compiler_options compiler_options.c

    root@raspberrypi:/home/pi/arm/episode1# ls -l

    total 12

    -rwxr-xr-x 1 root root 6288 Jun 14 20:21 compiler_options

    -rw-r--r-- 1 root root 488 Jun 14 19:41 compiler_options.c

    root@raspberrypi:/home/pi/arm/episode1# gcc -o compiler_options compiler_options.c -g

    root@raspberrypi:/home/pi/arm/episode1# ls -l

    total 16

    -rwxr-xr-x 1 root root 8648 Jun 14 20:21 compiler_options

    -rw-r--r-- 1 root root 488 Jun 14 19:41 compiler_options.c

    第二个文件更大,意味着它被加入了其他的信息。用 readelf 命令的 -S 选项(查看节头)查看其调试信息:

    root@raspberrypi:/home/pi/arm/episode1# readelf -S compiler_options | grep debug

    [27] .debug_aranges PROGBITS 00000000 0007f2 000020 00 0 0 1

    [28] .debug_info PROGBITS 00000000 000812 000318 00 0 0 1

    [29] .debug_abbrev PROGBITS 00000000 000b2a 0000da 00 0 0 1

    [30] .debug_line PROGBITS 00000000 000c04 0000de 00 0 0 1

    [31] .debug_frame PROGBITS 00000000 000ce4 000030 00 0 0 4

    [32] .debug_str PROGBITS 00000000 000d14 000267 01 MS 0 0 1

    这些调试信息以 GCC 默认的 DWARF 格式存储。用 objdump 查看:

    root@raspberrypi:/home/pi/arm/episode1# objdump --dwarf=info ./compiler_options

    Abbrev Number: 14 (DW_TAG_variable)

    DW_AT_name : (indirect string, offset: 0x8a): password

    DW_AT_decl_file : 1

    DW_AT_decl_line : 4

    DW_AT_type : DW_AT_location : 5 byte block: 3 70 7 2 0 (DW_OP_addr: 20770)

    Abbrev Number: 16 (DW_TAG_variable)

    DW_AT_name : (indirect string, offset: 0x215): stdin

    DW_AT_decl_file : 5

    DW_AT_decl_line : 168

    DW_AT_type : DW_AT_external : 1

    DW_AT_declaration : 1

    Abbrev Number: 0

    去除符号表和重定位信息

    选项为 -s。

    root@raspberrypi:/home/pi/arm/episode1# gcc -o compiler_options compiler_options.c

    root@raspberrypi:/home/pi/arm/episode1# readelf --sym compiler_options

    Symbol table '.dynsym' contains 8 entries:

    Num: Value Size Type Bind Vis Ndx Name

    0: 00000000 0 NOTYPE LOCAL DEFAULT UND

    1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__

    2: 00000000 0 FUNC GLOBAL DEFAULT UND fgets@GLIBC_2.4 (2)

    3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.4 (2)

    4: 00020788 4 OBJECT GLOBAL DEFAULT 24 stdin@GLIBC_2.4 (2)

    5: 00000000 0 FUNC GLOBAL DEFAULT UND strncmp@GLIBC_2.4 (2)

    6: 00000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (2)

    7: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.4 (2)

    Symbol table '.symtab' contains 115 entries:

    Num: Value Size Type Bind Vis Ndx Name

    0: 00000000 0 NOTYPE LOCAL DEFAULT UND

    1: 00010134 0 SECTION LOCAL DEFAULT 1

    2: 00010150 0 SECTION LOCAL DEFAULT 2

    ...

    112: 00000000 0 FUNC GLOBAL DEFAULT UND strncmp@@GLIBC_2.4

    113: 00000000 0 FUNC GLOBAL DEFAULT UND abort@@GLIBC_2.4

    114: 00010318 0 FUNC GLOBAL DEFAULT 11 _init

    可以看到 .symtab 有很多本地符号,这些符号运行时并不需要,可以把它们去掉。

    root@raspberrypi:/home/pi/arm/episode1# gcc -o compiler_options compiler_options.c -s

    root@raspberrypi:/home/pi/arm/episode1# readelf --sym compiler_options

    Symbol table '.dynsym' contains 8 entries:

    Num: Value Size Type Bind Vis Ndx Name

    0: 00000000 0 NOTYPE LOCAL DEFAULT UND

    1: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__

    2: 00000000 0 FUNC GLOBAL DEFAULT UND fgets@GLIBC_2.4 (2)

    3: 00000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.4 (2)

    4: 00020788 4 OBJECT GLOBAL DEFAULT 24 stdin@GLIBC_2.4 (2)

    5: 00000000 0 FUNC GLOBAL DEFAULT UND strncmp@GLIBC_2.4 (2)

    6: 00000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.4 (2)

    7: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.4 (2)

    用过 -s 选项后,函数名之类的信息已经去掉了,某个逆向小子的生活又艰难了一步。

    在之后的第三部分我会介绍其他更复杂的编译选项。

    ARM Hello World

    我们以两种方式开始这个 hello world 的研究:

    使用树莓派系统调用

    使用 libc 函数

    使用树莓派的系统调用

    .data

    string: .asciz "Hello World!\n"

    len = . - string

    .text

    .global _start

    _start:

    mov r0, #1      @ stdout

    ldr r1, =string @ string address

    ldr r2, =len    @ string length

    mov r7, #4      @ write syscall

    swi 0           @ execute syscall

    _exit:

    mov r7, #1      @ exit syscall

    swi 0           @ execute syscall

    汇编并链接此程序:

    root@raspberrypi:/home/pi/arm/episode1# as -o rasp_syscall.o rasp_syscall.s

    root@raspberrypi:/home/pi/arm/episode1# ld -o rasp_syscall rasp_syscall.o

    注意:如果用 gcc,

    root@raspberrypi:/home/pi/arm/episode1# gcc -o rasp_syscall rasp_syscall.s

    /tmp/ccChPTEP.o: In function `_start':

    (.text+0x0): multiple definition of `_start'

    /usr/lib/gcc/arm-linux-gnueabihf/4.9/../../../arm-linux-gnueabihf/crt1.o:/build/glibc-g3vikB/glibc-2.19/csu/../ports/sysdeps/arm/start.S:79: first defined here

    /usr/lib/gcc/arm-linux-gnueabihf/4.9/../../../arm-linux-gnueabihf/crt1.o: In function `_start':

    /build/glibc-g3vikB/glibc-2.19/csu/../ports/sysdeps/arm/start.S:119: undefined reference to `main'

    collect2: error: ld returned 1 exit status

    会得到错误:

    undefined reference to `main'

    这是因为源程序里没有 main 函数,在另一种实现里我们会看到如何使用 gcc 编译。

    执行:

    root@raspberrypi:/home/pi/arm/episode1# ./rasp_syscall

    Hello World!

    接下来使用 gdb:

    root@raspberrypi:/home/pi/arm/episode1# gdb -q ./rasp_syscall

    Reading symbols from ./rasp_syscall...(no debugging symbols found)...done.

    (gdb) info files

    Symbols from "/home/pi/arm/episode1/rasp_syscall".

    Local exec file:

    `/home/pi/arm/episode1/rasp_syscall', file type elf32-littlearm.

    Entry point: 0x10074

    0x00010074 - 0x00010094 is .text

    0x00020094 - 0x000200a2 is .data

    (gdb) b *0x00010074

    Breakpoint 1 at 0x10074

    (gdb) r

    Starting program: /home/pi/arm/episode1/rasp_syscall

    Breakpoint 1, 0x00010074 in _start ()

    (gdb) x/7i $pc

    => 0x10074 : mov r0, #1

    0x10078 : ldr r1, [pc, #16] ; 0x10090 0x1007c : mov r2, #14

    0x10080 : mov r7, #4

    0x10084 : svc 0x00000000

    0x10088 : mov r7, #1

    0x1008c : svc 0x00000000

    可以看到 .text 段中放着我们的代码。0x10078 处的指令代表将 0x10090 指向的值载入 r1。

    (gdb) x/14c *(int*)0x10090

    0x20094: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 32 ' ' 87 'W' 111 'o'

    0x2009c: 114 'r' 108 'l' 100 'd' 33 '!' 10 '\n' 0 '\000'

    使用 libc 函数

    这次我们会使用 printf 函数,这里我们将程序中的 .global _start 改为  .global main。

    .data

    string: .asciz "Hello World!\n"

    .text

    .global main

    .func main

    main:

    stmfd sp!, {lr}    @ save lr

    ldr r0, =string    @ store string address into R0

    bl printf          @ call printf

    ldmfd sp!, {pc}    @ restore pc

    _exit:

    mov lr, pc         @ exit

    编译器需要我们指定 global main, .func main, main: 等符号告诉 libc main 函数在哪。

    root@raspberrypi:/home/pi/arm/episode1# as -o libc_functions.o libc_functions.s

    root@raspberrypi:/home/pi/arm/episode1# ld -o libc_functions libc_functions.o

    ld: warning: cannot find entry symbol _start; defaulting to 00010074

    libc_functions.o: In function `main':

    (.text+0x8): undefined reference to `printf'

    汇编器和链接器只是 GCC 的一小部分,下面我们会用到 GCC 其他的特性来编译。

    root@raspberrypi:/home/pi/arm/episode1# gcc -o libc_functions libc_functions.s

    root@raspberrypi:/home/pi/arm/episode1# gdb -q ./libc_functions

    Reading symbols from ./libc_functions...(no debugging symbols found)...done.

    (gdb) b main

    Breakpoint 1 at 0x10420

    (gdb) r

    Starting program: /home/pi/arm/episode1/libc_functions

    Breakpoint 1, 0x00010420 in main ()

    (gdb) info proc mappings

    process 2023

    Mapped address spaces:

    Start Addr End Addr Size Offset objfile

    0x10000 0x11000 0x1000 0x0 /home/pi/arm/episode1/libc_functions

    0x20000 0x21000 0x1000 0x0 /home/pi/arm/episode1/libc_functions

    0x76e79000 0x76fa4000 0x12b000 0x0 /lib/arm-linux-gnueabihf/libc-2.19.so

    0x76fa4000 0x76fb4000 0x10000 0x12b000 /lib/arm-linux-gnueabihf/libc-2.19.so

    0x76fb4000 0x76fb6000 0x2000 0x12b000 /lib/arm-linux-gnueabihf/libc-2.19.so

    0x76fb6000 0x76fb7000 0x1000 0x12d000 /lib/arm-linux-gnueabihf/libc-2.19.so

    0x76fb7000 0x76fba000 0x3000 0x0

    0x76fba000 0x76fbf000 0x5000 0x0 /usr/lib/arm-linux-gnueabihf/libarmmem.so

    0x76fbf000 0x76fce000 0xf000 0x5000 /usr/lib/arm-linux-gnueabihf/libarmmem.so

    0x76fce000 0x76fcf000 0x1000 0x4000 /usr/lib/arm-linux-gnueabihf/libarmmem.so

    0x76fcf000 0x76fef000 0x20000 0x0 /lib/arm-linux-gnueabihf/ld-2.19.so

    0x76ff1000 0x76ff3000 0x2000 0x0

    0x76ff9000 0x76ffb000 0x2000 0x0

    0x76ffb000 0x76ffc000 0x1000 0x0 [sigpage]

    0x76ffc000 0x76ffd000 0x1000 0x0 [vvar]

    0x76ffd000 0x76ffe000 0x1000 0x0 [vdso]

    0x76ffe000 0x76fff000 0x1000 0x1f000 /lib/arm-linux-gnueabihf/ld-2.19.so

    0x76fff000 0x77000000 0x1000 0x20000 /lib/arm-linux-gnueabihf/ld-2.19.so

    0x7efdf000 0x7f000000 0x21000 0x0 [stack]

    0xffff0000 0xffff1000 0x1000 0x0 [vectors]

    可以看到在进程的地址空间里加载了 libc 共享库(libc-2.19.so)。

    (gdb) x/5i $pc

    => 0x10420 : stmfd   sp!, {lr}

    0x10424 :  ldr r0, [pc, #8]    ; 0x10434

    0x10428 :  bl  0x102c8

    0x1042c : ldmfd   sp!, {pc}

    0x10430 :   mov lr, pc

    0x10428 处调用了 printf 函数,0x10428 是个 PLT 入口,指向 GOT 中 printf 函数在运行时的真实地址。

    GCC 编译时,libc 的函数并没有被编译到可执行文件中,而是通过动态链接到 libc 使之可用。用 ldd 命令查看程序引用的动态库。

    root@raspberrypi:/home/pi/arm/episode1# ldd libc_functions

    linux-vdso.so.1 (0x7eeb1000)

    /usr/lib/arm-linux-gnueabihf/libarmmem.so (0x76fe6000)

    libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76e9f000)

    /lib/ld-linux-armhf.so.3 (0x54b6d000)

    可用看到 libc 是程序所需要的。多次查看如果 libc 的地址不同,是因为打开了 ASLR。用 IDA 打开:

    0x10428 处调用 printf,双击并没有进入 libc。

    而是到了 PLT 段,0x102D0 处通过 LDR PC, […] 修改 PC 跳转到了其他地址。

    到达 GOT 段,这里存着外部符号的地址。

    下面用 gdb 调试,断点下在 0x10428 处。

    Breakpoint 2, 0x00010428 in main ()Breakpoint 2, 0x00010428 in main ()

    (gdb) x/i $pc

    => 0x10428 : bl 0x102c8

    stepi 继续运行。

    走几步到达 ld 库中的 dl_runtime_resolve 函数。

    ld 是动态链接器,这里建立起了 libc 的外部引用环境。

    更多细节参考http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries/。

    逆向工程介绍

    这部分对于要分析的程序我不会提供源码。

    逆向算法

    第一个例子是输入一个字符串,经过算法处理后会输出另一个字符串,我需要使输出字符串为“Hello”。

    strIN -------[algorithm]-------strOUT

    strOUT = Hello

    下面是源码(我只会提供这个例子的):

    .data

    .balign 4

    info: .asciz "Please enter your string: "

    format: .asciz "%5s"

    .balign 4

    strIN: .skip 5

    strOUT: .skip 5

    val: .byte 0x5

    output: .asciz "your input: %s\n"

    .text

    .global main

    .extern printf

    .extern scanf

    main:

    push {ip, lr}      @ push return address + dummy register

    ldr r0, =info      @ print the info

    bl printf

    ldr r0, =format

    ldr r1, =strIN

    bl scanf

    @ parsing of the message

    ldr r5, =strOUT

    ldr r1, =strIN

    ldrb r2, [r1]

    ldrb r3, [r1,#1]

    eor r0, r2, r3

    str r0, [r5]

    ldrb r4, [r1,#2]

    eor r0, r4, r3

    str r0, [r5,#1]

    add r2, #0x5

    str r2, [r5,#2]

    ldrb r4, [r1,#3]

    eor r0, r3, r4

    str r0, [r5,#3]

    ldrb r2, [r1,#4]

    eor r0, r2, r4

    str r0, [r5,#4]

    @ print of the final string

    ldr r0, =strOUT    @ print num formatted by output string.

    bl printf

    pop {ip, pc}       @ pop return address into pc

    编译:

    root@raspberrypi:/home/pi/arm/episode1# gcc -o algorithm_reversing algorithm_reversing.s

    调试理解算法:

    root@raspberrypi:/home/pi/arm/episode1# gdb -q ./algorithm_reversing

    Reading symbols from ./algorithm_reversing...(no debugging symbols found)...done.

    (gdb) b main

    Breakpoint 1 at 0x10450

    (gdb) r

    Starting program: /home/pi/arm/episode1/algorithm_reversing

    Breakpoint 1, 0x00010450 in main ()

    (gdb) x/10i $pc

    => 0x10450 : push    {r12, lr}

    0x10454 :  ldr r0, [pc, #92]   ; 0x104b8

    0x10458 :  bl  0x102ec

    0x1045c : ldr r0, [pc, #88]   ; 0x104bc

    0x10460 : ldr r1, [pc, #88]   ; 0x104c0

    0x10464 : bl  0x10304

    0x10468 : ldr r5, [pc, #84]   ; 0x104c4

    0x1046c : ldr r1, [pc, #76]   ; 0x104c0

    0x10470 : ldrb    r2, [r1]

    0x10474 : ldrb    r3, [r1, #1]

    0x10454 代表 r0=*(pc+92)。查看 pc+92 的内容:

    (gdb) x/x 0x104b8

    0x104b8 :   0x00020668

    是数据段的地址,看一下内容:

    (gdb) x/s 0x20668

    0x20668: "Please enter your string: "

    0x20668 是 printf 函数的参数。

    运行到 0x10464,r0 是格式的地址,r1 是输入字符串的地址。

    (gdb) i r $r0 $r1

    r0 0x20683 132739

    r1 0x20688 132744

    (gdb) nexti

    从源码中可以看到输入字符串的长度为5:

    format: .asciz "%5s"

    strIN: .skip

    输入 “ABCDE”:

    (gdb) nexti

    Please enter your string: ABCDE

    0x10468 和 0x1046c 处的指令将输出字符串地址赋给 r5,输入字符串地址赋给 r1。运行至 0x10470:

    (gdb) x/18i $pc

    => 0x10470 :  ldrb    r2, [r1]

    0x10474 : ldrb    r3, [r1, #1]

    0x10478 : eor r0, r2, r3

    0x1047c : str r0, [r5]

    0x10480 : ldrb    r4, [r1, #2]

    0x10484 : eor r0, r4, r3

    0x10488 : str r0, [r5, #1]

    0x1048c : add r2, r2, #5

    0x10490 : str r2, [r5, #2]

    0x10494 : ldrb    r4, [r1, #3]

    0x10498 : eor r0, r3, r4

    0x1049c : str r0, [r5, #3]

    0x104a0 : ldrb    r2, [r1, #4]

    0x104a4 : eor r0, r2, r4

    0x104a8 : str r0, [r5, #4]

    0x104ac : ldr r0, [pc, #16]   ; 0x104c4

    0x104b0 : bl  0x102ec

    0x104b4 :    pop {r12, pc}

    注释如下:

    0x10470 : ldrb    r2, [r1]        ; r2 <- *r1

    0x10474 : ldrb    r3, [r1, #1]  ; r3  *r5

    运行到 0x10480 处查看 r0, r2, r3 的值:

    (gdb) i r $r0 $r2 $r3

    r0 0x3 3

    r2 0x41 65

    r3 0x42 66

    即*r5 = r2 xor r3。

    可以用伪代码表示:byte1strOut = byte1strInput xor byte2strInput。 比如我们想要生成“Hello”,需要使 r0 为 0x48(H)。

    接着看 0x10480,

    (gdb) x/8i $pc

    => 0x10480 :  ldrb    r4, [r1, #2]

    0x10484 : eor r0, r4, r3

    0x10488 : str r0, [r5, #1]

    0x1048c : add r2, r2, #5

    0x10490 : str r2, [r5, #2]

    0x10494 : ldrb    r4, [r1, #3]

    0x10498 : eor r0, r3, r4

    0x1049c : str r0, [r5, #3]

    注意看注释:

    0x10480 : ldrb    r4, [r1, #2]   ; r4  *(r5+1)

    执行到 0x1048c,看一下 r0、r3、r4 的值:

    (gdb) i r $r0 $r3 $r4

    r0 0x1 1

    r3 0x42 66

    r4 0x43 67

    即*(r5+1) = r4 xor r3。伪代码表示:byte2strOut = byte2strInput xor byte3strInput。

    0x1048c :    add r2, r2, #5

    0x10490 :    str r2, [r5, #2]

    意味着*(r5+2) = r2 + 0x5。伪代码表示:byte3outStr = byte1strInput + 0x5。

    第 4 个字节:

    0x10494 :    ldrb    r4, [r1, #3]

    0x10498 :    eor r0, r3, r4

    0x1049c :    str r0, [r5, #3]

    就是*(r5+3) = r3 xor r4。伪代码:byte4strOut = byte2strInput xor byte4strInput。

    第 5 个字节:

    0x104a0 :    ldrb    r2, [r1, #4]

    0x104a4 :    eor r0, r2, r4

    0x104a8 :    str r0, [r5, #4]

    就是*(r5+4) = r4 xor r2。伪代码:byte5strOut = byte4strInput xor byte5strInput。

    整个算法合在一起:

    byte1strOut = byte1strInput xor byte2strInput

    byte2strOut = byte2strInput xor byte3strInput

    byte3strOut = byte2strInput + 0x5

    byte4strOut = byte2strInput xor byte4strInput

    byte5strOut = byte4strInput xor byte5strInput

    将输出字符替换:

    ‘H’ = 0x48 = byte1strInput xor byte2strInput

    ‘e’ = 0x65 = byte2strInput xor byte3strInput

    ‘l’ = 0x6c = byte1strInput + 0x5

    ‘l’ = 0x6c = byte2strInput xor byte4strInput

    ‘o’ = 0x6f = byte4strInput xor byte5strInput

    推出应输入字符:

    byte1strInput = 0x6c – 0x5 = 0x67 (g)

    byte2strInput = 0x48 xor 0x67 = 0x2f (/)

    byte3strInput = 0x2f xor 0x65 = 0x4a (J)

    byte4strInput = 0x2f xor 0x6c = 0x43 (C)

    byte5strInput = 0x43 xor 0x6f = 0x2c (,)

    输入试试:

    root@raspberrypi:/home/pi/arm/episode1# ./algorithm_reversing

    Please enter your string: g/JC,

    Hello

    逆向一个简单的加载器

    这个加载器的作用是把指令加载到内存里,当你打印消息时执行。我们这次要打印“WIN”。程序在这里

    root@raspberrypi:/home/pi/arm/episode1# file loader_reversing

    loader_reversing: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

    root@raspberrypi:/home/pi/arm/episode1# strings loader_reversing

    Andrea Sindoni @invictus1306

    aeabi

    .symtab

    .strtab

    .shstrtab

    .text

    .data

    .ARM.attributes

    loader_reversing.o

    mystr

    code

    _loop

    _exit

    _bss_end__

    __bss_start__

    __bss_end__

    _start

    __bss_start

    __end__

    _edata

    _end

    用 IDA 打开:

    在 _start 这儿可以看到 0x10090 处有系统调用,调用号是 0xc0(mmap)。

    看下面我的注释分析:

    mov r4, #0xffffffff  @file descriptor

    ldr r0, =0x00030000  @address

    ldr r1, =0x1000      @size of the mapping table

    mov r2, #7           @prot

    mov r3, #0x32        @flags

    mov r5, #0           @offset

    mov r7, #192         @syscall number

    swi #0 @ mmap2(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, -1, 0)

    mmap 后有一块新内存(0x30000)。

    (gdb) info proc mappings

    process 2405

    Mapped address spaces:

    Start Addr End Addr Size Offset objfile

    0x10000 0x11000 0x1000 0x0 /home/pi/arm/episode1/loader_reversing

    0x20000 0x21000 0x1000 0x0 /home/pi/arm/episode1/loader_reversing

    0x30000 0x31000 0x1000 0x0

    0x76ffd000 0x76ffe000 0x1000 0x0 [sigpage]

    0x76ffe000 0x76fff000 0x1000 0x0 [vvar]

    0x76fff000 0x77000000 0x1000 0x0 [vdso]

    0x7efdf000 0x7f000000 0x21000 0x0 [stack]

    0xffff0000 0xffff1000 0x1000 0x0 [vectors]

    0x10098 处的指令.text:00010098 LDR R1, =code把某个变量的地址存入 r1,看一下:

    (gdb) i r $r1

    r1 0x200f1 131313

    (gdb) x/10x 0x200f1

    0x200f1: 0xe93f7c56 0xe25fe45e 0xe1b2745b 0xe3b21468

    0x20101: 0xe3b20454 0xe3b264c0 0xe0302453 0xe49f2457

    0x20111: 0xe2501448 0xe0302453

    这些看起来不像 arm 代码,接着看 0x100A4:

    .text:000100A4 LDR R2, [R1,R4]把 r1+r4 地址额值存入 r2,r1 是 code 变量,r4 表示索引,第一次值为 0。

    .text:000100A8 EOR R2, R2, R6r2 与 r6 异或,r6 的值是 0x123456,第一次 r2 的值是 0x56。异或的值存在 r2,在下条指令.text:000100AC STR R2, [R0,R4]中被写入 mmap 分配的地址 0x30000 处,注意 r0 是 mmap 的返回值。

    循环的作用是解密 code 的所有字节,在 0x100BC 处下断点查看 0x30000 的值。

    (gdb) b *0x100bc

    Breakpoint 3 at 0x100bc

    (gdb) c

    Continuing.

    Breakpoint 3, 0x000100bc in _loop ()

    (gdb) x/24i 0x30000

    0x30000: push {r11, lr}

    0x30004: sub sp, sp, #8

    0x30008: mov r4, sp

    0x3000c: mov r2, #62 ; 0x3e

    0x30010: mov r3, #2

    0x30014: mov r5, #150 ; 0x96

    0x30018: eor r1, r2, r5

    0x3001c: str r1, [sp], #1

    0x30020: sub r2, r2, #30

    0x30024: eor r1, r2, r5

    0x30028: str r1, [sp], #1

    0x3002c: add r2, r2, #7

    0x30030: subs r3, r3, #1

    0x30034: bne 0x30024

    0x30038: mov r0, #1

    0x3003c: mov r3, #10

    0x30040: str r3, [sp], #1

    0x30044: mov r1, r4

    0x30048: mov r2, #4

    0x3004c: mov r7, #4

    0x30050: svc 0x00000000

    0x30054: add sp, sp, #4

    0x30058: pop {r11, pc}

    0x3005c: andeq r0, r0, r0

    这些就是 ARM 指令了,也可以用 idc 脚本解密:

    auto i, t;

    auto start=0x200f1;

    for (i=0;i<=0x5C;i=i+4)

    {

    t = Dword(start)^0x123456;

    PatchDword(start,t);

    start=start+4;

    }

    现在来分析解密的指令:

    => 0x30004:  sub sp, sp, #8

    0x30008: mov r4, sp

    0x3000c: mov r2, #62 ; 0x3e

    0x30010: mov r3, #2

    0x30014: mov r5, #150    ; 0x96

    0x30018: eor r1, r2, r5

    0x3001c: str r1, [sp], #1

    0x30020: sub r2, r2, #30

    0x30024: eor r1, r2, r5

    0x30028: str r1, [sp], #1

    0x3002c: add r2, r2, #7

    0x30030: subs    r3, r3, #1

    0x30034: bne 0x30024

    0x30038: mov r0, #1

    0x3003c: mov r3, #10

    0x30040: str r3, [sp], #1

    0x30044: mov r1, r4

    0x30048: mov r2, #4

    0x3004c: mov r7, #4

    0x30050: svc 0x00000000

    0x30054: add sp, sp, #4

    0x30058: pop {r11, pc}

    执行过 0x30004 到 0x30014 的 5 条指令后,栈指针向低地址处移动了 8 ,r4 是栈指针,r2 的值是 0x3e,r3 的值是 0x2,r5 的值是 0x96。

    (gdb) i r $r2 $r3 $r4 $r5 $sp

    r2 0x3e 62

    r3 0x2 2

    r4 0x7efff7b0 2130704304

    r5 0x96 150

    sp 0x7efff7b0 0x7efff7b0

    接下来两条指令(0x30018 和 0x3001c)r2 与 r5 异或的结果 0xa8 存入 r1,这个值写入了栈顶,栈指针向高地址移动了 1 。此时:

    (gdb) x/x 0x7efff7b0

    0x7efff7b0: 0x000000a8

    (gdb) i r $sp

    sp 0x7efff7b1 0x7efff7b1

    0x30020 处 r2 自减 0x1e,得到:

    (gdb) i r $r2

    r2 0x20 32

    0x30024 处是一个循环:

    => 0x30024:  eor r1, r2, r5

    0x30028: str r1, [sp], #1

    0x3002c: add r2, r2, #7

    0x30030: subs    r3, r3, #1

    0x30034: bne 0x30024

    每次循环都将 r2 和 r5 异或,结果存入栈顶,sp + 1。0x30030 处可以看到 r3 是循环索引,每次减1,初始值为 2,所以共循环两次。

    循环结束时运行到 0x30038时, 0x7efff7b0 值为:

    (gdb) x/4bx 0x7efff7b0

    0x7efff7b0: 0xa8 0xb6 0xb1 0x00

    还有两个字节在栈顶存着,此时栈指针为:

    (gdb) i r $sp

    sp 0x7efff7b3 0x7efff7b3

    0x3003c 处的两条指令将剩下的两个字节存入栈顶:

    0x3003c: mov r3, #10

    0x30040: str r3, [sp], #1

    执行完 0x30040 后0x7efff7b0 值为:

    (gdb) x/4bx 0x7efff7b0

    0x7efff7b0: 0xa8 0xb6 0xb1 0x0a

    往下看就是 write 的系统调用了:

    0x30038: mov r0, #1  @ fd: stdout

    ...

    0x30044: mov r1, r4  @ buf: r4 (the buffer stored at 0xbefff7e0;)

    0x30048: mov r2, #4  @ count: len of the buffer

    0x3004c: mov r7, #4  @ write syscall number

    0x30050: svc 0x00000000

    write 过后:

    (gdb) nexti

    ���

    我们想要的是“WIN”,这时候就要修改 xor 的 key,这样存入栈顶的才会是正确的0x57 0x49 0x4e。

    来看 0x30018 处的异或操作0x30018: eor r1, r2, r5,r2 每次都变,所以 r5 是异或的 key,我们需要修改 r5 使得r1 = r2 xor r5 = 0x57。

    r2 的值是 0x3e,则 r5 应该是 0x69。

    (gdb) set $r5=0x69

    (gdb) i r $r5

    r5 0x69 105

    异或的 key 没有变过,这就直接继续执行就可以了。

    (gdb) c

    Continuing.

    WIN

    基本的反调试技术

    这是本章最后一个程序了,这次需要理解算法并绕过一些基本的反调试,使程序输出“Good”。在这里下载

    anti_dbg: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=7028a279e2161c298caeb4db163a96ee2b2c49f3, not stripped

    试着用调试器运行:

    root@raspberrypi:/home/pi/arm/episode1# gdb -q ./anti_dbg

    Reading symbols from ./anti_dbg...(no debugging symbols found)...done

    (gdb) r

    Starting program: /home/pi/arm/episode1/anti_dbg

    You want debug me?

    [Inferior 1 (process 2497) exited normally]

    即使再用 strace/ltrace 命令也是同样的输出。

    IDA 打开:

    我们从ldr r2, =aAd开始分析。

    aAd 是个变量:

    把 Array 转为 data 更好理解:

    0x10988 处的数组用 var_c 表示,还有另一个变量 var_10,aAd + 4 的值如下:

    即 var_10 变量存着 0x1098C 处的数组。

    看接下来的指令做了什么:

    LDRH R1, [R2]                        @ load an halfword (2 byte) into R1

    LDRB R2, [R2,#(unk_109CE – 0x109CC)] @ load the next byte(0x44) into r2

    STRH R1, [R3]                        @ store into *R3 the first two bytes (0x22, 0x41)

    STRB R2, [R3,#2]                     @ store the last byte 0x44 into *(R3+2)

    总结来说就是有两个数组:

    4个元素的 var_c:0x7, 0x2f, 0x2f, 0x24; 3个元素的 var_10:0x22, 0x41, 0x44。

    下面有个 flag 变量,我们来看 main 函数中关于它的流程图:

    flag为1,红色执行,不为1绿色执行。

    flag为1,r3 为 0 随后与 3 比较。

    flag不为1,r3 为 0 随后 与 2 比较。

    flag 为 1,我们来到 loc_107F8,最关键的是这句ADD R3, R3, #0x40,r3 的值是r3 = *(var_C+var_8),var_C 和 var_8 分别是:

    var_C = 4个元素的数组

    var_8 = 0 (索引,第一次的值)

    相加之后 r3 的值为r3 = 0x7 + 0x40 = 0x47。

    可以用个简单的 idc 脚本计算:

    结果是 Good:

    再来看flag不为1时的 loc_10864,这里的循环计算的是3个元素的数组,关键的是ADD R3, R3, #0x20。

    像之前一样,idc 脚本

    结果是 Bad:

    为了使程序输出“Good”,需要找到 flag 赋值的地方,而且刚刚并没有发现检测调试器的地方, “You want debug me?” 也没有出现过。

    查看 flag 的交叉引用:

    发现有个 ptrace_capt 的函数,在 main 函数前执行。可以在 .ctors (或者 .init_array) 段中发现其提供了一些列函数用来在程序开始/结束前执行。

    来看 ptrace_capt 函数:

    这里有个检查:

    if(ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)

    {

    printf("You want debug me?\n");

    exit(0);

    }

    用调试器可以轻易绕过,先来看 loc_10690:

    大致如下:

    只读模式打开 password.raw;

    fopen("password.raw", "r")

    计算大小

    .text:000106B4 LDR R0, [R11,#var_10] ; load the file descriptor into r0

    .text:000106B8 MOV R1, #0            ; offset

    .text:000106BC MOV R2, #2            ; SEEK_END

    .text:000106C0 BL fseek              ; seek to end of file

    .text:000106C4 LDR R0, [R11,#var_10] ; load the file descriptor into r0

    .text:000106C8 BL ftell              ; size

    验证大小是否小于 6

    .text:000106E4 LDR R3, [R11,#var_14]

    .text:000106E8 CMP R3, #6

    .text:000106EC BLS loc_106F

    .text:000106F0 MOV R0, #0

    .text:000106F4 BL exit

    如果小于等于6,来到 loc_10700:

    往下看发现这是个循环:

    调用 fgetc 函数:

    .text:00010700 LDR R0, [R11,#var_10] ; load into r0 the file descriptor

    .text:00010704 BL fgetc

    .text:00010708 STR R0, [R11,#var_18  ; save r0 into the local variable var_18

    after we have the function feof

    .text:0001070C LDR R0, [R11,#var_10] ; load into r0 the file descriptor

    .text:00010710 BL feof

    .text:00010714 MOV R3, R0            ; mov the reterun value into r3

    .text:00010718 CMP R3, #0            ; compare r3 with 0

    .text:0001071C BEQ loc_10750         ; associated with the stream is not set (r3=0) branch to loc_10750

    如果 r3 等于 0,则来到 loc_10750:

    .text:00010750 loc_10750 ; CODE XREF: ptrace_capt+D0#j

    .text:00010750 SUB R3, R11, #-var_1C  ; r3 = address of var_1C

    .text:00010754 LDR R0, [R11,#var_18]  ; r0 ← *(r11+var_18)

    .text:00010758 LDR R1, [R11,#var_8]   ; r1 ← *(r11+var_8)

    .text:0001075C MOV R2, R3             ; r2 = r3

    .text:00010760 BL sub0

    var_18 是读取的字符,var_8 是循环索引,sub0 的调用则为sub0(var_18, var_8, &var_1C);。

    看 sub0 函数:

    C 代码:

    if(var_C==0 || var_C==2)

    {

    //loc_1060C

    *var_10=var_8|0x55;

    }

    else

    {

    //loc_10620

    *var_10=var_8^0x69 | var_8<<3;

    }

    sub0 返回时继续执行,var_1C 保存返回值:

    .text:00010764 LDR R3, [R11,#var_1C]

    .text:00010768 LDR R2, [R11,#var_C]

    .text:0001076C ADD R3, R2, R3

    .text:00010770 STR R3, [R11,#var_C]

    .text:00010774 LDR R3, [R11,#var_8]

    .text:00010778 ADD R3, R3, #1

    .text:0001077C STR R3, [R11,#var_8]

    .text:00010780 B loc_10700

    这段用伪码表示即:

    var_C = var_1C + var_C;

    var_8++; //increment the index

    如果 r3 不等于 0,则来到:

    .text:00010720 LDR R3, [R11,#var_C]

    .text:00010724 LDR R2, =0x997

    .text:00010728 CMP R3, R2

    .text:0001072C BNE loc_10740

    .text:00010730 LDR R3, =flag

    .text:00010734 MOV R2, #1

    .text:00010738 STR R2, [R3]

    .text:0001073C B loc_10784

    .text:00010740 loc_10740 ; CODE XREF: ptrace_capt+E0#j

    .text:00010740 LDR R3, =flag

    .text:00010744 MOV R2, #2

    .text:00010748 STR R2, [R3]

    .text:0001074C B loc_10784

    C 代码表示:

    if (var_C==0x997)

    {

    flag=1;

    }

    else

    {

    //loc_10740

    flag=2;

    }

    终于找到了 flag 赋值的地方,而我们需要其值为 1。

    新建 password.raw 文件,写入:

    # vim password.raw

    bbbbb

    我用 vim 的设置删掉了换行:

    :set noendofline binary

    运行:

    现在用 gdb 运行去掉反调试:# gdb ./3b

    在 0x10678 处下断,修改 r3 的值:

    继续往下分析,我现在要使 var_C=0x997,flag 才能赋值为 1。现在文件里的内容是:

    要修改第五个字节使得 var_C=0x997,就要知道第4次循环时 var_C 的值。

    在 0x10774 下断点,

    此时循环索引为 3(第4次),var_C 的值为 0x724,现在要改掉第 5 个字节的值,我用了 Python 来计算:

    num = 0x997-0x724

    for c in range (0x20,0x7f):

    ref = c^0x69 | (c<<3)

    if (ref==num):

    print "The number is " + hex(c)

    print "End!"

    运行:

    # python antDgbAlgho.py

    The number is 0x4a

    End!

    得到了第五个字节的值,修改它:

    # vim password.raw

    bbbbJ

    记得删掉换行:set noendofline binary。

    运行:

    “Good” 就被打印了。

    本文由看雪翻译小组 kiyaa 编译,来源quequero@andrea sindoni

    转载请注明来自看雪社区

    相关文章

      网友评论

        本文标题:物联网上的 ARM 漏洞利用

        本文链接:https://www.haomeiwen.com/subject/qlgedxtx.html