结构体
首先要考虑对齐值问题,但是在逆向角度是不关心的,因为结构体大小在代码中会体现
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 [ebp-0Ch], 0Ah       ;第一个成员
.text:0041186C                 mov     dword ptr [ebp-8], 14h            ;第二个成员
.text:00411873                 mov     eax, [ebp-8]
.text:00411876                 push    eax
.text:00411877                 mov     ecx, [ebp-0Ch]
.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     [ebp+var_4], eax
.text:00401090                 lea     eax, [ebp-8]
.text:00401093                 mov     dword ptr [ebp-0Ch], 0Ah
.text:0040109A                 push    eax
.text:0040109B                 lea     eax, [ebp-0Ch]
.text:0040109E                 mov     dword ptr [ebp-8], 14h
.text:004010A5                 push    eax             ; Arglist
.text:004010A6                 push    offset aDD      ; "%d%d"
.text:004010AB                 call    sub_401110
.text:004010B0                 push    dword ptr [ebp-8]
.text:004010B3                 push    dword ptr [ebp-0Ch] ; ArgList
.text:004010B6                 push    offset Format   ; "x:%d y:%d"
.text:004010BB                 call    sub_4010E0
.text:004010C0                 mov     ecx, [ebp+var_4]
.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 [ebp-14h], 0Ah
mov     dword ptr [ebp-10h], 14h
mov     dword ptr [ebp-0Ch], 1Eh
lea     eax, [ebp-0Ch]
push    eax
lea     ecx, [ebp-10h]
push    ecx
lea     edx, [ebp-14h]
push    edx             ; pt1
push    offset aDDD     ; "%d%d%d"
call    j__scanf_s
add     esp, 10h
sub     esp, 0Ch
mov     eax, esp
mov     ecx, [ebp-14h]
mov     [eax], ecx
mov     edx, [ebp-10h]
mov     [eax+4], edx
mov     ecx, [ebp-0Ch]
mov     [eax+8], ecx
call    j_?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
add     esp, 0Ch
xor     eax, eax
release版
lea     eax, [ebp-8]
mov     dword ptr [ebp-10h], 0Ah
push    eax
lea     eax, [ebp-0Ch]
mov     dword ptr [ebp-0Ch], 14h
push    eax
lea     eax, [ebp-10h]
mov     dword ptr [ebp-8], 1Eh
push    eax             ; pt1
push    offset aDDD     ; "%d%d%d"
call    _scanf_s
movq    xmm0, qword ptr [ebp-10h]
add     esp, 4      ; scanf 的栈需要平16字节,平了4字节还剩12字节,刚好结构体大小
mov     eax, [ebp-8]
mov     ecx, esp
movq    qword ptr [ecx], xmm0            ;8字节大小
mov     [ecx+8], eax                                   ;4字节大小的
call    ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
函数:
push    ebp
mov     ebp, esp
push    dword ptr [ebp+10h]       ;结构体首地址
push    dword ptr [ebp+0Ch]     ;本来应该是  首地址 +4   即    ebp+10h - 4  被优了
push    dword ptr [ebp+8]
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语言语法规定  当函数参数是结构体时,整个结构体拷贝到栈顶当参数
寻址方式:  
[ebx + offset]   当寄存器不够时就会出现 首地址 +偏移 寻址
当结构体成员比较多
struct Point {
    int x;
    int y;
    int z[100];
};
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 [ebp-1A0h], 0Ah
mov     dword ptr [ebp-19Ch], 14h
mov     dword ptr [ebp-198h], 1Eh
push    18Ch            ; Size
push    0               ; Val
lea     eax, [ebp-194h]
push    eax             ; void *
call    j__memset
add     esp, 0Ch
lea     eax, [ebp-198h]
push    eax
lea     ecx, [ebp-19Ch]
push    ecx
lea     edx, [ebp-1A0h]
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, [ebp-1A0h]          ;源首地址(结构体首地址)
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, [ebp-190h]
mov     dword ptr [ebp-19Ch], 0Ah
push    0               ; Val
push    eax             ; void *
mov     dword ptr [ebp-198h], 14h
mov     dword ptr [ebp-194h], 1Eh
call    _memset                  ;396  + 12 (3个mov)  = 408
lea     eax, [ebp-194h]
push    eax
lea     eax, [ebp-198h]
push    eax
lea     eax, [ebp-19Ch]
push    eax
push    offset aDDD     ; "%d%d%d"
call    _scanf_s
sub     esp, 17Ch            ;抬栈  
lea     esi, [ebp+pt1]
mov     ecx, 66h ; 'f'
mov     edi, esp
rep movsd
call    ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
结构体指针
struct Point {
    int x;
    int y;
    int z[100];
};
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, [ebp+var_190]
mov     [ebp+pt1.x], 0Ah
push    0               ; Val
push    eax             ; void *
mov     [ebp+pt1.y], 14h
mov     [ebp+pt1.z], 1Eh
call    _memset
lea     eax, [ebp+pt1.z]
push    eax
lea     eax, [ebp+pt1.y]
push    eax
lea     eax, [ebp+pt1]
push    eax
push    offset aDDD     ; "%d%d%d"
call    _scanf_s
lea     eax, [ebp+pt1]
push    eax             ; pt1
call    ?showPoint@@YAXPAUPoint@@@Z ; showPoint(Point *)
函数
push    ebp
mov     ebp, esp
mov     ecx, [ebp+pt1]    ;获取指针(首地址)
lea     eax, [ecx+8]           ;首地址 + 偏移寻址
push    eax
push    dword ptr [ecx+4]    ;首地址 + 偏移寻址
push    dword ptr [ecx]        ;首地址 + 偏移寻址
push    offset _Format  ; "x=%d y:%d z:%d\n"
call    _printf
add     esp, 10h
pop     ebp
retn
总结
因此判断是否结构体的方法
- 访问方式      [首地址 +  偏移]
- 传参在拷贝
如果上面2种都不存在,那么可能是局部变量
结构体成员如果没有被使用,可以当做不存在,还原结构体大小可以按照访问的最大偏移的来计算
而且一般结构体会初始化,可以从初始化获取结构体大小
数组和结构体的区别
- (1)数组
- 
- 还原为数组:(1)类型一致,业务一致。(2)地址首地址参与比例因子寻址或有循环。
- 数组传参:数组传参必然是指针。
 
