C/C++逆向:寻找mian函数(其他编译配置特征)

2024-09-02 03:52

本文主要是介绍C/C++逆向:寻找mian函数(其他编译配置特征),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上篇文章中写了在逆向中定位main函数几种方法,其中有一种方法是通过编译器特征定位 main 函数(使用IDA分析简单demo程序获取特征,根据得到的特征可以定位相同编译器编译程序的main函数)。在上一篇文章中我们提取了VS环境(VS2017)中MSVC编译器编译配置与目标平台架构分别为Debugx86生成的程序特征,从而正确定位到了main函数所在位置。本篇文章主要写一下根据编译器特征定位main函数的方法,提取VS环境(VS2017)生成的编译配置与目标平台架构分别为Debug+x64Release+x86Release+x64以及GCC编译器编译生成的程序特征,从而定位到main函数。

1.Debug-x64程序特征提取

先准备一个Debug-x64的Hello World程序(以下称Demo程序);且准备程序的步骤都一样,后续就不再赘述:

接着右击项目生成程序即可;

打开程序所在文件夹,找到程序即可:

exe程序放入IDA(x64-bit)中进行加载解析;pdb文件也最好一并载入,即弹出以下窗口时选择yes:

.pdb文件包含了与可执行文件关联的调试符号和源代码信息。这些信息包括函数名、变量名、源代码行号等。在调试时,.pdb文件使得调试器能够将二进制代码映射回源代码,从而允许开发者查看变量的名称和类型、设置断点、查看调用堆栈以及源代码的确切行号等。没有.pdb文件,调试器只能展示汇编代码和原始的内存地址,极大地增加了调试的难度。

接着在函数窗口定位Demo程序main函数所在位置:

定位到main函数:

接着进行交叉引用(ctrl + x),提取特征(过程在上一篇文章中已经有详细说明,这篇文章就不再做过多赘述),这边补充一下小细节;若是在交叉引用的过程中出现了两个目标,且目标中包含Runtime_Function;此时一律不选Runtime_Function:

以下是笔者获取的程序(Debug-x64)特征:

第一个jmp
​
第一个call
​
第二个call
​
第一个jnz
sub     rsp, 68h
mov     ecx, 1
call    j___scrt_initialize_crt
movzx eax, al
test eax, eax
jnz short loc_140011FEF
​
​
第一个jmp
mov     [rsp+68h+var_48], 0
call    j___scrt_acquire_startup_lock
mov     [rsp+68h+var_47], al
cmp     cs:__scrt_current_native_startup_state, 1
jnz     short loc_140012012
mov     ecx, 7
call    j___scrt_fastfail
jmp     short loc_14001206A
​
​
第一个jz
movzx   ecx, [rsp+68h+var_47]
call    j___scrt_release_startup_lock
call    j___scrt_get_dyn_tls_init_callback
mov     [rsp+68h+var_38], rax
mov     rax, [rsp+68h+var_38]
cmp     qword ptr [rax], 0
jz      short loc_1400120C6
​
第一个jz
call    j___scrt_get_dyn_tls_dtor_callback
mov     [rsp+68h+var_30], rax
mov     rax, [rsp+68h+var_30]
cmp     qword ptr [rax], 0
jz      short loc_1400120F9
​
第一个call
​
第4个call
​
jmp main(跳转表)

接着根据特征进行动态调试定位main函数:

将程序拖如x64dbg中进行调试;按下F9来到自身程序模块中:

接着根据特征成功定位到main函数:

因为具体方法在上一篇文章中已经介绍过了,所以这篇文章主要的侧重点还是来比较一下生成的Debug-x86/x64程序的特征:

