Search code examples
securityreverse-engineeringcode-security

protecting stack memory of program


Lets assume that i wrote program in .c and that end user is starting .exe file. During program's execution there is variable called CHECK that gets dynamically assigned in middle of program's execution using some pseudo random algorithms. In one point, if the variable matches some criteria (lets say CHECK == 1580 or some static predifined number) the program does something on output. My question is, can a person that has controll over the system running this program modify memory in such way that he modifies address space of variable CHECK and match it to number '1580' before IF condition is set and trigger IF function even if the algorithm didnt set '1580' in the first place?


Solution

  • Yes, it's easy using a debugger, e.g. gdb. Set a breakpoint right before the if, run the program until the breakpoint triggers, set the variable to any desired value, remove the breakpoint, and continue. You could even have the debugger skip the condition check altogether, jumping directly into the if-block. You could also replace the check in the binary code by a nop. This is basically what "cracks" for pirating software do.

    Without the source code and debugging symbols this becomes somewhat harder as you have to figure out the addresses, but it just delays the inevitable. With complete access to the computer, you can manipulate any program any way you want. Various protection schemes exist (mainly obfuscation), but they just make it harder, not impossible.

    To further prove my point, here is a very quick example: Given the following C code:

    #include <stdlib.h>
    #include <time.h>
    #include <stdio.h>
    
    int main () {
        srand (time (NULL));
    
        while (1) {
            if (rand () == 1580) {
                puts ("You got me!");
                break;
            }
        }
    }
    

    Compile it with optimizations and without symbols to make it a little harder, assuming an x86_64 linux system:

    gcc -O3 -flto -ffunction-sections -fdata-sections -Wl,--gc-sections -s test.c -o test
    

    Ordinarily, this program would run for a few seconds before it quits. We want to make it quit immediately. Start it via the gdb debugger:

    $ gdb ./test
    (gdb) starti
    Starting program: /tmp/test 
    
    Program stopped.
    0x00007ffff7dd6090 in _start () from /lib64/ld-linux-x86-64.so.2
    

    Obtain information about memory ranges. We are interested in the start address of the .text section:

    (gdb) info files
    Symbols from "/tmp/test".
    Native process:
        Using the running image of child process 12745.
        While running this, GDB does not access memory from...
    Local exec file:
        `/tmp/test', file type elf64-x86-64.
        Entry point: 0x555555554650
        ...
        0x0000555555554610 - 0x00005555555547b2 is .text
        ...
    

    So the actual code starts at 0x0000555555554610 in memory. Let's disassemble some of it:

    (gdb) disas 0x0000555555554610,0x0000555555554700
    Dump of assembler code from 0x555555554610 to 0x555555554700:
       0x0000555555554610:  xor    %edi,%edi
       0x0000555555554612:  sub    $0x8,%rsp
       0x0000555555554616:  callq  0x5555555545e0 <time@plt>
       0x000055555555461b:  mov    %eax,%edi
       0x000055555555461d:  callq  0x5555555545d0 <srand@plt>
       0x0000555555554622:  nopl   0x0(%rax)
       0x0000555555554626:  nopw   %cs:0x0(%rax,%rax,1)
       0x0000555555554630:  callq  0x5555555545f0 <rand@plt>
       0x0000555555554635:  cmp    $0x62c,%eax
       0x000055555555463a:  jne    0x555555554630
       0x000055555555463c:  lea    0x17a(%rip),%rdi        # 0x5555555547bd
       0x0000555555554643:  callq  0x5555555545c0 <puts@plt>
       0x0000555555554648:  xor    %eax,%eax
       0x000055555555464a:  add    $0x8,%rsp
       0x000055555555464e:  retq   
       ...
    

    That's the whole program. The cmp instruction is the interesting part; set a breakpoint there and let the program run:

    (gdb) break *(0x0000555555554635)
    Breakpoint 1 at 0x555555554635
    (gdb) c
    Continuing.
    
    Breakpoint 1, 0x0000555555554635 in ?? ()
    

    From the above assembly output you can see that 0x62c (i.e. 1580) is the magic number. Write it into the register, overwriting rand()s return value, and continue the program:

    (gdb) set $eax = 1580
    (gdb) c
    Continuing.
    You got me!
    [Inferior 1 (process 12745) exited normally]
    (gdb)
    

    The program will immediately print the message and quit. Had we used some kind of password-entry function instead of just rand(), we could have done exactly the same thing to circumvent the password check. Instead of setting the value in the register, we could also have typed jump *0x000055555555463c to just jump into the if-block; that way, we don't even have to find the "magic" number.