avatar

4.27日笔记 (week 2)

4.27日更新

😭我搞了一天博客,于是乎上周日那天鸽了,今天刚搞好,一看已经13.24了,不多说了学习!

今日任务:小黄书 + 对抗反汇编 x86

启动函数

mainCRTStartup函数代码片段分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//预编译宏
#else /* _WINMAIN_ */
#ifdef WPRFLAG
//宽字符版控制台启动函数
void wmainCTRStartup(
#else /* WPRFLAG* /
//多字节版控制台启动函数
void mainCRTStartup(
#endif /* WPRFLAG */
#endif /* _WINMAIN_ */
void
)
{
//获取版本信息
_osver = GetVersion();
_winminor = (_osver >> 8) & 0x00FF;
_winmajor = _osver & 0x00FF;
_winver = (_winmajor << 8) + _winminor;
_osver = (_osver >>16) & 0x00FFFF;

//堆空间初始化过程,在这个函数中,制定了程序中堆空间的起始位置
//_MT是多线程标记
#ifdef _MT
if(!_heap_init(1))
#else /* _MT */
if(!_heap_init(0))
#endif /*_MT */
fast_error_exit(_RT_HEAPINIT);
//初始化多线程环境
#ifdef _MT
if(!_mtinit())
fast_error_exit(_RT_THREAD);
#endif /* _MT* /
_try{
//宽字节处理代码略

//多字节版获取命令行
_acmdln = (char*)GetCommandLineA();
//多字节版获取环境变量信息
_aenvptr = (char*)_crtGetEnvironmentStringsA();
//多字节版获取命令行信息
_setargv();
//多字节版获取环境变量信息
_setenvp();
#endif /* WPRFLAG */
//初始化全局数据和浮点寄存器
_cinit();
//窗口程序处理代码略

//宽字符处理代码略

//获取环境变量信息
_initenv = _environ;
//调用main函数,传递命令行参数信息
mainret = main(_argc,_argv,_environ);
#endif /* WPRFLAG */

#endif /* _WINMAIN_ */
//检查main函数返回值执行析构函数或atexit注册的函数指针,并结束程序
exit(mainret);
}
//退出结束代码略
}

这里面👴通读了一下,有很多的函数不知道干什么的,总结一下:

  • GetVersion函数:获取当前运行平台的版本号,控制台程序运行在Windows模拟的DOS下,因此这里获取的版本号为MS-DOS的版本信息
  • _heap_init函数:用于初始化堆空间,在函数实现中使用HeapCreate申请堆空间,申请空间的大小由_heap_init传递的参数决定,_sbh_heap_init函数用于初始化堆结构信息
  • GetCommandLineA函数:获取命令行参数信息的首地址
  • _crtGetEnvironmentStringsA函数:获取环境变量信息的首地址
  • _setargv函数:根据GetCommandLineA获取命令行参数信息的首地址并进行参数分析,将分离出的参数的个数保存在全局变量_argc中,将分析出的每个命令行参数的首地址存放在数组中,并将这个字符指针数组的首地址保存在全局变量_argv中,从而得到命令行参数的个数以及命令行参数的信息
  • _setenvp函数:根据_crtGetEnvironmentStringA函数获取环境变量信息的首地址进行分析,将得到的每条环境变量字符串的首地址存放在字符指针数组内,并将这个数组的首地址存放在全局变量env

所以当我们的main函数得到这三个参数,_argc,_argv,env三个全局变量,他们作为参数以栈传递的方式传到main函数中

  • _cinit函数:用于全局数据和浮点寄存器的初始化,全局对象和IO流的初始化都是这个函数实现的,利用函数_initterm进行数据链初始化,这个函数由两个参数组成,类型为_PVFV*,这是一个函数指针数组,保留了每个初始化函数的地址,初始化函数的类型为_PVFV

    1
    typedef void(_cdecl *_PVFV)(void);

所以,这个初始化函数是无参数也是无返回值的,c++规定全局对象和静态对象必须在main函数前构造,在main函数返回后析构,所以这里是用来代理构造析构函数的

_cinit函数代码

1
2
3
4
5
6
7
//用于初始化寄存器
if(_FPinit != NULL)
(*_FPinit)(); //初始化浮点寄存器
//用于初始化C语法中的数据
_initterm( _xi_a, _xi_z);
//用于初始化C++语法中的数据
_initterm( _xc_a , _xc_z);

这里_Fpinit是一个全局函数指针,类型是_PVFV,如果编译器扫描代码的时候,发现有浮点计算,则这个指针保存了初始化浮点寄存器的代码地址,否则就是0,如果浮点寄存器没有被出刷,会有异常错误👴在上周的时候说过了,参数xi_a为函数指针数组的其实地址,_xi_z为结束地址,第一个_initterm初始化的都是C支持库中所需要的数据

_initterm函数代码

1
2
3
4
5
6
7
8
9
10
11
static void _cdecl _initterm (
_PVFV * pfbegin, _PVFV * pfend )
{
//遍历数组的各元素
while(pfbegin < pfend){
//若函数指针不为空,则执行该函数
if(*pfbegin!=NULL)
(**pfbegin)();
++pfbegin;
}
}

C++初始化操作会在第二次的_initterm进行调用,一般是全局变量或者静态对象的初始化函数(这里😥不咋懂,书上说第10章会说)

正常的mainCRTStartup函数会根据编译器版本的不同所有不同!👴早就猜到了,如vs2005,其中的mainCRTStartup变成_tmainCRTStartup,在我们的默认情况下,入口函数是main,这时候会从mainCRTStartup启动,再传入main所需要的三个参数argcargvenv,最后再调用main

如果我们重新指定入口函数

image-20200427145345539

image-20200427145438829

可以看到没有调用mainCRTStartup函数,我们可以再测试一下

image-20200427145535373

我再写一个新的函数调用并不是main来看一下

image-20200427145843968

如果我没有调用mainCRTStartup函数,我们的堆空间是没有被初始化的,所以用到堆就会报错

image-20200427151105530

我们看一下,正常的情况

image-20200427151458866

张长是可以分配的,所以说如果书用了malloc函数,没有初始化堆空间会出错的

main函数识别

VC++6.0main函数特征,三个参数:命令行参数个数,命令行参数信息,环境变量信息,main是启动函数中唯一一个具有3个参数的函数,WinMain也是启动函数中为一具有4个参数的函数

VC++6.0,main函数被调用前,要先调用的函数:

GetVersion() _heap_int() GetCommandLineA() _crtGetEnvironmentStringsA() _setargv() _setenvp()

_cinit()这些函数调用结束后就会调用main函数,根据main函数调用的特征,将argc,argv,env压入栈作为参数

用ida可以看到_mainCRTStartup的大概流程

image-20200427160426209

我们用od去看看做了什么,od会直接暂停在程序的入口处,并不是定位在main函数的位置,我们试着通过main函数的特性查找一下他在哪个位置

image-20200427160802795

image-20200427161042351

自己根据分析,我在断点的位置就是main的函数入口

我用书上的代码做一个例子,便于分析!🏃‍♂️奥里给冲!👍

OD反汇编信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//省略部分代码
//OD识别函数名称为GetCommandLineA
00401210 |. FF15 38514200 call dword ptr ds:[<&KERNEL32.GetCommand>]
//得到命令行参数
00401216 |. A3 444F4200 mov dword ptr ds :[424F44],eax
//根据main函数的特性,此处为_crtGetEnvirinmentStringsA()调用
0040121B |. E8 E0240000 call ProgramE.00403700
00401220 |. A3 BC354200 mov dword ptr ds:[4235BC],eax
//根据main函数特性,此处为函数_setargv()调用
00401225 |. E8 C61F0000 call ProgramE.004031F0
//根据main函数特性,此处为函数_setenvp()调用
0040122A |. E8 711E0000 call ProgramE.004030A0
//根据main函数特性,此处为函数_cinit()调用
004012FF |. E8 8C1A0000 call PromgramE.00402CC0
00401234 |. 8B0D 00364200 mov ecx,dword ptr ds:[423600]
0040123A |. 890D 04364200 mov dword ptr ds:[423604],ecx
00401240 |. 8B15 00364200 mov edx,dword ptr ds:[423600]
//压栈传参,环境变量信息
00401246 |. 52 push edx
00401247 |. A1 F8354200 mov eax,dword ptr ds:[4235F8]
//压栈传参,命令行参数信息
0040124C |. 50 push eax
0040124D |. 8B0D F4354200 mov ecx,dword ptr ds:[4235F4]
//压栈传参,命令行参数个数
00401253 |. 51 push ecx
//此处为卖弄函数的调用出,跟进到函数中便是main函数的实现代码流程
00401254 |. E8 ACFDFFFF call ProgramE.00401005

大概👴总结一下,一般我们用od的时候,来到的应该是mainCRTStartup的位置,那么我们寻找main函数的时候,我们需要去找到相应的调用前的函数的流程结束,从GetVersion()获取平台版本号 _heap_init初始化堆空间 GetCommandLineA获取命令行参数信息的首地址,_crtGetEnvironmentStringA获取环境变量信息的首地址,_setargv得到命令行参数个数,以及命令行参数信息,_setenvp获取环境变量字符串的首地址 _cinit全局数据和浮点寄存器初始化,之后main函数所需要的参数压入堆栈,调用main

👍小黄书的第三章也算完结了,明天开始搞第四章,😡家里实在是太冷了,我倒在床上就睡着了,呜呜呜,准备开始深入搞一下对抗反汇编x86的问题,在52破解上也找到了相关的文章,准备学习一下!

对抗反汇编x86

52破解原帖子:https://bbs.pediy.com/thread-259064.htm

Gstalker师傅给了一个Opcode在线转化网站:https://onlinedisassembler.com/odaweb/

相同的跳转指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
void 相同的跳转指令()
{
printf("定位");
_asm
{
jz _P2
jnz _P2
_P1 :
__emit 0xE8
}
_P2:
return;
}
int main(int argc, char* argv[]) {
相同的跳转指令();
return 0;
}

image-20200427215715803

在ida中会骗到ida,实际上动态分析调试的时候,会转到相应的pop的功能,但是静态分析会欺骗到

image-20200427215836987

我跳转过去看一下

image-20200427215857904

直接退出堆栈,就不会像ida中那样说的是对应的CALL

固定条件的跳转指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
void 固定条件的跳转指令()
{
printf("定位");
_asm
{
xor eax, eax
jz _P2
__emit 0xE9
}
_P2:
return;
}
int main(int argc, char* argv[]) {
固定条件的跳转指令();
return 0;
}

可以看到我们直接是跳转到jmp这里

image-20200427220338465

但是我们正常进行动态分析的时候不会这样,还是进行一个pop出原来现场的问题

image-20200427220549550

无效的反汇编码:内部跳转的jmp指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
void 无效的反汇编码1()
{
//内部跳转的jmp指令
printf("定位");
_asm
{
__emit 0xEB
__emit 0xFF
__emit 0xC0
__emit 0x48
}
return;
}
int main(int argc , char* argv[]) {
无效的反汇编码1();
return 0;
}

image-20200427224442652

动态调试直接过来就可以了

image-20200427224801827

无效的反汇编码:多层内部调转序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
void 无效的反汇编码2()
{
//多层内部调转序列
printf("定位");
_asm
{
__emit 0x66
__emit 0xB8
jmp _P1
__emit 0x31
__emit 0xC0
__emit 0x74
__emit 0xFA
__emit 0xE8
}
_P1:
printf("真实代码!");
return;
}
int main(int argc , char* argv[]) {
无效的反汇编码2();
return 0;
}

第一次的跳转:

image-20200427225442861

第二次的跳转:

image-20200427225504730

第三次的跳转:

image-20200427225514408

去ida看一下:

image-20200427225618251

滥用返回指针:

在反汇编部分我们看不出函数有调用_P2就retn了,成功欺骗了F5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
void 滥用返回指针()
{
printf("定位");
_asm
{
call _P1
_P1 :
add[esp], 5
retn
}
_P2:
printf("模糊函数边界!"); //这里会被执行
return;
}
int main(int argc , char* argv[]) {
滥用返回指针();
return 0;
}

image-20200427225817551

感觉正常的程序调试没什么毛病,我们去ida看一下

image-20200427225959658

直接就是栈指针不平衡的问题了,不能f5需要手动去修复

滥用结构化异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void 滥用结构化异常处理()
{
printf("定位");
_asm
{
mov eax, _PE1
push eax
push fs : [0]
mov fs : [0], esp
xor ecx, ecx
div ecx
retn
_PE1:
mov esp,[esp+8]
mov eax, fs : [0]
mov eax, [eax]
mov eax, [eax]
mov fs : [0], eax
add esp, 8
}
printf("结构化异常");
return;
}

挫败的栈帧分析:

F5后发现有很多参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
void 挫败的栈帧分析()
{
printf("定位");
_asm
{
xor eax, eax
add eax, 2
ret 0xff
}
printf("挫败的栈帧分析");
return;
}
int main(int argc , char* argv[]) {
挫败的栈帧分析();
return 0;
}

image-20200427230644253

image-20200427230846072

Author: L0x1c
Link: https://l0x1c.github.io/2020/04/27/2020-4-27/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