①Debug-x64特征
第一个jmp
​
第一个call
​
第二个call
​
第一个jnz
sub     rsp, 68h
mov     ecx, 1
call    j___scrt_initialize_crt
movzx eax, al
test eax, eax
jnz short loc_140011FEF
​
​
第一个jmp
mov     [rsp+68h+var_48], 0
call    j___scrt_acquire_startup_lock
mov     [rsp+68h+var_47], al
cmp     cs:__scrt_current_native_startup_state, 1
jnz     short loc_140012012
mov     ecx, 7
call    j___scrt_fastfail
jmp     short loc_14001206A
​
​
第一个jz
movzx   ecx, [rsp+68h+var_47]
call    j___scrt_release_startup_lock
call    j___scrt_get_dyn_tls_init_callback
mov     [rsp+68h+var_38], rax
mov     rax, [rsp+68h+var_38]
cmp     qword ptr [rax], 0
jz      short loc_1400120C6
​
第一个jz
call    j___scrt_get_dyn_tls_dtor_callback
mov     [rsp+68h+var_30], rax
mov     rax, [rsp+68h+var_30]
cmp     qword ptr [rax], 0
jz      short loc_1400120F9
​
第一个call
​
第4个call
​
jmp main(跳转表)
②Debug-x86特征:
第一个jmp
​
第一个call
push
mov 
call
​
第二个call
​
第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B
​
第一个jmp
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
push    7
call    j____scrt_fastfail
jmp     short loc_411E01
​
第一个jz
movzx   ecx, [ebp+var_1A]
push    ecx
call    j____scrt_release_startup_lock
add     esp, 4
call    sub_411195
mov     [ebp+var_20], eax
mov     edx, [ebp+var_20]
cmp     dword ptr [edx], 0
jz      short loc_411E51
​
第一个jz
call    sub_41123A
mov     [ebp+var_24], eax
mov     edx, [ebp+var_24]
cmp     dword ptr [edx], 0
jz      short loc_411E82
​
第一个call
call    ?invoke_main@@YAHXZ ; invoke_main(void)   
mov     [ebp+Code], eax
call    j____scrt_is_managed_app
movzx   ecx, al
test    ecx, ecx
jnz     short loc_411E9F
mov     edx, [ebp+Code]
​
第4个call
第一个jmp

前三个特征:Debug-x64与Debug-x86特征一致;

①Debug-x64特征
第一个jmp
​
第一个call
​
第二个call
②Debug-x86特征:
第一个jmp
​
第一个call
push
mov 
call
​
第二个call

第四个特征:主要的区别在于MSVC运行时初始化函数(j___scrt_initialize_crt)调用时所作的处理方式不同,以下是两者的具体区别分析:

①Debug-x64特征
第一个jnz
sub     rsp, 68h
mov     ecx, 1
call    j___scrt_initialize_crt
---------------------------------------
movzx eax, al
test eax, eax
jnz short loc_140011FEF
  1. 针对 x64架构(64位),使用了 rsp 这个x64 特有的寄存器。

  2. 使用寄存器传递函数参数(mov ecx, 1),不涉及栈操作来传递参数。该片段在调用函数前并没有通过 push 将参数压入栈,而是直接将参数放入寄存器 ecx,这是典型的 x64使用的fastcall调用约定。

在 x64 架构(64位)中,Windows 平台的调用约定通常使用 RCX, RDX, R8, R9 寄存器来传递前四个整数或指针类型的参数,返回值使用 RAX 寄存器。
②Debug-x86特征:
第一个jnz
mov     large fs:0, eax
mov     [ebp+ms_exc.old_esp], esp
push    1
call    j____scrt_initialize_crt
add     esp, 4
---------------------------------------
movzx   eax, al
test    eax, eax
jnz     short loc_411D7B

1.针对 x86架构(32位),使用了 espebpfs 等 x86 特有的寄存器和调用约定。

2.通过 push 指令将参数压入栈(push 1);参数通过 push 指令压栈,这是 x86 调用约定(如 stdcall、cdecl 等)的常见特征。

3.该片段代码 使用了add esp, 4代码进行栈清理操作,这说明该x86程序使用的是 cdecl 调用约定。

除了以上区别,其余的特征一致。接着来对比一下第五个特征:

