защита стековой памяти программы - PullRequest
1 голос
/ 03 мая 2020

Предположим, что я написал программу на. c и этот конечный пользователь запускает файл .exe. Во время выполнения программы существует переменная CHECK, которая динамически назначается в середине выполнения программы с использованием некоторых псевдослучайных алгоритмов. С одной стороны, если переменная соответствует некоторым критериям (скажем, CHECK == 1580 или определенное число с заранее заданным числом c), программа что-то делает на выходе. Мой вопрос заключается в том, может ли человек, который контролирует систему, выполняющую эту программу, изменить память таким образом, чтобы он изменил адресное пространство переменной CHECK и сопоставил его с числом «1580» перед установкой условия IF, и активировал функцию IF, даже если алгоритм не поставил «1580» на первое место?

1 Ответ

2 голосов
/ 03 мая 2020

Да, легко использовать отладчик, например, 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".

...