Hello World 的漏洞

任何程序都有漏洞,包括简单的 Hello World程序,这是一个客观存在的事实。也许你会质疑,一个用C/C++语言编写的输出一句 Hello World 的程序漏洞在哪里?
实际上,程序漏洞不一定全部都来自代码逻辑,漏洞还发生在程序运行时期。通过技术手段干扰程序执行,使程序不能得到预期的执行结果,那么这个程序就出现了漏洞。

分析者在没有程序源代码的情况下,只能通过逆分析程序的漏洞,常规操作通常是找到程序的入口点,
进一步跟踪程序的调用逻辑和数据,通过修改其中的调用或者数据,使程序出现漏洞。

要做的事:

  1. 用C/C++编写一个输出”Hello,World!”的可执行的exe程序。
  2. 在程序运行期间修改内存中的数据,使程序输出 “Hi,lanshiqin”

使用的工具:

  1. VisualStudio
  2. Ollydbg

为了减小篇幅,不再啰嗦怎么创建项目,直接上代码
经典的 Hello,World!源代码如下:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char* argv)
{
printf("Hello,World!\n");
system("pause");
return 0;
}

将程序编译成可执行程序 HelloWorld.exe 并运行

可以看到程序按源代码所编写的语句执行,得到了预期的输出结果。

也许有人会问,我直接修改程序的源代码,然后再编译成可执行程序运行,就可以得到想要的输出结果了,
为什么要用反汇编工具调试这么麻烦?

因为我们日常使用的绝大多数软件都是软件公司或者其他个人开发者的产品,我们不是这些软件的开发者,所以我们没有这些软件的源代码,所以只能通过逆向来对程序进行分析。在互联网上我们可以看到有很多正版的收费软件有对应的破解版,这些破解版软件就是有人通过逆向找到程序入口并修改其中的数据,通过程序逻辑漏洞或者运行时期的漏洞对软件进行破解。

一个程序要被CPU执行,首先需要先被载入内存中,我们想要达到修改程序的预期输出结果,就需要找到这块内存并修改其中的数据,以达到我们的目的。

逆向工具

在Windows平台的逆向领域中,有两个很好用的工具,一个是Ollydbg(简称OD),经常用来做动态调试,另一个是IDA 经常被用来静态分析。
OD用来调试运行时期的程序,可以将CPU执行的机器指令反成汇编指令,可以实时断点查看当前执行的指令,这对分析一些病毒的行为很有用,可以细微到每一条指令的调用,分析出程序具体做了什么。
IDA 主要用来分析复杂的代码,通过可执行程序可以得到伪代码,对于一些高级语言编写的程序甚至可以得到近似于程序的源代码,这样就很方便去查找程序里的逻辑漏洞。
OD和IDA这两个工具是逆向领域的倚天剑和屠龙刀,具体介绍和功能使用可以查阅相关文档。

操作步骤

1.使用OD来打开HelloWorld.exe

此时程序还没执行到输出“Hello,World!”的语句,此时是程序刚刚被载入内存,OD界面上可以看到程序当前执行的指令

2.组合键 Alt+M,打开 Memory map 窗口,这是程序运行时的内存数据

3.组合键 Ctrl+B, 搜索内存中的数据

ASCII码这里输入要需要替换的“Hello,World!”,底下HEX+00这里是对应ASCII码字符的十六进制数据。

4.点击搜索,可以看到如下图所示,已经搜索到了数据。

可以看到对应的内存地址,HEX dump 和ASCII

5.组合键Ctrl+E, 或者点击Edit->Binary edit 对内存数据进行编辑

将ASCII码的“Hello,World!”修改成“Hi,lanshiqin”,点击OK

6.继续运行程序,可以看到控制台输出了 “Hi,lanshiqin”。

通过修改程序运行时的内存数据,我们已经成功的改变了程序的预期结果。

通过运行时调试只能影响程序当前运行的结果,如果要影响每次运行的结果,我们可以将此次修改的指令和数据保存成新的可执行程序,以补丁包的形式附加成一个目标程序。(在没有程序源代码的情况下,这种做法相当于达到软件破解的目的,这个补丁相当于破解补丁,功能是使程序输出Hi,lanshiqin)

现在已经证实了,所有程序都有漏洞,包括仅仅是输出一句 “Hello,World!”的程序。

所以,很多程序为了防止被破解,通常都使用了代码混淆,程序加壳等保护措施。这些措施可以加大程序被破解的难度,但是仍然存在漏洞。代码混淆后编译使分析者很难读懂代码,程序加壳主要是为了程序的保护,防止分析者找到程序的入口点。

对于代码混淆,可以在一定程度上防止自己的程序被人还原出源代码,目前的APP应用程序,通常是为了防止应用被抄袭,这点混淆就很有用,因为分析者花费大量精力来分析还原出一个程序的工作量,都可以够他重新写出好几个新的程序了。但是混淆后程序行为依然可以被轻易的分析,所有的程序运行时都需要先被载入内存,然后被CPU执行,分析者可以通过CPU的执行指令进行调试分析,可以对运行时的内存进行分析,进一步修改内存数据,达到破解目的,比如这是个木马APP,安全人员可以跟踪这个程序,分析出这个程序究竟做了什么。

对于程序加壳,通常可以去壳,然后再找程序的入口点。
程序的启动过程和入口点是一个比较大的话题,通过C/C++编写的 Hello World 程序逆向分析发现,

程序的入口点并不是main()函数。
教科书为了易于理解,通常都告诉我们程序的入口点是main()函数,这是在代码逻辑的角度看。
如果从程序执行的角度来看,程序的入口点并不是main()函数。入口点其实是一个地址。
入口点地址不是main()函数的开端,而是另一段来自运行时的代码。

通过前面的OD分析HelloWorld.exe可执行程序,我们发现程序并不是从Main函数开始执行的,
教科书上所谓的“入口地址Main函数“其实也是一个参数,需要被别人调用。

对于逆向寻找程序入口点 不再记录笔记,因为已经有人写了很好的文章:
https://bbs.pediy.com/thread-216976.htm

使用VisualStudio断点调试HelloWorld的源代码


在调用堆栈窗口可以看到函数调用关系,打开mainCRTStartup,可以看到如下图所示:

可以发现有这么一句赋值语句,mainret = main(argc, argv, envp);
main函数作为参数赋值给了mainret变量。

在微软的Windows操作系统上,可执行程序c中提供了mainCRTStartup()运行配置函数,
里面包含了程序运行需要的库以及相关的存储资源,
在配置完成后,调用 mainret = main(argc, argv, envp); 这条语句
所以main并不是程序的入口,main只是作为普通的函数。

计算机程序最终都需要在计算机上运行,程序本身可能存在逻辑漏洞,执行程序的计算机硬件也存在被干扰的可能。我们对程序的定义是:程序是可以被计算机执行的指令集合
从虚拟的数学逻辑到现实的物理化学,我们可以通过一切已知的工具和未知的技术手段去寻找程序的漏洞,
所以受外界条件的影响,任何程序都有漏洞,程序漏洞永远存在

0%