Get the executable here
Instructions
- Give the program the correct argument so it will print the flag.
- Do not reverse the decrypt function or modify the program.
Let's Begin
Let's take a look what type of file it is.
┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/offByOne]
└─$ file a.out
a.out: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
It is an arm file, so let's push it to our android device.
──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/offByOne]
└─$ adb push a.out /data/local/tmp
* daemon not running; starting now at tcp:5037
* daemon started successfully
a.out: 1 file pushed. 0.2 MB/s (3392956 bytes in 13.310s)
Next, try to run it in our android device.
126|root@hammerhead:/data/local/tmp # chmod +x a.out
root@hammerhead:/data/local/tmp # ./a.out
usage: ./a.out <argument>
It requires an argument. I proceeded to give it a short and a long argument.
root@hammerhead:/data/local/tmp # ./a.out aaaa
You failed :(
root@hammerhead:/data/local/tmp # /a.out aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You failed :(
Both resulted in a failure.
Next, let's proceed to perform our static analysis using IDA
.
It seems like a very small program. So let's begin reversing from the start.
When performing static analysis, it is best to put lots of comments for each block of code.
I will be showing both IDA
view and nasm markdown
view as it might be more clearer.
Let's look at the beginning of the main
function.
; int __cdecl main(int argc, const char **argv, const char **envp)
EXPORT main
main
var_124= -0x124
var_120= -0x120
var_11C= -0x11C
var_115= -0x115
var_15= -0x15
var_14= -0x14
var_10= -0x10
var_C= -0xC
PUSH {R4,R5,R11,LR}
ADD R11, SP, #8
SUB SP, SP, #0x120
MOV R2, #0
STR R2, [R11,#var_C]
STR R0, [R11,#var_10]
STR R1, [R11,#var_14]
LDR R0, [R11,#var_10]
CMP R0, #2
BGE loc_857C
We know that,
R0
- represents argc
R1
- represents argv
R2
- represents envp
Hence, lets rename our variables and add comments. The above block of code now looks like,
; Attributes: bp-based frame
; int __cdecl main(int argc, const char **argv, const char **envp)
EXPORT main
main
var_124= -0x124
var_120= -0x120
var_11C= -0x11C
var_115= -0x115
var_15= -0x15
argv= -0x14
argc= -0x10
envp= -0xC
PUSH {R4,R5,R11,LR}
ADD R11, SP, #8
SUB SP, SP, #0x120
MOV R2, #0
STR R2, [R11,#envp]
STR R0, [R11,#argc]
STR R1, [R11,#argv]
LDR R0, [R11,#argc]
CMP R0, #2 ; Checking if argc is >=2
BGE loc_857C
Since it branches based on whether we put in arguments, in our case, we are supplying 1
argument, which makes the argc
count 2
. Hence the program branches to the right.
Lets take a look at the block on the right.
loc_857C
LDR R0, =(byte_6325B - 0x8588)
ADD R0, PC, R0 ; byte_6325B
LDRB R0, [R0]
STRB R0, [R11,#var_15]
LDR R0, [R11,#argv]
LDR R0, [R0,#4] ; s
BL strlen
CMP R0, #0x100
BLS loc_85BC
Reversing it produces,
This code executes when there are atleast 1 cmd line argument
loc_857C
LDR R0, =(byte_6325B - 0x8588)
ADD R0, PC, R0 ; byte_6325B
LDRB R0, [R0]
STRB R0, [R11,#var_15]
LDR R0, [R11,#argv] ;
; The below line gets the first argument
LDR R0, [R0,#4] ; s
BL strlen ; gets the first argument and performs a strlen on it
CMP R0, #0x100 ; Length of the first argument is compared against 256
BLS loc_85BC ; Branch to the right if LOWER OR SAME than 256
Looking at the code on the left block, it says Length higher than 256 is not allowed
. What if the length is 256
? We need to test the edge cases.
Let's now take a look at the block on the right.
loc_85BC
ADD R0, SP, #0x128+var_115
LDR R1, [R11,#argv]
LDR R1, [R1,#4]
BL strcpy
LDRB R1, [R11,#var_15]
CMP R1, #0
BNE loc_8604
Reversing it gives,
Copies the first argument to the destination buffer
loc_85BC ; dest to strcpy
ADD R0, SP, #0x128+destBuffer
LDR R1, [R11,#argv]
LDR R1, [R1,#4] ; LOAD THE FIRST ARGUMENT as src to strcpy
BL strcpy
LDRB R1, [R11,#var_15]
CMP R1, #0
BNE loc_8604 ; if R1 is zero, we will get the flag!
We can see that if R1 is Not Equal
to 0
, we will fail to the right. Hence R1 has to be 0 in order for us to get the flag.
R1 gets its value from the stack variable var_15
.
var_15
gets its value from the previous block, from a readonly
memory location named =(byte_6325B - 0x8588)
.
Let's hover our mouse over it to see what the .rodata
contains.
It simply contains the number, 1
.
Hence, we know that var_15
will contains the number 1
but we somehow need to make it become 0
in order for us to get the flag.
Lets take a look at the stack location of the variable, right at the start of main.
The code we looked at allows a max of 256
bytes to be written and the 2 variables are exactly 256 bytes from each other. Hence we need at least 257 characters to override into var_15
. Moreover, the character that overrides var_15
has to be 0 in order for us to get the flag.
The flaw here relies on strcpy
. If we were to pass 256 characters, strcpy
copies all 256 characters into the buffer
and it also adds a NULL
, \0
, into the next position, the 257th position, to terminate the string. This 257th position happens to be our var_15
.
Hence from our host machine, we can use python to quickly generate a string of length 256
.
┌──(razali㉿razali)-[~/…/Ivy/AndroidVulnResearch/ctf/offByOne]
└─$ python -c 'print "a"*256'
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Then we can copy this string to our android.
root@hammerhead:/data/local/tmp # ./a.out aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You did it!
The flag is: "off_by_one"
And we got the flag.
Top comments (0)