①Debug-x64特征
mov     [rsp+68h+var_48], 0
call    j___scrt_acquire_startup_lock
-------------对比下面特征--------------------------------------
mov     [rsp+68h+var_47], al
cmp     cs:__scrt_current_native_startup_state, 1
jnz     short loc_140012012
-----------------------------------------
mov     ecx, 7
call    j___scrt_fastfail
jmp     short loc_14001206A
②Debug-x86特征:
mov     [ebp+var_1A], al
cmp     dword_41A158, 1
jnz     short loc_411DA0
-----------------------------------------
push    7
call    j____scrt_fastfail
jmp     short loc_411E01

通关观察可以看出,第五个特征中也是由于x86与x64程序所用的调用约定不同从而使得两个特征又些许差异,除了在使用call调用函数部分会存在区别以外,其他的特征基本是一致的。其余特征的区别也是一洋,这边就不做过多赘述了。

2.Release-x86程序特征提取

准备一个Release-x86程序:

将程序拖入IDA(32bit)进行解析;

再解析完成后,一样在函数窗口中搜索main函数:

在这里我们可以发现 IDA函数窗口中Release 模式下的程序显示的函数数量明显少于 Debug 模式下的程序;这种现象的主要原因在于编译器在 Release模式下会进行各种优化,导致一些函数被内联、删除或合并。以下是部分导致这种现象的具体原因:

①编译器优化
1>函数内联
编译器会将一些小的、频繁调用的函数直接替换为它们的代码实现,避免了函数调用的开销。这种优化会导致这些小函数在最终的可执行文件中不再作为单独的函数存在,而是被“内联”到调用它们的地方,结果是 IDA 在分析 Release 版本时,无法识别出这些函数,导致函数窗口中显示的函数数量减少。
2>死代码消除
编译器会删除在程序执行过程中永远不会被调用的代码和函数,减少可执行文件的大小,在 Debug 模式下,编译器不会执行这种优化,因此即使是未使用的函数也会出现在最终的可执行文件中。
3>代码折叠和合并:
对功能相似或相同的代码段进行折叠或合并,以减少代码的冗余度。这样会导致 IDA 将多个函数识别为一个函数或者直接将多个函数的代码合并成一个函数。
②去除调试信息
Release 模式下通常会去除调试信息,如符号信息(函数名、变量名等)和调试符号(如 PDB 文件),以保护程序的源代码结构和内部逻辑,并且减少可执行文件的大小。

接着回到正题,在定位到main函数后,接着就可以对Release-x86程序进行特征提取,这边步骤就省略了,最后得到的特征为:

第一个call
​
第二个call
​
第一个jmp
mov     [ebp+ms_exc.registration.TryLevel], 0
call    ___scrt_acquire_startup_lock
mov     [ebp+is_nested], al
cmp     ___scrt_current_native_startup_state, 1
jnz     short loc_4011D0
push    7               ; code
call    ___scrt_fastfail
jmp     short loc_401231
​
第一个jz
movzx   ecx, [ebp+is_nested]
push    ecx             ; is_nested
call    ___scrt_release_startup_lock
add     esp, 4
call    ___scrt_get_dyn_tls_init_callback
mov     [ebp+tls_init_callback], eax
mov     edx, [ebp+tls_init_callback]
cmp     dword ptr [edx], 0
jz      short loc_401281
​
第一个jz
call    ___scrt_get_dyn_tls_dtor_callback
mov     [ebp+tls_dtor_callback], eax
mov     edx, [ebp+tls_dtor_callback]
cmp     dword ptr [edx], 0
jz      short loc_4012B2
​
第一个call
​
第4个call

在这边可以看到Release程序的特征层数比较Debug程序的特征层数要来的少;最后根据特征定位到main函数:

3.Release-x64程序特征提取

生成Release-x64程序;

打开所在文件夹,并将其载入IDA(64-bit)中进行解析:

在IDA中一样先在函数窗口中定位main函数:

接着进行交叉引用,提取特征,步骤省略,最后得到的特征为:

