X86C++反汇编11.作用域
### 全局变量可以通过寻址和存储地方可以分辨出来
全局变量有一个特点:他的地址肯定命中在 PE结构中 节表所以描述的区域中
未初始化数据地址有几种形态:
1. 在未初始化节,不同的编译器,对未初始化节的命名不一样
2. 在已初始化节,但是在文件对应的文件大小对应映射的内存大小以外的地方
3. 无文件映射节 (PE的节可以没有文件映射,节对应的文件映射地址和大小位0),未初始化节可以省空间
#### 非静态全局
```
int g_nTest = 0x87654091
int main(int argc, char* argv[])
{
return g_nTest;
}
反汇编代码:
DEBUG 版
0040D718 mov eax,
分析: 可以看到
通过对全局变量的寻址,我们可以看到, 他的地址是 [ 绝对地址 ] ,即[ 立即数 ],数组首地址也是立即数,或者有一个寄存器间接寻址,但是寄存器有保存的是一个全局的地址
```
release 版
mov eax, **dword_406030**
!(./notesimg/1658320660639-483151f0-c5c0-460c-bf10-d6a01821d11e.png)
```
int g_nTest;
int main(int argc, char* argv[])
{
return g_nTest;
}
反汇编代码:
mov eax, dword_4084D4
```
!(./notesimg/1658321360563-19c99e54-7811-47b6-9e9b-67abd66ba622.png)
!(./notesimg/1658321448160-d774799a-4497-4d9b-b59f-8c981afc8133.png)
可以看到内存大小远大于文件大小,多出来的部分就是未初始化数据
很多壳会用到这招,预留空间用的,用来保存解压数据,代码运行时把文件数据拷贝进对堆,,对在数据解压到内存中,一个壳的工作就完成了
数组的值是全局的信息排列,进函数的时候再把全局的数据复制到局部空间,这样可以省代码
#### 静态全局
如果是静态全局,他是编译器检查,看有没有跨文件访问,如果跨文件访问,编译时就直接报错,逆向时对于全局静态变量只能酌情处理,愿意给静态就给,不愿意给就不给
```
static int g_nTest = 0x87654091;
int main(int argc, char* argv[])
{
return g_nTest;
}
```
#### const 全局变量
如果是const就在 rdata 里面,但是有时候编辑器会把他放到text 节 里面,由链接器决定
```
const char g_szMsg[] = "Hello";
int main(int argc, char* argv[])
{
return strlen(g_szMsg);
}
反汇编代码
push edi
mov edi, offset aHello ; "Hello"
or ecx, 0FFFFFFFFh
xor eax, eax
repne scasb
not ecx
dec ecx
pop edi
mov eax, ecx
```
!(./notesimg/1658323775597-554ab29a-b2ce-460e-9e42-28b6379725ff.png)
#### 带初始化功能的全局
高低版本有区别
```
int GetInt()
{
return 0x87654091;
}
int g_nTest = GetInt();
int main(int argc, char* argv[])
{
return g_nTest;
}
反汇编
mov eax, dword_4084C0
```
此时可以看到 ,变量的值为 0
!(./notesimg/1658324028758-0bafef2c-e4d5-491e-9622-5f8f575c1bc2.png)
但是运行的时候又不是0值
!(./notesimg/1658324253418-eb0a349c-0dbd-42f4-82dc-a29251a3124b.png)
重新运行
!(./notesimg/1658324318843-71220687-454c-4069-a58f-4df80bd04629.png)
发现此时是0 ,在改地址下一个内存写入断点
!(./notesimg/1658324398348-606765ab-1530-4afd-96f2-8280e21e3b21.png)
可以看出, 该全局变量是函数 的 返回值,所以确定该全局变量的值是由函数初始化的
在非调试的环境下
通过栈窗口追踪 变量的初始化过程 可以发现
E2 调E1 函数 ,E1 再调我们的初始化函数,把返回值写到全局变量
他实际上有一层代理
!(./notesimg/1658325220105-7a751e28-2d34-4df6-a8b2-4b697efc7866.png)
为什么要E1 呢 ,因为函数指针得统一是无参无返回值的,但是我们写的函数不一定能做到无参无返回值,因此要处理这个问题,需要包一层,由无参无返回值的函数调用一下我们的函数就行了,这样可以做到统一接口
但是这样应该包一层就够了,为啥还要 E2 呢,因为每一层职责不同
E2其实是为 C++ 准备的 ,c++的全局对象需要激活构造函数,这时候 E1 负责传递this指针调构造,但是全局对象还需要析构,因此E2 还得 调 E3 ,E3负责调析构函数,在main函数执行完触发
!(./notesimg/1658325794256-583a78bc-ba14-40fb-a60d-7285e8f25675.png)
但是优化后,如果函数比较简单,可能被优化调,就只看到一层
通过IDA 怎么看呢
**正面看**
!(./notesimg/1658326085800-c2a836eb-0dcb-463f-a2b4-14402d10ec06.png)
我们知道运行时这个值会变,说明有其他地方改这个值,到main函数的调用方
找到cinit
!(./notesimg/1658326112227-e81cfd25-5ba6-45c4-8301-e853ee3a3296.png)
再进去找第二个
!(./notesimg/1658326229443-619d4fd6-063f-4ccd-b45c-59dc5bd293e6.png)
!(./notesimg/1658326437443-c498acd9-e36c-410c-a252-61275357c74d.png)
点击2个值中的一个
!(./notesimg/1658326534223-70719a47-9787-449d-a63c-30b739f2b3ac.png)
**简单方法**
!(./notesimg/1658326611086-e446d41b-87e7-48c9-8616-f3301e971346-170824819216720.png)
改变量被2处引用 w 代表写r 代表读,直接点击写的函数
!(./notesimg/1658326687947-35c9b06f-1701-44b0-8cfd-e82eb59b8572.png)
!(./notesimg/1658326769008-6845d8b4-8477-40f4-96e4-278a2b92454c.png)
!(./notesimg/1658326931892-bd2a743b-3dd3-4eb1-98a4-655a481d5883.png)
!(./notesimg/1658327907253-836178df-efb9-4630-9535-f8f6b2ddcdc7.png)
可以看函数的规模,即函数或变量的调用关系
!(./notesimg/1658327095449-cce47f09-81c5-44b0-a3ec-79c745f925e6.png)
粉红的表示导入表命中,青色表示被识别出来特征
最复杂的是操作系统的实现代码
!(./notesimg/1658327382414-dbc1893f-e23a-4487-82e6-f62c2f1f06ad.png)
```
int GetInt(int m,int n)
{
return 0x87654091+m*n;
}
int g_nTest = GetInt(5,6);
int main(int argc, char* argv[])
{
return g_nTest;
}
高版本:
mov eax, ds:?g_nTest@@3HA ; int g_nTest
```
如果函数比较简单,高版本会内联
可以看到值已经出来了
!(./notesimg/1658328453266-13bb1f9f-2aaa-49f7-a2b1-fe470c2af6fd.png)
vs 内联优化选项
!(./notesimg/1658328572791-f5931076-3755-4ce8-abde-76ed85688245.png)
全程优化就是跨函数优化,即常量传播,折叠等优化可以跨函数了 ,只能在函数特别简单时才能优化
!(./notesimg/1658328776262-e0ec6a87-3378-43d8-b7e0-5cb67e4da460.png)
0b0禁用内联,内联关键字会失效 debug版默认值
0b1只有写内联关键字的才会内联
0b2无视关键字,能内联的都内联 release版默认值
### 局部变量
#### 静态局部变量
静态变量如果初始为常量,他就是一个特殊的全局变量
```
int main(int argc, char* argv[])
{
static stcTset = argc;
return stcTset;
}
反汇编代码
debug版
9: static stcTset = argc;
0040D508 xor eax,eax
0040D50A mov al,[`main'::`2'::$S1 (00427f4c)] ;读一个标志
0040D50F and eax,1
0040D512 test eax,eax ;判断标志的最低位是否为0,,为0代表没有被初始化
0040D514 jne main+3Eh (0040d52e) ;代表已经初始化跳走
0040D516 mov cl,byte ptr [`main'::`2'::$S1 (00427f4c)] ;重新读标志位
0040D51C or cl,1 ;标志位最低位置1
0040D51F mov byte ptr [`main'::`2'::$S1 (00427f4c)],cl ;写回标志
0040D525 mov edx,dword ptr ;参数给 edx
0040D528 mov dword ptr ,edx ; edx给静态静态变量
10: return stcTset;
0040D52E mov eax,
relaese版
.text:00401000 mov cl, byte_4084C4
.text:00401006 mov al, 1
.text:00401008 test al, cl
.text:0040100A jnz short loc_40101E
.text:0040100C or cl, al
.text:0040100E mov eax,
.text:00401012 mov byte_4084C4, cl
.text:00401018 mov dword_4084C0, eax ;dword_4084C0就是静态局部变量
release版不像debug 版 那么啰嗦
对于静态局部变量都可以这个思路处理,但是标志的位置不一样,有的时候就在变量旁边,有的可能很远,在2019种考虑了多线程问题 ,如果是一个线程函数中 有一个 静态局部变量,在多线程的时候,有的线程读,有的线程写就会有问题,所以基于保护的情况,把标志 放到tls 里面去了,标志的值也不怎么重要
高版本
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 mov eax, large fs:2Ch
.text:00401009 mov ecx,
.text:0040100B mov eax, $TSS0
.text:00401010 cmp eax,
.text:00401016 jle short loc_401043
.text:00401018 push offset $TSS0 ; pOnce
.text:0040101D call __Init_thread_header
.text:00401022 add esp, 4
.text:00401025 cmp $TSS0, -1
.text:0040102C jnz short loc_401043
.text:0040102E mov eax,
.text:00401031 push offset $TSS0 ; pOnce
.text:00401036 mov stcTset, eax
.text:0040103B call __Init_thread_footer
; 2个call 中间的代码就是静态局部变量初始化的代码
.text:00401040 add esp, 4
.text:00401043
.text:00401043 loc_401043: ; CODE XREF: _main+16↑j
.text:00401043 ; _main+2C↑j
.text:00401043 mov eax, stcTset
```
### 堆作用域
堆作用域
1. 主要看符号,识别出这是一个堆申请,或者堆释放
2. 看这个变量是否在 栈里面 ,是否在PE 里面,都不在的话那就是堆
```
int main(int argc, char* argv[])
{
int* p = (int*)malloc(4);
*p = 999;
free(p);
p = new int;
*p = 666;
delete p;
return 0;
}
反汇编代码
高版本
.text:00401000 push 4 ; Size
.text:00401002 call ds:__imp__malloc
.text:00401008 push eax ; Block
.text:00401009 mov dword ptr , 3E7h
.text:0040100F call ds:__imp__free
.text:00401015 push 4 ; size
.text:00401017 call ??2@YAPAXI@Z ; operator new(uint)
.text:0040101C push 4 ; __formal
.text:0040101E push eax ; block
.text:0040101F mov dword ptr , 29Ah
.text:00401025 call ??3@YAXPAXI@Z ; operator delete(void *,uint)
.text:0040102A add esp, 14h
.text:0040102D xor eax, eax
高版本比较简单,就看是否调用了 malloc free new delete
低版本
低版本比较复杂一点,要人肉识别或者通过签名来看
.text:00401000 push 4 ; Size
.text:00401002 call _malloc
.text:00401007 push eax ; lpMem
.text:00401008 mov dword ptr , 3E7h
.text:0040100E call sub_401049
.text:00401013 push 4 ; Size
.text:00401015 call ??2@YAPAXI@Z ; operator new(uint)
.text:0040101A push eax ; lpMem
.text:0040101B mov dword ptr , 29Ah
.text:00401021 call sub_401030
.text:00401026 add esp, 10h
.text:00401029 xor eax, eax
```
可以直接看他调用了什么函数
!(./notesimg/1658331247993-dc98f24f-43a4-4aab-8d6a-c8a7873f09e0.png)
或
!(./notesimg/1658331160724-472fde64-9bc0-40a2-9ce7-cb6ae285ce42.png)
堆的创建
!(./notesimg/1658331418950-972374b3-9672-4955-ab96-5308a5bbdf61.png)
看变量地址
005A25F8明显不是栈空间,也不在PE里面,所以是堆
!(./notesimg/1658331936287-0ab69231-739f-4e13-9bb9-77170ed66f2d.png)
!(./notesimg/1658332010972-3342b13f-eeca-473a-8f4a-3d2a5c7e3e36.png)
到了 C++ 的时候delete[]和 delete 才有区别,有无 [ ] 区别是 需要析构,有 [] 表示需要调用析构
谢谢分享
页:
[1]