2006年12月29日
易语言终于发布了许诺已久的新版本。这个版本更名为“易语言.飞扬”,是和原来版本完全不同的一个新的产品。
发布的帖子:
http://www.dywt.com.cn/vbs/dispbbs.asp?boardID=124&ID=100124&page=1
技术白皮书:
http://liigo.diy.myrice.com/efly/whitebook/index.html
http://liigo.diy.myrice.com/efly/whitebook/white_book.pdf
下载地址:
http://www.dywt.com.cn/edown/efly/efly.rar
2006年12月31日
由于这个版本是完全面向对象的,所以跟踪了一下类的创建和调用方式。
先写一段源代码,然后反汇编分析。
//////////////////////////////////////////////////////////////////////
类库TestClassLib1的原代码:
公开 类 TestClass1
{
公开 go()
{
控制台.输出行("Hello go!\n");
}
静态 公开 run()
{
控制台.输出行("Hello run!\n");
}
}
编译参数:
..\..\ec test2.ef -ecl_name="TestClassLib1" -out_mode=ecl
//////////////////////////////////////////////////////////////////////
测试程序源代码:
引入 TestClassLib1;
公开 类 启动类 <>
{
公开 静态 启动()
{
TestClass1 pObj1 = 创建 TestClass1;
pObj1.go();
TestClass1.run();
控制台.输出行("Hello world.\n");
返回;
}
}
编译参数:
..\..\ec test1.ef -ecl_name="程序" -starter_cls="启动类" -out_mode=runable -out="..\..\test1.exe"
//////////////////////////////////////////////////////////////////////
分析部分:
用OD加载生成的目标程序test1.exe。下面是程序入口处的代码:
004010AD 8D85 ECFAFFFF LEA EAX, [EBP-514]
004010B3 50 PUSH EAX
004010B4 E8 0B020000 CALL 004012C4
004010B9 68 1F104000 PUSH 0040101F ; ASCII "系统.NCL"
004010BE 8D85 ECFAFFFF LEA EAX, [EBP-514]
004010C4 50 PUSH EAX
004010C5 E8 EC010000 CALL <JMP.&KERNEL32.lstrcatA>
004010CA 50 PUSH EAX
004010CB E8 DA010000 CALL <JMP.&KERNEL32.LoadLibraryA>
004010D0 85C0 TEST EAX, EAX
004010D2 0F85 57010000 JNZ 0040122F
004010D8 8D85 E0F9FFFF LEA EAX, [EBP-620]
004010DE 50 PUSH EAX
004010DF 68 19000200 PUSH 20019
004010E4 6A 00 PUSH 0
004010E6 68 2F104000 PUSH 0040102F ; ASCII "Software\FlySky\NewE\Install"
004010EB 68 01000080 PUSH 80000001
004010F0 E8 FD010000 CALL <JMP.&ADVAPI32.RegOpenKeyExA>
004010F5 83F8 00 CMP EAX, 0
004010F8 75 7D JNZ SHORT 00401177
004010FA C785 DCF9FFFF 1>MOV DWORD PTR [EBP-624], 513
00401104 8D85 DCF9FFFF LEA EAX, [EBP-624]
0040110A 50 PUSH EAX
0040110B 8D85 ECFAFFFF LEA EAX, [EBP-514]
00401111 50 PUSH EAX
00401112 6A 00 PUSH 0
00401114 6A 00 PUSH 0
00401116 68 4C104000 PUSH 0040104C ; ASCII "Path"
0040111B FFB5 E0F9FFFF PUSH DWORD PTR [EBP-620]
00401121 E8 D2010000 CALL <JMP.&ADVAPI32.RegQueryValueExA>
00401126 50 PUSH EAX
00401127 FFB5 E0F9FFFF PUSH DWORD PTR [EBP-620]
0040112D E8 BA010000 CALL <JMP.&ADVAPI32.RegCloseKey>
00401132 58 POP EAX ; 0012F9A4
00401133 83F8 00 CMP EAX, 0
00401136 75 3F JNZ SHORT 00401177
00401138 8D85 ECFAFFFF LEA EAX, [EBP-514]
0040113E 50 PUSH EAX
0040113F E8 78010000 CALL <JMP.&KERNEL32.lstrlenA>
00401144 8D9D ECFAFFFF LEA EBX, [EBP-514]
0040114A 03D8 ADD EBX, EAX
0040114C 4B DEC EBX
0040114D 803B 5C CMP BYTE PTR [EBX], 5C
00401150 74 06 JE SHORT 00401158
00401152 66:C743 01 5C00 MOV WORD PTR [EBX+1], 5C
00401158 68 1F104000 PUSH 0040101F ; ASCII "系统.NCL"
0040115D 8D85 ECFAFFFF LEA EAX, [EBP-514]
00401163 50 PUSH EAX
00401164 E8 4D010000 CALL <JMP.&KERNEL32.lstrcatA>
00401169 50 PUSH EAX
0040116A E8 3B010000 CALL <JMP.&KERNEL32.LoadLibraryA>
0040116F 85C0 TEST EAX, EAX
00401171 0F85 B8000000 JNZ 0040122F
00401177 68 14050000 PUSH 514
0040117C 8D85 ECFAFFFF LEA EAX, [EBP-514]
00401182 50 PUSH EAX
00401183 68 51104000 PUSH 00401051 ; ASCII "ECLS_PATHS"
00401188 E8 11010000 CALL <JMP.&KERNEL32.GetEnvironmentVar>
0040118D 83F8 00 CMP EAX, 0
00401190 0F84 8D000000 JE 00401223
00401196 3D 14050000 CMP EAX, 514
0040119B 0F83 82000000 JNB 00401223
004011A1 C785 D8F9FFFF 0>MOV DWORD PTR [EBP-628], 0
004011AB 8D9D ECFAFFFF LEA EBX, [EBP-514]
004011B1 83BD D8F9FFFF 0>CMP DWORD PTR [EBP-628], 0
004011B8 75 69 JNZ SHORT 00401223
004011BA 8BD3 MOV EDX, EBX
004011BC 8A03 MOV AL, [EBX]
004011BE 3C 3B CMP AL, 3B
004011C0 74 17 JE SHORT 004011D9
004011C2 3C 2C CMP AL, 2C
004011C4 74 13 JE SHORT 004011D9
004011C6 3C 00 CMP AL, 0
004011C8 75 0C JNZ SHORT 004011D6
004011CA C785 D8F9FFFF 0>MOV DWORD PTR [EBP-628], 1
004011D4 EB 03 JMP SHORT 004011D9
004011D6 43 INC EBX
004011D7 ^ EB E3 JMP SHORT 004011BC
004011D9 8BCB MOV ECX, EBX
004011DB 2BCA SUB ECX, EDX ; ntdll.KiFastSystemCallRet
004011DD 74 41 JE SHORT 00401220
004011DF 53 PUSH EBX
004011E0 51 PUSH ECX
004011E1 51 PUSH ECX
004011E2 52 PUSH EDX ; ntdll.KiFastSystemCallRet
004011E3 8D85 E8F9FFFF LEA EAX, [EBP-618]
004011E9 50 PUSH EAX
004011EA E8 C1000000 CALL <JMP.&KERNEL32.RtlMoveMemory>
004011EF 59 POP ECX ; 0012F9A4
004011F0 8D9D E8F9FFFF LEA EBX, [EBP-618]
004011F6 03D9 ADD EBX, ECX
004011F8 4B DEC EBX
004011F9 803B 5C CMP BYTE PTR [EBX], 5C
004011FC 74 01 JE SHORT 004011FF
004011FE 43 INC EBX
004011FF 66:C703 5C00 MOV WORD PTR [EBX], 5C
00401204 68 1F104000 PUSH 0040101F ; ASCII "系统.NCL"
00401209 8D85 E8F9FFFF LEA EAX, [EBP-618]
0040120F 50 PUSH EAX
00401210 E8 A1000000 CALL <JMP.&KERNEL32.lstrcatA>
00401215 50 PUSH EAX
00401216 E8 8F000000 CALL <JMP.&KERNEL32.LoadLibraryA>
0040121B 5B POP EBX ; 0012F9A4
0040121C 85C0 TEST EAX, EAX
0040121E 75 0F JNZ SHORT 0040122F
00401220 43 INC EBX
00401221 ^ EB 8E JMP SHORT 004011B1
00401223 68 1F104000 PUSH 0040101F ; ASCII "系统.NCL"
00401228 E8 7D000000 CALL <JMP.&KERNEL32.LoadLibraryA>
0040122D 74 43 JE SHORT 00401272
0040122F 8985 E4F9FFFF MOV [EBP-61C], EAX
00401235 68 28104000 PUSH 00401028 ; ASCII "RunECL"
0040123A 50 PUSH EAX
0040123B E8 64000000 CALL <JMP.&KERNEL32.GetProcAddress>
00401240 85C0 TEST EAX, EAX
00401242 74 23 JE SHORT 00401267
00401244 8B25 00304000 MOV ESP, [403000]
0040124A 6A 00 PUSH 0
0040124C 6A 00 PUSH 0
0040124E E8 00000000 CALL 00401253
00401253 810424 AD2D0000 ADD DWORD PTR [ESP], 2DAD
0040125A FC CLD
0040125B FFD0 CALL EAX
0040125D 83C4 0C ADD ESP, 0C
00401260 6A 00 PUSH 0
00401262 E8 2B000000 CALL <JMP.&KERNEL32.ExitProcess>
00401267 FFB5 E4F9FFFF PUSH DWORD PTR [EBP-61C] ; ADVAPI32.77DA9BB8
0040126D E8 26000000 CALL <JMP.&KERNEL32.FreeLibrary>
00401272 6A 10 PUSH 10
00401274 68 A7104000 PUSH 004010A7 ; ASCII "Error"
00401279 68 5C104000 PUSH 0040105C ; ASCII "Not found the kernel class library or the kernel class library is invalid!"
0040127E 6A 00 PUSH 0
00401280 E8 07000000 CALL <JMP.&USER32.MessageBoxA>
00401285 B8 FFFFFFFF MOV EAX, -1
0040128A C9 LEAVE
0040128B C3 RETN
首先根据环境变量得到系统库的地址,目前是"系统.NCL"。然后得到"RunECL"函数的地址,用"RunECL(ecode RVA)"的方式调用。RunECL函数应该就是这个版本易语言的Loader,负责加载ecode RVA处的易格式。
等待Loader加载完成后,会返回到真正代码的领空。使用OD在00404108处下硬件断点可以在入口处断下来。不要下int3断点,好像Loader会对ecode的内存映像做类似于CRC的监测,所以如果下int3断点的话会直接从Loader中退出。
下面是ecode真正入口处的代码:
00404108 55 PUSH EBP
00404109 8BEC MOV EBP, ESP
0040410B 83EC 08 SUB ESP, 8
0040410E C745 FC 0000000>MOV DWORD PTR [EBP-4], 0
00404115 68 12424000 PUSH 00404212 ; UNICODE "TestClassLib1.TestClass1"
0040411A E8 51A0C00F CALL 1000E170
0040411F 83C4 04 ADD ESP, 4
00404122 8945 F8 MOV [EBP-8], EAX
00404125 8D4D FC LEA ECX, [EBP-4]
00404128 51 PUSH ECX
00404129 FF75 F8 PUSH DWORD PTR [EBP-8]
0040412C E8 5FB1C00F CALL 1000F290
00404131 83C4 08 ADD ESP, 8
00404134 8B45 FC MOV EAX, [EBP-4]
00404137 E8 6499C10F CALL 1001DAA0
0040413C 50 PUSH EAX
0040413D 8B40 04 MOV EAX, [EAX+4]
00404140 8B40 10 MOV EAX, [EAX+10]
00404143 FF50 00 CALL [EAX]
00404146 83C4 04 ADD ESP, 4
00404149 6A 01 PUSH 1
0040414B 68 12424000 PUSH 00404212 ; UNICODE "TestClassLib1.TestClass1"
00404150 E8 4BAEC00F CALL 1000EFA0
00404155 83C4 08 ADD ESP, 8
00404158 FFD0 CALL EAX
0040415A 68 44424000 PUSH 00404244
0040415F 6A 07 PUSH 7
00404161 68 64424000 PUSH 00404264 ; ASCII "鹼邁."
00404166 E8 35AEC00F CALL 1000EFA0
0040416B 83C4 08 ADD ESP, 8
0040416E FFD0 CALL EAX
00404170 83C4 04 ADD ESP, 4
00404173 FF75 FC PUSH DWORD PTR [EBP-4]
00404176 E8 45B1C00F CALL 1000F2C0
0040417B 83C4 04 ADD ESP, 4
0040417E 8BE5 MOV ESP, EBP
00404180 5D POP EBP ; 0038A968
00404181 C3 RETN
根据上面的源代码很容易分析出调用方法。由于是完全面向对象的编程,所以所有的调用都是基于Class的。调用分为Class static function的调用和Class virtual interface的调用。
如下代码:
00404149 6A 01 PUSH 1
0040414B 68 12424000 PUSH 00404212 ; UNICODE "TestClassLib1.TestClass1"
00404150 E8 4BAEC00F CALL 1000EFA0
00404155 83C4 08 ADD ESP, 8
00404158 FFD0 CALL EAX
是“TestClass1.run();”语句反汇编代码。
实际上做的类似如下的操作:
pAddr = GetVirtualAddress(UNICODE_Class_Name, Function_Index);
pAddr();
使用的是C方式的压栈方式。
由于这个版本的易语言类库支持类似于Java的反射功能,即RTTI。很明显可以看出编译的代码中已经完全开始使用这种技术来代替以前版本中的Function Table + Index的调用方式了。首先根据“组件名称.函数名称”来得到真正静态函数的地址,然后通过C方式压栈调用这个函数。
如下代码:
0040415A 68 44424000 PUSH 00404244
0040415F 6A 07 PUSH 7
00404161 68 64424000 PUSH 00404264 ; ASCII "鹼邁."
00404166 E8 35AEC00F CALL 1000EFA0
0040416B 83C4 08 ADD ESP, 8
0040416E FFD0 CALL EAX
00404170 83C4 04 ADD ESP, 4
也是类似的调用模式。
是代码“控制台.输出行("Hello world.\n");”的反汇编结果。地址00404264处是unicode的“系统.控制台”,然后根据静态函数序号“7”来得到“输出行”函数的指针。回栈后“PUSH 00404244”就变成了“输出行”函数的参数。
下面代码:
0040410E C745 FC 0000000>MOV DWORD PTR [EBP-4], 0
00404115 68 12424000 PUSH 00404212 ; UNICODE "TestClassLib1.TestClass1"
0040411A E8 51A0C00F CALL 1000E170 ;创建类TestClass1
0040411F 83C4 04 ADD ESP, 4
00404122 8945 F8 MOV [EBP-8], EAX ;得到this指针
00404125 8D4D FC LEA ECX, [EBP-4]
00404128 51 PUSH ECX
00404129 FF75 F8 PUSH DWORD PTR [EBP-8]
0040412C E8 5FB1C00F CALL 1000F290
00404131 83C4 08 ADD ESP, 8
00404134 8B45 FC MOV EAX, [EBP-4]
00404137 E8 6499C10F CALL 1001DAA0
0040413C 50 PUSH EAX ;压入this指针作为第一个隐含参数
0040413D 8B40 04 MOV EAX, [EAX+4] ;根据this指针取到虚表
00404140 8B40 10 MOV EAX, [EAX+10] ;根据虚表取到成员go的地址
00404143 FF50 00 CALL [EAX] ;go
00404146 83C4 04 ADD ESP, 4
是类成员函数的调用。即:
TestClass1 pObj1 = 创建 TestClass1;
pObj1.go();
首先还是使用UNICODE的类名称来进行类的创建,然后在0040413C出压入this指针,0040413D处取出虚表地址,00404140处从虚表里得到要道用的函数“go”的地址。然后“CALL [EAX]”直接调用。
具体的分析参见上面的注释。
放假前闲来无事随手写写,不当之处各位大虾请勿见怪。剩下的时间可以搞Zune去了^_^。如果有哪位朋友对Zune感兴趣,可以联系我;)
祝各位元旦快乐!