Да, легко использовать отладчик, например, gdb. Установите точку останова прямо перед if
, запустите программу, пока точка останова не сработает, установите для переменной любое желаемое значение, удалите точку останова и продолжайте. Вы можете даже заставить отладчик вообще пропустить проверку состояния, прыгнув прямо в блок if. Вы также можете заменить проверку в двоичном коде на nop
. Это в основном то, что делают «крэки» для пиратского программного обеспечения.
Без исходного кода и символов отладки это становится несколько сложнее, так как вы должны выяснить адреса, но это просто задерживает неизбежное. Имея полный доступ к компьютеру, вы можете манипулировать любой программой любым удобным для вас способом. Существуют различные схемы защиты (главным образом, запутывание), но они только усложняют, а не делают невозможным.
Чтобы еще раз доказать мою точку зрения, приведу очень быстрый пример: Учитывая следующий код C:
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
int main () {
srand (time (NULL));
while (1) {
if (rand () == 1580) {
puts ("You got me!");
break;
}
}
}
Скомпилируйте его с оптимизацией и без символов, чтобы сделать его немного сложнее, если предположить, что система x86_64 linux:
gcc -O3 -flto -ffunction-sections -fdata-sections -Wl,--gc-sections -s test.c -o test
Обычно эта программа запускается за несколько секунд до завершения. Мы хотим сделать это немедленно. Запустите его через отладчик gdb
:
$ gdb ./test
(gdb) starti
Starting program: /tmp/test
Program stopped.
0x00007ffff7dd6090 in _start () from /lib64/ld-linux-x86-64.so.2
Получите информацию о диапазонах памяти. Нас интересует начальный адрес секции .text
:
(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
...
Таким образом, фактический код начинается с 0x0000555555554610
в памяти. Давайте разберем некоторые из них:
(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
...
Вот и вся программа. Инструкция cmp
является интересной частью; установите точку останова и позвольте программе работать:
(gdb) break *(0x0000555555554635)
Breakpoint 1 at 0x555555554635
(gdb) c
Continuing.
Breakpoint 1, 0x0000555555554635 in ?? ()
Из вышеприведенного вывода сборки вы можете видеть, что 0x62c
(то есть 1580) - это число волхвов c. Запишите его в регистр, переписав возвращаемое значение rand()
, и продолжите программу:
(gdb) set $eax = 1580
(gdb) c
Continuing.
You got me!
[Inferior 1 (process 12745) exited normally]
(gdb)
Программа немедленно распечатает сообщение и выйдет. Если бы мы использовали какую-то функцию ввода пароля вместо rand()
, мы могли бы сделать то же самое, чтобы обойти проверку пароля. Вместо установки значения в регистр, мы могли бы также набрать jump *0x000055555555463c
, чтобы просто перейти в блок if; Таким образом, нам даже не нужно находить число "волхвов c".