Using ARM Inline Assembly and Naked Functions to Fool Disassemblers


On this post I want to share a simple trick I learned a while ago, it’s nothing special but if you think about it, it’s quite nice :)

Sometimes we want to obfuscate/hide strings in our program to make reversing more difficult and the more common approach is to encrypt them somehow and put them inside binary buffers instead of plain ASCII strings.
One downside of this naive approach is of course, once decompiled, the access to these binary buffers will easily be noticed by a seasoned reverser, he would assume some sort of obfuscation/encryption/whatever and start reversing the algorithm to unobfuscate the strings in a matter of minutes.

One thing you can do to make his/her life harder ( but not impossible ) is embedding your encrypted data as code … how?

UPDATE/NOTE (10 May 2015)

After a few critics I feel the need to do some clarifications.

  • This is not intended to be the definitive obfuscation method, it’s just a simple and small example of out-of-the-box thinking while coding in C.
    Indeed it’s a simple and naive trick that wouldn’t create much problems to good reversers and a properly implemented recursive disassembler.

  • Please don’t comment with things such as “The hello world string would be visible anyway” … of course it will be! That’s just an example, ideally the data stored in the function would be encrypted, thus binary.

  • It is possible to fool even a recursive disasm, just make a call to the fake function, catch the signal and go on … the disasm will see the call and consider it as a legit function.

  • Nice things happen when you start declaring odd sized constants on ARM, since the opcode align is of 4 bytes, that could screw in some cases the analysis of the next code portions.

Naked Functions

Let’s start with the definition of what a naked function is. Basically whenever you declare a C/C++ function inside your code, the compiler will add some extra assembly code to the beginning and to the end of it, this code is responsible for cleaning the stack, popping arguments of the function and so forth.
If you try to compile the following function:

1
2
3
int sum( int a, int b ) {
return a + b;
}

The resulting ( ARM ) assembly will be:

1
2
3
4
5
6
7
8
9
10
11
12
push	{fp}		; (str fp, [sp, #-4]!)
add fp, sp, #0
sub sp, sp, #12
str r0, [fp, #-8]
str r1, [fp, #-12]
ldr r2, [fp, #-8]
ldr r3, [fp, #-12]
add r3, r2, r3
mov r0, r3
sub sp, fp, #0
pop {fp} ; (ldr fp, [sp], #4)
bx lr

As you can see, there’s a lot of extra stuff rather than just the expected add operator.
The first instructions basically save the current frame pointer and load the function arguments into the appropriate registers, then the add r3, r2, r3 itself is performed ( this is the original purpose of our sum function ), the result is moved inside R0 ( which by convention is the register that stores the return value of the function ), the original stack is restored and eventually there’s a branch to LR, namely a jump back to the previous address ( the one that called sum ).

In order to “avoid” those extra operators ( and generally speaking it’s not a good idea to avoid them unless you really know what you’re doing ), we can use the naked GCC attribute, its definition on the docs is:

Use this attribute on the ARM, AVR, IP2K, RX and SPU ports to indicate that the specified
function does not need prologue/epilogue sequences generated by the compiler.
It is up to the programmer to provide these sequences. The only statements that can be safely
included in naked functions are asm statements that do not have operands. All other statements,
including declarations of local variables, if statements, and so forth, should be avoided.
Naked functions should be used to implement the body of an assembly function, while allowing
the compiler to construct the requisite function declaration for the assembler.

Now, let’s try to use this attribute with the same sum function:

1
2
3
__attribute__ ((naked)) int sum( int a, int b ) {
return a + b;
}

The resulting assembly code will be just:

1
2
3
4
mov	r2, r0
mov r3, r1
add r3, r2, r3
mov r0, r3

As you can see, only the add and return operations have been compiled this time, with no prologue and epilogue whatsoever.
Obviously if you try to call the naked sum function, the program will crash with a Segmentation Fault message, due to the stack not being properly prepared and cleaned as we saw in the previous example.

So how can we use the naked attribute?

Inline ASM

If you’re a C/C++ developer you probably already know ( or at least you should! ) that you can use raw assembly inside your C/C++ code using the asm gcc directive, if you don’t you definitely need to read the GCC documentation about that ( and shame on you! :D ).
An interesting ASM directive we can exploit for our purpose is .long ( you can find it here inside the “Constannt Definition Directives” section ), which basically allows you to directly define a constant value inside your assembly code.
You would normally use that to define a numeric constant to use in your code, but what happens if we embed it in a naked function?

Let’s take the following example:

1
2
3
4
5
__attribute__ ((naked)) void my_mum_said_im_special(){
asm ( ".long 0x6C6C6548" );
asm ( ".long 0x6f57206f" );
asm ( ".long 0x00646c72" );
}

What you see there is the definition of four constants of four bytes each, 16 bytes in total, which are the string

"\0dlroW olleH"

Seems familiar? No? Well … try to reverse it and you’ll get the famous “Hello World” constant string with his null byte, since it’s represented as numbers the ARM endianess requires them to be in reverse order.

Now some magic with pointers, let’s try to take the address of this function, cast it to a *const char ** and printf it.

1
2
const char *s = (const char *)&my_mum_said_im_special;
printf( "%s\n", s );

The output will be, as expected, an “Hello World” :D

But how does the decompiler sees this function? Is it able to determine it’s actually just data and not code? The answer of course it’s NO:

1
2
3
4
00000338 <my_mum_said_im_special>:
cfstr64vs mvdx6, [ip], #-288 ; 0xfffffee0
svcvs 0x0057206f
rsbeq r6, r4, r2, ror ip

The objdump utility simply took those bytes and tried to interpret them as ARM opcodes, miserably failing to detect them as data instead of code.
What about more sophisticated disassemblers?

The following is a screenshot of the Hopper disassembler:

Hopper

In this case we even get a totally different code … what about IDA, the state of the art disassembler?

ida

Again different code :D

Now try to imagine, instead of a simple “Hello World”, to put some strongly encrypted data or even strongly encrypted code that you can decrypt and execute at runtime.
In my experiments, this trick not only is able to fool every decompiler I tried, but in some cases it’s even able to screw all the code that follows the naked function and/or make the decompiler crash!

Enjoy :)

Become a Patron!