查看程序信息

  • 对于静态分析而言,拿到一个程序,并不要着急去使用IDA pro将这个可执行文件或者dll文件逆向出来。而是先要收集程序的一些相关的信息,一般而言要收集的程序信息有如下:
    • 查看程序的架构,这个程序是在什么架构下的,不同架构下的程序其二进制指令是不同的即对于的汇编指令是不同的。例如:x86架构、x86_64架构、mips架构、arm架构等
    • 查看程序的文件结构,对于Windows系统、Linux系统、MAC系统,其对应的可执行文件的格式都不一样的,如果是windows系统的可执行文件那就不能在其他系统上直接运行。如果在动态调试的时候就需要选择对应的系统了。
    • 查看程序的编译环境,对于相同的代码来说,不同编译器或者编译环境编译成的可执行文件,在main函数执行前以及在main函数结束后所执行的东西是不同的。(这个文章后面会详细说明)
    • 查看程序的壳,有些程序并不想那么轻易的被反汇编,反编译,这个时候程序就会被加一个壳,这样在对程序进行逆向分析的时候就只能逆向出来程序的壳。而不能逆向出程序的正确代码。而这个称呼其实就是有保护程序的意思和自然界一些壳的功能非常相似。
    • 查看程序的动态链接库,一般而言如果不是静态编译的程序都会有一个动态链接库,而在逆向的目标程序中会保存着动态链接库的信息,已经一些用到的函数接口。从而使得我们在逆向的时候能查找资料或者能很快的反应过来这个函数的作用和功能。
    • 查看程序编写的语言,在逆向的时候目标程序不都是C语言编写的,可能还有rustC++golang以及一些比较古老和小众的语言。获取程序是使用哪种语言编写的这有助于我们了解到该程序会使用哪些标准库,以及一些语言特性。
  • 上面简单解释了一下收集程序信息的一些作用,接下来介绍一下怎么收集、使用什么工具收集。这里选用的工具是diedie的全称为Detect It Easy,它是一个开源的可执行文件分析工具,用来检测上面所说的程序信息。它的下载方式在这里:horsicq/Detect-It-Easy:用于确定 Windows、Linux 和 MacOS 文件类型的程序。
  • 这里做一个示范,随便找一个.exe的可执行文件,在拿到一个.exe可执行文件时,不要先急着使用IDA反汇编它,而是先打开DIE

image-20250811134310354

  • 这样在没有开启高级选项其信息是这样的

image-20250811134427490

  • 在开启高级选项后会出现一大串东西,但是其实这一大串东西目前不知道有没有用。但是下图画红框的地方是需要注意的:它标识了程序基地址以及程序入口点,对于程序入口点(EnteryPoint)并不是main函数,这里放后面会详细说明,这里先知道有EnteryPoint这个东西
  • 在下图中其实我们就能收集到上面所说的层序相关信息,有助于我们逆向分析程序。

image-20250811134550977

程序入口点(EntryPoint)

  • 在学PE文件结构的时候在NT选项头的这个结构体即_IMAGE_OPTIONAL_HEADER这个结构体中,会发现有这么一个成员,这个成员其实就是EntryPoint
1
2
3
4
5
typedef struct _IMAGE_OPTIONAL_HEADER{
.....
DWORD AddressOfEntryPoint; // 程序入口点地址,简称EP(当前入口点)。OEP(原始入口点),在加壳的时候这么称呼
....
}
  • 如果没学过PE文件格式其实也不大,这里具体介绍一下EntryPoint这个概念:

EP(EntryPoint)入口点:

EP是Windows可执行文件的代码入口点,是执行应用程序时最先执行的代码的起始位置,它依赖于CPU。

注解:从EP的概念中其实就可以知道,我们点击一个exe文件,它就是从这个入口点保存的地址开始运行的,即EIP、RIP一开始的值为EP

  • 在学习C语言的时候,我们写的代码都是从main函数开始执行的,但是程序并不是一开始就执行main函数,而是从EP开始执行。

定位main函数

  • 在收集完信息后,如果有壳就脱壳,没壳就可以直接使用IDA进行逆向分析了。如果有做过几题逆向题就会发现,逆向题一开始都是先去寻找main函数,找到main函数后才开始对程序进行逆向分析。其实我们编写一个程序也是从main函数开始。
  • 但是在逆向分析中有的时候main函数可并不是那么好找的。接下来就使用一个例子来说明为什么有的时候main函数不好找

例子

  • 使用vs2022编写如下程序,编译后找到对应的可执行文件

image-20250811140349517

  • 下面是对应的可执行文件,在这里除了有Realme.exe文件外,其实还有Realme.pdb文件,这个.pdb文件是什么呢?pdb的全称其实是Program Database(程序数据库),它包含了调试信息,包括变量名、函数名、行号等信息。

