保护程序栈内存

问题描述 投票:0回答:1

假设我用.c语言写了一个程序,终端用户启动的是.exe文件。在程序的执行过程中,有一个叫做CHECK的变量,它在程序执行的中间使用一些伪随机算法被动态分配。在某一点上,如果这个变量符合一些标准(比如说CHECK == 1580或者一些静态的预定义数字),程序就会在输出上做一些事情。我的问题是,一个对运行这个程序的系统有控制权的人,能不能在IF条件被设置之前,以这样的方式修改变量CHECK的地址空间,并将其匹配到数字'1580',并触发IF函数,即使该算法一开始就没有设置'1580'?

security reverse-engineering content-security-policy java-security code-security
1个回答
3
投票

是的,用调试器很简单,比如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)是神奇的数字。将其写入寄存器中,覆盖 rand()的返回值,然后继续程序。

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

程序会立即打印信息并退出。如果我们使用某种密码输入函数,而不是仅仅使用 rand()我们可以做同样的事情来规避密码检查。我们也可以不在寄存器中设置值,而是键入 jump *0x000055555555463c 来跳入if块;这样一来,我们甚至不需要找到 "神奇 "的数字。

© www.soinside.com 2019 - 2024. All rights reserved.