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?
-
1Why the `java-security` tag if the question is about C? – Erlkoenig May 03 '20 at 10:46
1 Answers
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.

- 2,664
- 1
- 9
- 18
-
Does this mean that web apps would be better approach to this if i wanted to actually 'hide' the attacker possibility of entering in this IF statement? Also, this doesnt make a sense to me actually. By your claims, if desktop app had login form, then any attacker would be able to bypass the if statement checking if password is correct and just go straight to user dashboard? I understand perfect security doesnt exist, but this thing with debugger seems very easy @Erlkoenig – TrueStar May 03 '20 at 11:02
-
Yes, a purely local login form can easily be bypassed. After all, that's what trainers or cracks for older games do; circumventing license key entry for copy-protection or required actions in the game to obtain points or items. A web-based approach is much more secure because the attacker can't control the server. If the server only presents the information if correct login data is entered, an attacker would have to find a security vulnerability to circumvent the login or e.g. use social engineering to obtain login data. – Erlkoenig May 03 '20 at 11:12
-
I added an example to show how you would use `gdb` to manipulate the program to do what you want. – Erlkoenig May 04 '20 at 07:14