第一个call
​
第二个call
​
第一个jmp
call    __scrt_acquire_startup_lock
mov     [rsp+68h+var_47], al
cmp     cs:__scrt_current_native_startup_state, 1
jnz     short loc_140001222
mov     ecx, 7          ; code
call    __scrt_fastfail
jmp     short loc_14000127A
​
第一个jz
movzx   ecx, [rsp+68h+var_47]
call    __scrt_release_startup_lock
call    __scrt_get_dyn_tls_init_callback
mov     [rsp+68h+target], rax
mov     rax, [rsp+68h+target]
cmp     qword ptr [rax], 0
jz      short loc_1400012D6
​
第一个jz
call    __scrt_get_dyn_tls_dtor_callback
mov     [rsp+68h+var_30], rax
mov     rax, [rsp+68h+var_30]
cmp     qword ptr [rax], 0
jz      short loc_140001309
​
第一个call
​
第四个call

接着根据特征成功定位main函数:

4.gcc程序特征定位main函数

gcc编译的Demo程序准备,在此之前系统中需要存在gcc环境;若没有的gcc环境的兄弟文章末尾领取。我这边也只记录64位的gcc特征定位main函数的过程。

1.准备一个C文件

接着在命令行中运行以下命令,生成exe程序:

gcc Hello.c -o Hello.exe

此时就可以开始进行特征提取了,将程序拖入IDA(64-bit)进行加载解析;

特征如下:

第二个call
​
第一个jnz
push    r13
push    r12
push    rbp
push    rdi
push    rsi
push    rbx
sub     rsp, 98h
xor     eax, eax
mov     ecx, 0Dh
lea     rdx, [rsp+0C8h+StartupInfo]
mov     rdi, rdx
rep stosq
mov     rdi, cs:_refptr_mingw_app_type
mov     r9d, [rdi]
test    r9d, r9d
jnz     loc_401470
​
​
第二个jmp
xor     eax, eax
xchg    rax, [rbx]
jmp     loc_40122C
mov     rcx, rdx        ; lpStartupInfo
call    cs:__imp_GetStartupInfoA
jmp     loc_4011B4
​
第一个jmp
mov     rax, gs:30h
mov     rbx, cs:_refptr___native_startup_lock
xor     ebp, ebp
mov     rsi, [rax+8]
mov     r12, cs:__imp_Sleep
jmp     short loc_4011E4
​
第一个jnz
mov     rax, rbp
lock cmpxchg [rbx], rsi
test    rax, rax
jnz     short loc_4011D3
​
第一个jz
xor     ebp, ebp
mov     rsi, [rax+8]
mov     r12, cs:__imp_Sleep
jmp     short loc_4011E4
cmp     rsi, rax
jz      loc_401410
​
第一个jnz
mov     rsi, cs:_refptr___native_startup_state
mov     ebp, 1
mov     eax, [rsi]
cmp     eax, 1
jnz     loc_401205
​
第2个jz
xor     ebp, ebp
mov     eax, [rsi]
cmp     eax, 1
jz      loc_401427
mov     eax, [rsi]
test    eax, eax
jz      loc_401480
​
第一个jmp
mov     rdx, cs:_refptr___xi_z
mov     dword ptr [rsi], 1
mov     rcx, cs:_refptr___xi_a
call    _initterm
jmp     loc_401219
​
第一个jz
mov     eax, [rsi]
test    eax, eax
jz      loc_401480
mov     cs:has_cctor, 
mov     eax, [rsi]
cmp     eax, 1
jz      loc_40143C
​
第一个jnz
mov     rdx, cs:_refptr___xc_z
mov     rcx, cs:_refptr___xc_a
call    _initterm
test    ebp, ebp
mov     dword ptr [rsi], 2
jnz     loc_40122C
​
第一个jz
mov     rax, cs:_refptr___dyn_tls_init_callback
mov     rax, [rax]
test    rax, rax
jz      short loc_401247
xor     r8d, r8d
mov     edx, 2
​
第一个jmp
call    __p__acmdln
xor     ecx, ecx
mov     rax, [rax]
test    rax, rax
jnz     short loc_4012B2
jmp     short loc_4012F7
​
第一个jz
mov     r8d, [rdi]
test    r8d, r8d
jz      short loc_401315
test    byte ptr [rsp+0C8h+StartupInfo.dwFlags], 1
mov     eax, 0Ah
jnz     loc_401400
​
​
第一个jle
mov     rcx, r12        ; Size
call    malloc
test    ebx, ebx
mov     rdi, cs:argv
mov     rbp, rax
jle     short loc_401387
​
第二个call:
mov     qword ptr [rax], 0
mov     cs:argv, rbp
call    __main
mov     rax, cs:_refptr___imp___initenv
mov     rdx, cs:envp
mov     ecx, cs:argc    ; argc
mov     rax, [rax]
mov     [rax], rdx
mov     r8, cs:envp     ; envp
mov     rdx, cs:argv    ; argv
call    main
mov     ecx, cs:managedapp
mov     cs:mainret, eax
test    ecx, ecx
jz      loc_40149E