image-20250811140606696

  • 这时我们使用IdaRealme.exe文件进行逆向,在逆向的时候我们会出现一个这样的提示,其实就是Realme.exe文件连接了一个调试信息和符号表在Realme.pdb中,我们是否要加载这个符号表,这里我们先选择Yes

image-20250811140915727

  • Ida反编译完后直接在Functions那一栏搜索main,操作下来会很快的确定到main函数,这相当的轻松。

image-20250811141219078

  • 此时我们关掉这个ida界面,对该程序重新逆向,遇到这个选项的时候选择No

image-20250811141317418

  • Ida反编译好该文件后继续搜索main函数,发现并没有搜索到main函数

image-20250811141415646

  • 这时因为程序没有加载.pdb文件,所以IDA在逆向的时候没有对应的符号表,而在真实的环境中,程序都是又多、又没有符号表。在这种情况下就需要先定位到main函数才能开始逆向分析。对于找到main函数,其实我们就需要从EP程序入口点入手。
  • 程序入口点在收集程序信息的时候就已经知道了,并且IDA中对程序入口点的函数都会使用start名称,搜索一下函数名称就可以看到(可以多试几个程序,看看这些程序的start函数)

image-20250811141909123

  • 接下来就以vs2022编译的x64 debug的这个realme.exe文件在有符号表的情况下进行分析,看看这个程序从startmain会执行哪些步骤。
  • 首先可以查壳看看该程序的的EP

image-20250811143755880

  • 接下来在IDA中找到EP也就是对应的start函数,此时发现这个start函数会jmpmainCRTStartup这个函数中去,这个函数的全称为main CRT StartupCRT C Runtime Library

image-20250811143706011

  • 这时跟进这个函数看看,发现这个函数中会call一个函数,一个名为__scrt_common_main这个函数,接下来继续跟进

image-20250811144020190

  • 发现__scrt_common_maincall俩个函数,这时会注意到第二个函数与main函数有关系,所以跟进到__scrt_common_main_seh这个函数中,进行分析。

image-20250811144204988

  • 进入到__scrt_common_main_seh会发现这是一个比较复杂的函数

image-20250811144558042

  • 这个时候定位到函数块结束前面一点的位置,会看到这个函数会调用invoke_main跟进到这个函数中去

image-20250811144632550

  • 进入到这个函数中去就会发现有一个j_main,跟进j_main

image-20250811144728913

  • 发现j_main这个函数会执行jmp main,而jmp mainjmp到的位置其实就是main函数的位置

image-20250811144748447

image-20250811144852445

  • 所以在startmain之间vs2022编译的x64程序执行的流程是这样的:
1
2
3
4
5
6
7
8
9
10
start
->mainCRTStartup
->_scrt_common_main
->__scrt_common_main_seh
.....
->invoke_main
.....
->j_main
->main
.....

定位main函数的方法

  • 通过上面的分享,这时如果遇到一个vs2022编译的x64版本的exe程序尽管它没有符号表,我们也能比较快速的找到main函数。但是其他开发环境编译的程序start->main是否也是这样的流程呢?答案是其他开发环境编译的程序可能并不是这样的流程,主要有以下几点:

    • 不同编译器,编译后的exe程序start->main函数之间执行的程序是不同的。
    • 相同编译器但是版本不同,编译后的exe程序start->main函数之间执行的程序也是不同的。
  • 因为编译器的原因start->main之间执行的程序逻辑并不相同,这样我们并不能学习一个vs2022 x64编译的程序start->main之间的过程就一劳永逸,还需要一些其他的方法来确定main函数的地址,这里介绍几个方法。

    • 方法一:收集到逆向的目标程序编译环境(包括开发环境、编译器版本等)信息后,在本地配置一个相同或者差不多的环境(比如都使用vs2022)使用该开发环境编写一个简单的程序并带有符号表编译,通过调试这个简单的程序熟悉start->main之间的程序流程,这样逆向目标程序就很容易找到main函数了。
    • 方法二:字符串搜索法
    • 方法三:动态调试,调试call到相应函数时程序会触发一些行为,或者在调试器中看到一些字符串即可确定函数。
    • 方法四:API检索法在调用代码中设置断点、在API代码中设置断点。
    • 方法五:寻找exit函数,寻找exit函数的参数从哪里来,一般来说exit函数的参数是main的返回值。
  • 对于方法一,一种就是现学,另一种就是平时的积累,没什么好说的,在文章后面会对各个编译器和开发环境下编译的exe文件从start->main的这一过程进行分析并积累起来。接下来重点介绍一下方法二、三、四

注意:对于方法二、三、四其实也适用于定位其他关键函数。

方法二

方法三

方法四

Visual_studio2022_start分析

x64

x32