最近又拿起了黑客反汇编揭秘翻了翻,其中有一个例子引起了我的关注,我们知道数值类型和指针类型的参数一般是通过寄存器push(stdcall、cdecl约定)来传递的,那么一个结构体的大小肯定不止4字节(32位系统),那么他是如何进行传递的呢?难道像对象一样通过指针?其实不是,是通过堆栈指针直接传递,希望能给初初学者带来一点帮助。(本人也是初学者,大牛们嘴下留情,有错误请指出。)
vs2008,Max Speed (/02)编译源代码如下:
#include <stdio.h>
#include <string.h>
struct zzz
{
char s0[16];
int a;
float f;
};
void func(zzz y)
{
printf("%s %x %f\n", &y.s0[0], y.a, y.f);
}
int main()
{
zzz y;
strcpy(&y.s0[0], "Hello");
y.a=0x666;
y.f=6.6;
func(y);
}
.text:00401070 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401070 _main: ; CODE XREF: ___tmainCRTStartup+10Ap
.text:00401070 sub esp, 1Ch
.text:00401073 mov eax, dword ptr stru_403000.s0 ; 将结构体中s0的地址放入eax
.text:00401078 xor eax, esp ; 这里不明白,为何要和esp进行异或
.text:0040107A mov [esp+18h], eax ; s0地址存入放入esp+0x18
.text:0040107E mov ax, word ptr ds:aHello+4 ; 此处将"Hello"的第四个字符Ascii放入ax
.text:00401084 fld ds:flt_402108 ; 6.6压入浮点寄存器
.text:0040108A mov ecx, dword ptr ds:aHello ; "Hello"
.text:00401090 fstp dword ptr [esp+14h] ; 弹出6.6到esp+0x14
.text:00401094 mov [esp+4], ax ; 转存esp+4,不明白为何要保存ax?
.text:00401099 sub esp, 18h ; 分配sizeof(zzz)=0x18字节
.text:0040109C mov eax, esp ; eax指向栈顶
.text:0040109E mov [eax], ecx ; 字符串拷贝1,因为s0有16字节,所以分4句
.text:004010A0 mov ecx, [esp+1Ch]
.text:004010A4 mov [eax+4], ecx ; 字符串拷贝2
.text:004010A7 mov ecx, [esp+20h]
.text:004010AB mov [eax+8], ecx ; 字符串拷贝3
.text:004010AE mov ecx, [esp+24h]
.text:004010B2 mov edx, 666h
.text:004010B7 mov [eax+0Ch], ecx ; 字符串拷贝4
.text:004010BA mov [eax+10h], edx ; 666
.text:004010BD mov edx, [esp+2Ch]
.text:004010C1 mov [eax+14h], edx ; 6.6
.text:004010C4 call func ; 此时eax即esp开始的0x18字节均为zzz结构体了
.text:004010C9 mov ecx, [esp+30h]
.text:004010CD add esp, 18h
.text:004010D0 xor ecx, esp
.text:004010D2 xor eax, eax
.text:004010D4 call sub_4010DD
.text:004010D9 add esp, 1Ch
.text:004010DC retn
下面看看结构体是如何被取出的
.text:00401000 func proc near ; CODE XREF: .text:004010C4p
.text:00401000
.text:00401000 var_24 = qword ptr -24h
.text:00401000 var_1C = dword ptr -1Ch
.text:00401000 var_18 = dword ptr -18h
.text:00401000 var_14 = dword ptr -14h
.text:00401000 var_10 = dword ptr -10h
.text:00401000 var_C = dword ptr -0Ch
.text:00401000 var_8 = dword ptr -8
.text:00401000 var_4 = dword ptr -4
.text:00401000 arg_0 = zzz ptr 4
.text:00401000
.text:00401000 sub esp, 1Ch
.text:00401003 mov eax, dword ptr stru_403000.s0 ; 取出s0的地址
.text:00401008 xor eax, esp ; 又进行了异或,为何?
.text:0040100A mov [esp+1Ch+var_4], eax
.text:0040100E mov ecx, dword ptr [esp+1Ch+arg_0.s0+4] ; 取出"Hello"第2部分,由此可见取出字符串并不是按照顺序来的
.text:00401012 mov eax, dword ptr [esp+1Ch+arg_0.s0] ; 取出"Hello"第1部分
.text:00401016 mov edx, dword ptr [esp+1Ch+arg_0.s0+8] ; 取出"Hello"第3部分
.text:0040101A mov [esp+1Ch+var_1C], eax ; 保存"Hello"第1部分
.text:0040101D mov eax, dword ptr [esp+1Ch+arg_0.s0+0Ch] ; 取出"Hello"第4部分
.text:00401021 mov [esp+1Ch+var_18], ecx ; 保存"Hello"第2部分
.text:00401025 mov ecx, [esp+1Ch+arg_0.f] ; 取出6.6
.text:00401029 sub esp, 8
.text:0040102C mov [esp+24h+var_8], ecx
.text:00401030 fld [esp+24h+var_8]
.text:00401034 mov [esp+24h+var_10], eax ; 保存"Hello"第4部分
.text:00401038 fstp [esp+24h+var_24] ; 相当于push 6.6
.text:0040103B mov eax, [esp+24h+arg_0.a] ; 取出666
.text:0040103F mov [esp+24h+var_14], edx ; 保存"Hello"第3部分
.text:00401043 push eax ; push 666
.text:00401044 lea edx, [esp+28h+var_1C]
.text:00401048 push edx ; push "Hello"
.text:00401049 push offset Format ; "%s\t%x\t%f\n"
.text:0040104E mov [esp+30h+var_C], eax
.text:00401052 call ds:printf
.text:00401058 mov ecx, [esp+30h+var_4]
.text:0040105C add esp, 14h
.text:0040105F xor ecx, esp
.text:00401061 call sub_4010DD
.text:00401066 add esp, 1Ch
.text:00401069 retn
.text:00401069 func endp
为了分析方便,结构体zzz在IDA中定义一下
00000000 zzz struc ; (sizeof=0x18)
00000000 s0 db 16 dup(?)
00000010 a dd ?
00000014 f dd ?
00000018 zzz ends
编译好的exe和idb文件都附上。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法