X86C++反汇编14.结构体、返回值
## 结构体首先要考虑对齐值问题,但是在逆向角度是不关心的,因为结构体大小在代码中会体现
ida 如果读不到pbd,可以自动从微软服务器下载pbd (系统api的pbd)
### 结构体成员2个整形
看不出是结构体,很像定义2个 变量,没啥区别
```
struct Point {
int x;
int y;
};
int main()
{
Point pt1 = { 10,20 };
printf("x:%d y:%d", pt1.x, pt1.y);
return 0;
}
```
debug版反汇编代码
```
.text:00411865 mov dword ptr , 0Ah ;第一个成员
.text:0041186C mov dword ptr , 14h ;第二个成员
.text:00411873 mov eax,
.text:00411876 push eax
.text:00411877 mov ecx,
.text:0041187A push ecx ; char
.text:0041187B push offset aXDYD ; "x:%d y:%d"
.text:00411880 call sub_4110CD
.text:00411885 add esp, 0Ch
```
release版
```
struct Point {
int x;
int y;
};
int main()
{
Point pt1 = { 10,20 };
scanf_s("%d%d", &pt1.x, &pt1.y);
printf("x:%d y:%d", pt1.x, pt1.y);
return 0;
}
反汇编代码:
.text:00401080 push ebp
.text:00401081 mov ebp, esp
.text:00401083 sub esp, 0Ch ;抬了12字节,其中四字节是cookie,所以剩下8字节是变量
.text:00401086 mov eax, ___security_cookie
.text:0040108B xor eax, ebp
.text:0040108D mov , eax
.text:00401090 lea eax,
.text:00401093 mov dword ptr , 0Ah
.text:0040109A push eax
.text:0040109B lea eax,
.text:0040109E mov dword ptr , 14h
.text:004010A5 push eax ; Arglist
.text:004010A6 push offset aDD ; "%d%d"
.text:004010AB call sub_401110
.text:004010B0 push dword ptr
.text:004010B3 push dword ptr ; ArgList
.text:004010B6 push offset Format ; "x:%d y:%d"
.text:004010BB call sub_4010E0
.text:004010C0 mov ecx,
.text:004010C3 add esp, 18h
一样看不出来是结构体
```
### 结构体成员数多个整形
结构体和一堆变量最大的区别是 内存空间连续,多个变量内存不一定是连续的
```
struct Point {
int x;
int y;
int z;
};
void showPoint(Point pt1) {
printf("x=%d y:%d z:%d\n", pt1.x, pt1.y, pt1.z);
}
int main()
{
Point pt1 = { 10,20,30 };
scanf_s("%d%d%d", &pt1.x, &pt1.y, &pt1.z);
showPoint(pt1);
return 0;
}
```
debug版
```
mov dword ptr , 0Ah
mov dword ptr , 14h
mov dword ptr , 1Eh
lea eax,
push eax
lea ecx,
push ecx
lea edx,
push edx ; pt1
push offset aDDD ; "%d%d%d"
call j__scanf_s
add esp, 10h
sub esp, 0Ch
mov eax, esp
mov ecx,
mov , ecx
mov edx,
mov , edx
mov ecx,
mov , ecx
call j_?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
add esp, 0Ch
xor eax, eax
```
release版
```
lea eax,
mov dword ptr , 0Ah
push eax
lea eax,
mov dword ptr , 14h
push eax
lea eax,
mov dword ptr , 1Eh
push eax ; pt1
push offset aDDD ; "%d%d%d"
call _scanf_s
movq xmm0, qword ptr
add esp, 4 ; scanf 的栈需要平16字节,平了4字节还剩12字节,刚好结构体大小
mov eax,
mov ecx, esp
movq qword ptr , xmm0 ;8字节大小
mov , eax ;4字节大小的
call ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
函数:
push ebp
mov ebp, esp
push dword ptr ;结构体首地址
push dword ptr ;本来应该是首地址 +4 即 ebp+10h - 4被优了
push dword ptr
push offset _Format; "x=%d y:%d z:%d\n"
call _printf
add esp, 10h
pop ebp
retn
所以上面等于
sub esp,sizeof(Point)
mencpy (esp,pt1);
c语言语法规定当函数参数是结构体时,整个结构体拷贝到栈顶当参数
寻址方式:
当寄存器不够时就会出现 首地址 +偏移 寻址
```
### 当结构体成员比较多
```
struct Point {
int x;
int y;
int z;
};
void showPoint(Point pt1) {
printf("x=%d y:%d z:%d\n", pt1.x, pt1.y, pt1.z);
}
int main()
{
Point pt1 = { 10,20,30 };
scanf_s("%d%d%d", &pt1.x, &pt1.y, &pt1.z);
showPoint(pt1);
return 0;
}
```
debug版
```
mov dword ptr , 0Ah
mov dword ptr , 14h
mov dword ptr , 1Eh
push 18Ch ; Size
push 0 ; Val
lea eax,
push eax ; void *
call j__memset
add esp, 0Ch
lea eax,
push eax
lea ecx,
push ecx
lea edx,
push edx
push offset aDDD ; "%d%d%d"
call j__scanf_s
add esp, 10h
sub esp, 198h ;抬栈抬了 408字节
mov ecx, 66h ; 'f'
lea esi, ;源首地址(结构体首地址)
mov edi, esp ;目的地址
rep movsd ;内存拷贝大小是ecx * 4= 408
call j_?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
add esp, 198h
xor eax, eax
```
release版
```
push edi
push 18Ch ; Size
lea eax,
mov dword ptr , 0Ah
push 0 ; Val
push eax ; void *
mov dword ptr , 14h
mov dword ptr , 1Eh
call _memset ;396+ 12 (3个mov)= 408
lea eax,
push eax
lea eax,
push eax
lea eax,
push eax
push offset aDDD ; "%d%d%d"
call _scanf_s
sub esp, 17Ch ;抬栈
lea esi,
mov ecx, 66h ; 'f'
mov edi, esp
rep movsd
call ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
```
### 结构体指针
```
struct Point {
int x;
int y;
int z;
};
void showPoint(Point* pt1) {
printf("x=%d y:%d z:%d\n", pt1->x, pt1->y, pt1->z);
}
int main()
{
Point pt1 = { 10,20,30 };
scanf_s("%d%d%d", &pt1.x, &pt1.y, &pt1.z);
showPoint(&pt1);
return 0;
}
```
```
push 18Ch ; Size
lea eax,
mov , 0Ah
push 0 ; Val
push eax ; void *
mov , 14h
mov , 1Eh
call _memset
lea eax,
push eax
lea eax,
push eax
lea eax,
push eax
push offset aDDD ; "%d%d%d"
call _scanf_s
lea eax,
push eax ; pt1
call ?showPoint@@YAXPAUPoint@@@Z ; showPoint(Point *)
函数
push ebp
mov ebp, esp
mov ecx, ;获取指针(首地址)
lea eax, ;首地址 + 偏移寻址
push eax
push dword ptr ;首地址 + 偏移寻址
push dword ptr ;首地址 + 偏移寻址
push offset _Format; "x=%d y:%d z:%d\n"
call _printf
add esp, 10h
pop ebp
retn
```
### 总结
因此判断是否结构体的方法
1. 访问方式 [首地址 +偏移]
2. 传参在拷贝
如果上面2种都不存在,那么可能是局部变量
结构体成员如果没有被使用,可以当做不存在,还原结构体大小可以按照访问的最大偏移的来计算
而且一般结构体会初始化,可以从初始化获取结构体大小
### 数组和结构体的区别
- **(1)数组**
- - 还原为数组:(1)类型一致,业务一致。(2)地址首地址参与比例因子寻址或有循环。
- 数组传参:数组传参必然是指针。
- **(2)结构体**
- - 还原为结构体:(1)类型一致,业务不一致。(2)类型不一致,业务不一致。(3)!(地址首地址参与比例因子寻址或有循环)。
- 结构体传参:结构体传参是浅拷贝。如果是引用参数的话,以栈顶为目标,执行memcpy,在高版本下会使用两个多媒体指令相当于传入了一个参数,即,栈顶为参数地址。
### 结构体和类的区别
- 类:有成员函数的调用。
- 结构体:没有成员函数的调用
### iad自动识别指定偏移位置的结构体成员
SDK定义的结构体会自动导入结构体类型
#### 第一种方法:添加结构体类型
当结构体成员很多时,我们使用ida 可能没法记住所有成员的偏移,因此ida 提供了该功能
!(./notesimg/1658763359571-6ba8ba17-9c6b-4097-8bc5-604bdd090cf9.png)
右键创建结构体 快捷键 **ins**
!(./notesimg/1658763468008-062a41e1-d3c7-4cbf-8d30-70962ae956aa.png)
!(./notesimg/1658763504586-52cd87bf-1e36-4c55-b8d5-509658141a92.png)
编辑对齐值
!(./notesimg/1658763545443-4a1f21a8-a041-4055-9878-bad4a2702c2f.png)
选中地址通过快捷键 d 可以添加成员
!(./notesimg/1658763604055-6c000dcb-8439-4c27-9cab-dfcb01bc8019.png)
alt + q可以吧改地址解释为结构体
!(./notesimg/1658763799621-1ffcce4c-e8e4-44fa-a44d-37b7364f8e6a.png)
或者到栈里面 按 alt + q
!(./notesimg/1658763877623-693c3a7d-7760-48db-92ad-a82a0b988bbc.png)
!(./notesimg/1658764089721-7bdeb2d7-2ccf-43ce-9ad1-a7bcbbd4a4c3.png)
或者到变量地址 **alt + q**
!(./notesimg/1658764230274-303a9963-7978-418e-933c-0441542e2cf1.png)
#### 第二种方法: 头文件导入
建一个结构体头文件
!(./notesimg/1658764553847-6dab75b1-07b6-4497-ae83-d28c784c3fa2.png)
导入
!(./notesimg/1658764665799-45bc625a-ffbf-4698-8a72-1cdf5b4e47ee.png)
导入成功
!(./notesimg/1658764705490-aaa18474-e914-4cf2-94ee-0b70fcf515ad.png)
有时候结构体会被折叠导致看不到,可以直接右键插入结构体(ins)
!(./notesimg/1658764822109-8625ab84-33b4-4411-9993-01eef340041e.png)
!(./notesimg/1658764930934-3ebd3561-7451-44df-a651-7714af574984.png)
!(./notesimg/1658765238423-9d613eac-5af4-4fe6-a522-676e56526ec7.png)
!(./notesimg/1658765181299-377b182c-91d7-4e32-9f07-dcf58fd337f5.png)
!(./notesimg/1658765280688-cc5bf325-7fb9-46c5-9882-92d35d4fa96b.png)
!(./notesimg/1658765310584-ca557bb6-79cc-4e5e-9289-14b1ae20c7ff.png)
### x32 ,OD 解析结构体
x32 也可以载入头文件来解析结构体
!(./notesimg/1658765756399-672d9fb8-7783-4364-a797-8289dc1f73e3.png)
!(./notesimg/1658765852958-9a030daa-5211-4872-9a4e-b7bf492464de.png)
!(./notesimg/1658766029328-ef76d114-da33-4a91-9ac9-9cd7dfe1d7e9.png)
!(./notesimg/1658766049014-9f537038-b90e-4c50-b413-30366a016985.png)
OD 没有改功能
## 函数的传参和返回值
### 参数是结构体
#### 结构体成员只有1个
push 1个成员 ,并没有传地址
```
struct Point {
int x;
};
void showPoint(Point pt1) {
printf("x=%d\n", pt1.x);
}
int main()
{
Point pt1 = { 10};
scanf_s("%d%d%d", &pt1.x);
showPoint(pt1);
return 0;
}
```
按照我们传参的约定,是吧结构体压入栈顶,然后把栈顶地址当做参数传进去,当做一个参数
```
lea eax,
mov dword ptr , 0Ah
push eax ; Arglist
push offset aD ; "%d"
call sub_401110
push dword ptr ; ArgList
call sub_401000
mov ecx,
add esp, 0Ch
编译器视为: void showPoint(intx )
```
#### 结构体有2个成员
push 2个成员 ,并没有传地址
```
struct Point {
int x;
int y;
};
void showPoint(Point pt1) {
printf("x=%d\n", pt1.x);
}
int main()
{
Point pt1 = { 10};
scanf_s("%d%d", &pt1.x, &pt1.y);
showPoint(pt1);
return 0;
}
```
lea eax,
mov , 0Ah
push eax
lea eax,
mov , 0
push eax
push offset aDD ; "%d%d"
call _scanf_s
**push **
**push ; pt1**
call ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
编译器视为: void showPoint(intx ,int y)
#### 结构体有多个成员
```
struct Point {
int x;
int y;
int z;
};
void showPoint(Point pt1) {
printf("x=%d y:%d z:%d\n", pt1.x, pt1.y, pt1.z);
}
int main()
{
Point pt1 = { 10};
scanf_s("%d%d%d", &pt1.x, &pt1.y, &pt1.z);
showPoint(pt1);
return 0;
}
```
```
lea eax,
mov , 0Ah
push eax
lea eax,
xorps xmm0, xmm0
push eax
lea eax,
movq qword ptr , xmm0
push eax ; pt1
push offset aDDD ; "%d%d%d"
call _scanf_s
movq xmm0, qword ptr
add esp, 4
mov eax,
mov ecx, esp
movq qword ptr , xmm0
mov , eax
call ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
产生了拷贝就是结构体 ,当参数超过 12字节,才会产生拷贝
```
### 返回值是结构体
#### 结构体只有一个成员 (返回值 长度<= 4 字节)
通过 eax 返回
```
struct Point {
int x;
};
Point GetPoint() {
Point p1 = { 1};
return p1;
}
int main()
{
Point ret = GetPoint();
printf("x:%d", ret.x);
return 0;
}
```
```
?GetPoint@@YA?AUPoint@@XZ proc near
mov eax, 1
retn
?GetPoint@@YA?AUPoint@@XZ endp
```
#### 返回值只有2个成员 (返回值 长度 4~ 8 字节)
通过 eax (结构体第一个成员),edx(结构体第二个成员) 返回
调用函数前会保存edx的值 (push),调完后会恢复edx的值 (pop)
```
struct Point {
int x;
int y;
};
Point GetPoint() {
Point p1 = { 1,2};
return p1;
}
int main()
{
Point ret = GetPoint();
printf("x:%d,y:%d", ret.x, ret.y);
return 0;
}
```
```
?GetPoint@@YA?AUPoint@@XZ proc near
mov eax, 1
lea edx,
retn
?GetPoint@@YA?AUPoint@@XZ endp
```
结构体地址当作参数传还进去,然后通过 地址+ 偏移 访问成员 ,然后返回 结构体地址
如果他前面还有一个其他参数,那么就会往后延,放到第二个
```
struct Point {
int x;
int y;
int z;
};
Point GetPoint() {
Point p1 = { 1,2,3};
return p1;
}
int main()
{
Point ret = GetPoint();
printf("x:%d,y:%d,z:%d", ret.x, ret.y, ret.z);
return 0;
}
```
```
push ebp
mov ebp, esp
sub esp, 24
lea eax,
push eax ; 把 ret 的地址当参数产进去了
call ?GetPoint@@YA?AUPoint@@XZ ; GetPoint(void)
push dword ptr
movq xmm0, qword ptr
movq qword ptr , xmm0
push
push
push offset _Format; "x:%d,y:%d,z:%d"
call _printf
函数实现
push ebp
mov ebp, esp
mov eax,
mov dword ptr , 1
mov dword ptr , 2
mov dword ptr , 3
pop ebp
retn
```
### 其他类型的返回值
#### 返回者是 int或 char
```
返回值是 eax
```
#### 返回值是 longlong
```
返回者 通过 eax (低4位),edx (高四位) 返回
```
```
long long GetInt() {
return 10;
}
int main()
{
long long ret2 = GetInt();
printf("ret:%lld", ret2);
return 0;
}
```
?GetInt@@YA_JXZ proc near
mov eax, 0Ah
xor edx, edx
retn
?GetInt@@YA_JXZ endp
#### 返回值是float
通过 st(0)返回
就算禁止使用x87指令集 ,结果还是放在 st(0)里面,因为需要固定返回值
```
float GetFloat() {
return 10.5f;
}
int main()
{
float ret = GetFloat();
printf("ret:%f", ret);
return 0;
}
```
?GetFloat@@YAMXZ proc near
fld ds:__real@41280000
retn
?GetFloat@@YAMXZ endp
#### 返回值是double
st(0)有80位,可以放下double
因此也是 通过 st(0)返回
```
double GetDouble() {
return 10.5;
}
int main()
{
double ret = GetDouble();
printf("ret:%f", ret);
return 0;
}
```
?GetDouble@@YANXZ proc near
fld ds:__real@4025000000000000
retn
?GetDouble@@YANXZ endp
### 其他编译器
在其他编译器中,不一定是这么传的,即我们可能碰到位置的编译器,此时逆向时,如果操作了寄存,进函数去看有没有直接用寄存器,直接用了说明是参数寄存器,如果是入栈是通过 ebp 访问
啥也不说了,楼主就是给力! 不管你信不信,反正我是信了。 不管你信不信,反正我是信了。 谢谢分享 楼主,不论什么情况你一定要hold住!hold住就是胜利! 看了LZ的帖子,我只想说一句很好很强大! 楼主,不论什么情况你一定要hold住!hold住就是胜利! 不管你信不信,反正我是信了。 啥也不说了,楼主就是给力!
页:
[1]
2