gcc程序的特征定位比较诡异,所以这边简单做一个记录:

①首先按F9进入自身程序模块中

进入以后查看反汇编代码后发现并未存在特征:

此时先按F8进行程序运行,但是F8按着按着发现又回到了ntdll.dll模块当中,所以还需要按下F9;

回到自己领空后接着按F8运行代码,结果运行没一会儿又回到了ntdll.dll模块,继续按F9。

回到自己程序模块中后接着F8运行,若是又到ntdll.dll模块就再按F9以此类推,直到出现以下特征:

两个call,依照上述IDA中提取的特征,我们需要进入第二个call;接着就正常根据特征进行main函数的定位即可。

注:由于gcc编译程序最后一个特征比较明显,所以可以选择一直点击F8,直到最后一个特征出现后,即可根据最后一个特征成功定位到main函数。

第二个call:
mov     qword ptr [rax], 0
mov     cs:argv, rbp
call    __main
mov     rax, cs:_refptr___imp___initenv
mov     rdx, cs:envp
mov     ecx, cs:argc    ; argc
mov     rax, [rax]
mov     [rax], rdx
mov     r8, cs:envp     ; envp
mov     rdx, cs:argv    ; argv
call    main
mov     ecx, cs:managedapp
mov     cs:mainret, eax
test    ecx, ecx
jz      loc_40149E
GCC环境:关注VX_GZ号[风铃Sec]后台回复[mingw64]获取MinGW,后添加环境变量即可使用。如若不会,GZH后台回复。

这篇关于C/C++逆向:寻找mian函数(其他编译配置特征)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128912

相关文章

Redis MCP 安装与配置指南

《RedisMCP安装与配置指南》本文将详细介绍如何安装和配置RedisMCP,包括快速启动、源码安装、Docker安装、以及相关的配置参数和环境变量设置,感兴趣的朋友一起看看吧... 目录一、Redis MCP 简介二、安www.chinasem.cn装 Redis MCP 服务2.1 快速启动(推荐)2.

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

Spring Boot配置和使用两个数据源的实现步骤

《SpringBoot配置和使用两个数据源的实现步骤》本文详解SpringBoot配置双数据源方法,包含配置文件设置、Bean创建、事务管理器配置及@Qualifier注解使用,强调主数据源标记、代... 目录Spring Boot配置和使用两个数据源技术背景实现步骤1. 配置数据源信息2. 创建数据源Be

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

Spring Boot Maven 插件如何构建可执行 JAR 的核心配置

《SpringBootMaven插件如何构建可执行JAR的核心配置》SpringBoot核心Maven插件,用于生成可执行JAR/WAR,内置服务器简化部署,支持热部署、多环境配置及依赖管理... 目录前言一、插件的核心功能与目标1.1 插件的定位1.2 插件的 Goals(目标)1.3 插件定位1.4 核

RabbitMQ消息总线方式刷新配置服务全过程

《RabbitMQ消息总线方式刷新配置服务全过程》SpringCloudBus通过消息总线与MQ实现微服务配置统一刷新,结合GitWebhooks自动触发更新,避免手动重启,提升效率与可靠性,适用于配... 目录前言介绍环境准备代码示例测试验证总结前言介绍在微服务架构中,为了更方便的向微服务实例广播消息,

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)