- (2)结构体
- 
- 还原为结构体:(1)类型一致,业务不一致。(2)类型不一致,业务不一致。(3)!(地址首地址参与比例因子寻址或有循环)。
- 结构体传参:结构体传参是浅拷贝。如果是引用参数的话,以栈顶为目标,执行memcpy,在高版本下会使用两个多媒体指令相当于传入了一个参数,即,栈顶为参数地址。
 
结构体和类的区别
- 类:有成员函数的调用。
- 结构体:没有成员函数的调用
iad自动识别指定偏移位置的结构体成员
SDK定义的结构体会自动导入结构体类型
第一种方法:  添加结构体类型
当结构体成员很多时,我们使用ida 可能没法记住所有成员的偏移,因此ida 提供了该功能

右键创建结构体   快捷键   ins


编辑对齐值

选中地址通过快捷键 d 可以添加成员

alt + q  可以吧改地址解释为结构体

或者到栈里面 按 alt + q


或者到变量地址    alt + q

第二种方法: 头文件导入
建一个结构体头文件

导入

导入成功

有时候结构体会被折叠导致看不到,可以直接右键插入结构体  (ins)






x32 ,OD 解析结构体
x32 也可以载入头文件来解析结构体




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, [ebp-8]
mov     dword ptr [ebp-8], 0Ah
push    eax             ; Arglist
push    offset aD       ; "%d"
call    sub_401110
push    dword ptr [ebp-8] ; ArgList
call    sub_401000
mov     ecx, [ebp-4]
add     esp, 0Ch
编译器视为:    void showPoint(int  x ) 
结构体有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, [ebp+pt1.y]
mov     [ebp+pt1.x], 0Ah
push    eax
lea     eax, [ebp+pt1]
mov     [ebp+pt1.y], 0
push    eax
push    offset aDD      ; "%d%d"
call    _scanf_s
push    [ebp+pt1.y]
push    [ebp+pt1.x]     ; pt1
call    ?showPoint@@YAXUPoint@@@Z ; showPoint(Point)
编译器视为:    void showPoint(int  x ,  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, [ebp+var_8]
mov     [ebp+pt1.x], 0Ah
push    eax
lea     eax, [ebp+pt1.y]
xorps   xmm0, xmm0
push    eax
lea     eax, [ebp+pt1]
movq    qword ptr [ebp+pt1.y], xmm0
push    eax             ; pt1
push    offset aDDD     ; "%d%d%d"
call    _scanf_s
movq    xmm0, qword ptr [ebp+pt1.x]
add     esp, 4
mov     eax, [ebp+var_8]
mov     ecx, esp
movq    qword ptr [ecx], xmm0
mov     [ecx+8], 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, [eax+1]
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, [ebp+result]
push    eax             ; 把 ret 的地址当参数产进去了
call    ?GetPoint@@YA?AUPoint@@XZ ; GetPoint(void)
push    dword ptr [eax+8]
movq    xmm0, qword ptr [eax]
movq    qword ptr [ebp+ret.x], xmm0
push    [ebp+ret.y]
push    [ebp+ret.x]
push    offset _Format  ; "x:%d,y:%d,z:%d"
call    _printf
函数实现
push    ebp
mov     ebp, esp
mov     eax, [ebp+result]
mov     dword ptr [eax], 1
mov     dword ptr [eax+4], 2
mov     dword ptr [eax+8], 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 访问