I edited the whole example a little so that it would better match the question.
My SPARC assembly fu is weak, but what I did was write a little "Hello world" with a twist (or one could say with jumps/goto
s) in C and use gcc -S
to translate it to assembly. I have a SPARC on which I am running it, details:
$ isainfo -v
64-bit sparcv9 applications
vis2 vis
32-bit sparc applications
vis2 vis v8plus div32 mul32
NB: b
is the same as jmp
, it's just a different mnemonic for the same thing, really. One takes an immediate value (b
), the other a register (jmp
).
It turns out that what the link you gave is true for GCC:
Notice that the last instruction executes before the jump takes place,
not after the subroutine returns. This first instruction after a jump
is called a delay slot. It is common practice to fill the delay slot
with a special operation that performs no task, called a no-operation,
or nop
.
Real life test
I reckon we need to do this with and without debugger, because it's not clear whether it might behave differently under a debugger. So the code should output something readable so we can see what kind of effect our tinkering has ;)
C code
#include <stdio.h>
int foo(int argc)
{
switch(argc)
{
case 0:
case 1:
goto a1;
case 2:
return 3;
case 4:
goto a2;
case 5:
return -1;
default:
goto a4;
}
a1: return 1;
a2: return 2;
a4: return 4;
}
int main(int argc, char** argv)
{
printf("Hello world: %i\n", foo(argc));
return foo(argc);
}
This gives me plenty of branch instructions to play around with the idea raised in the question.
Assembly created by gcc -S
Here's the assembly before I tinkered with it:
.file "test.c"
.section ".text"
.align 4
.global foo
.type foo, #function
.proc 04
foo:
!#PROLOGUE# 0
save %sp, -120, %sp
!#PROLOGUE# 1
st %i0, [%fp+68]
ld [%fp+68], %g1
cmp %g1, 5
bgu .LL11
nop
ld [%fp+68], %g1
sll %g1, 2, %i5
sethi %hi(.LL12), %g1
or %g1, %lo(.LL12), %g1
ld [%i5+%g1], %g1
jmp %g1
nop
.LL6:
mov 3, %g1
st %g1, [%fp-20]
b .LL1
nop
.LL9:
mov -1, %g1
st %g1, [%fp-20]
b .LL1
nop
.LL5:
mov 1, %g1
st %g1, [%fp-20]
b .LL1
nop
.LL8:
mov 2, %g1
st %g1, [%fp-20]
b .LL1
nop
.LL11:
mov 4, %g1
st %g1, [%fp-20]
.LL1:
ld [%fp-20], %i0
ret
restore
.align 4
.align 4
.LL12:
.word .LL5
.word .LL5
.word .LL6
.word .LL11
.word .LL8
.word .LL9
.size foo, .-foo
.section ".rodata"
.align 8
.LLC0:
.asciz "Hello world: %i\n"
.section ".text"
.align 4
.global main
.type main, #function
.proc 04
main:
!#PROLOGUE# 0
save %sp, -112, %sp
!#PROLOGUE# 1
st %i0, [%fp+68]
st %i1, [%fp+72]
ld [%fp+68], %o0
call foo, 0
nop
mov %o0, %o5
sethi %hi(.LLC0), %g1
or %g1, %lo(.LLC0), %o0
mov %o5, %o1
call printf, 0
nop
ld [%fp+68], %o0
call foo, 0
nop
mov %o0, %g1
mov %g1, %i0
ret
restore
.size main, .-main
.ident "GCC: (GNU) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)"
I'll concentrate on modifying the result of foo()
, so I won't repeat all of the assembly code again but instead only bits and pieces.
btw: GCC created the extra indentation for the nop
instructions, but it makes it easy to spot them, of course.
Steps to get from C to executable with tinkering involved
Here are the steps to get to the modified program.
- use
gcc -S test.c
to get a test.s
file
- modify the
test.s
- Assemble it with
gas -o test.o test.s
- Link with GCC using
gcc -o test test.o
Modifications to the assembly code
First, I felt compelled to "optimize" the instructions in LL6
, LL9
, LL5
, LL8
, LL11
and LL1
like this:
.LL6:
mov 3, %i0
b .LL1
nop
.LL9:
mov -1, %i0
b .LL1
nop
.LL5:
mov 1, %i0
b .LL1
nop
.LL8:
mov 2, %i0
b .LL1
nop
.LL11:
mov 4, %i0
.LL1:
ret
restore
It should be clear that if your colleague is right, we should be able to substitute the nop
instructions for a mov ..., %i0
to see something other than the expected value.
I called my modified assembly file modified.s
so as to not confuse myself ;)
Verifying my "optimizations"
First test is with my "optimizations only". I wrote a little test script:
#!/usr/bin/env bash
for i in optimized test; do
echo -n "$i: "; ./$i
echo -n "$i: "; ./$i a1
echo -n "$i: "; ./$i a1 a2
echo -n "$i: "; ./$i a1 a2 a3
done
The binaries are called optimized
(my "optimizations" from above) and test
(plain assembly created by GCC from C code).
Results:
$ ./runtest
optimized: Hello world: 1
optimized: Hello world: 3
optimized: Hello world: 4
optimized: Hello world: 2
test: Hello world: 1
test: Hello world: 3
test: Hello world: 4
test: Hello world: 2
So my "optimizations" seem to be just fine. Now let's tinker a little.
Tinkering with the instructions which modify the program counter
The claim is that anything past a jmp
(i.e. b
) will get executed before the jump itself. We have several labels with jumps, so let's replace the nop
in each with something that changes the value inside %i0
and thus the return value of foo()
.
The changes:
.LL6:
mov 3, %i0
b .LL1
mov 30, %i0
.LL9:
mov -1, %i0
b .LL1
mov 42, %i0
.LL5:
mov 1, %i0
b .LL1
mov 10, %i0
.LL8:
mov 2, %i0
b .LL1
mov 20, %i0
.LL11:
mov 4, %i0
.LL1:
ret
restore
So except for return code -1
(which becomes 42
) and 4
(which stays the same) everything should now return the original value times ten.
Let's see the results (I added modified
to the list of items in my for
loop):
$ ./runtest
optimized: Hello world: 1
optimized: Hello world: 3
optimized: Hello world: 4
optimized: Hello world: 2
test: Hello world: 1
test: Hello world: 3
test: Hello world: 4
test: Hello world: 2
modified: Hello world: 10
modified: Hello world: 30
modified: Hello world: 4
modified: Hello world: 20
A change that is as close to your example as I can get it
mov 39, %i0
jmp %g1
b .LL11
b .LL1
.LL6:
mov 37, %i0
b .LL1
mov 30, %i0
[...]
.LL11:
mov 4, %i0
.LL1:
ret
restore
Amending the test script, here's the output:
$ ./runtest
optimized: Hello world: 1
optimized: Hello world: 3
optimized: Hello world: 4
optimized: Hello world: 2
test: Hello world: 1
test: Hello world: 3
test: Hello world: 4
test: Hello world: 2
modified: Hello world: 10
modified: Hello world: 30
modified: Hello world: 4
modified: Hello world: 20
question: Hello world: 4
question: Hello world: 4
question: Hello world: 4
question: Hello world: 4
Baffling!
Result
You can play tricks on the reverse engineer's mind with this - no doubt. I learned something new and that alone was worth it.
Here's the situation
jmp %g1
b .LL11 ; <-- this is the branch taken
b .LL1
mov 37, %i0 ; <-- but this gets executed first (at least in GDB)
Now I don't know whether this is true for all SPARC machines, but certainly for the one I was using for my tests (specs at the top)
Conclusion
Yes, this can certainly be used to trick the unwitting reverse engineer and perhaps the disassembler (static analysis tool). It's basically an opaque predicate. I.e. the outcome is clear at compile time, but it looks like it's dynamic.
It's difficult to see how good different disassemblers cope, given that I only have IDA Pro and objdump
available here. My educated guess would be that they cope the same as with other opaque predicates, i.e. sometimes they'll get fooled, sometimes they'll be surprisingly smart. So whether or not this is a suitable obfuscation method remains unsolved.
Bonus information
As opposed to prior to the edit, IDA seems to be mildly confused by the new code, watch this graph view:
click here for full size image (previous version)
Little GDB session
0x106CC
is the mov 39, %i0
instruction, found via IDA.
$ gdb -q ./question
(no debugging symbols found)
(gdb) b *0x106CC
Breakpoint 1 at 0x106cc
(gdb) run a1
Starting program: /export/home/builder/test/question a1
[New LWP 1]
[New LWP 2]
[LWP 2 exited]
[New LWP 2]
(no debugging symbols found)
(no debugging symbols found)
Breakpoint 1, 0x000106cc in foo ()
(gdb) disp/i $pc
1: x/i $pc
0x106cc <foo+44>: mov 0x27, %i0
(gdb) si
0x000106d0 in foo ()
1: x/i $pc
0x106d0 <foo+48>: jmp %g1
0x106d4 <foo+52>: b 0x1070c <foo+108>
0x106d8 <foo+56>: b 0x10710 <foo+112>
0x106dc <foo+60>: mov 0x25, %i0
(gdb)
0x000106d4 in foo ()
1: x/i $pc
0x106d4 <foo+52>: b 0x1070c <foo+108>
0x106d8 <foo+56>: b 0x10710 <foo+112>
0x106dc <foo+60>: mov 0x25, %i0
(gdb)
0x000106dc in foo ()
1: x/i $pc
0x106dc <foo+60>: mov 0x25, %i0
(gdb)
0x0001070c in foo ()
1: x/i $pc
0x1070c <foo+108>: mov 4, %i0
(gdb)
0x00010710 in foo ()
1: x/i $pc
0x10710 <foo+112>: ret
0x10714 <foo+116>: restore
(gdb)
So according to GDB we are executing the mov 37, %i0
before the branching. This seems to suggest to me that even when you chain multiple branch instructions, the first thing to be executed is whatever comes after the last one in the chain.