Prosty przykład, jak skompilować i wywołać dynamiczną metodę C#, wykorzystując wbudowany mechanizm kompilacji w .NET.
kompilator
RDG Packer Detector v0.7.3
RDG Packer Detector — to detektor packerów, exe-protectorów, joinerów, kompilatorów i binderów. Dzisiaj ukazała się nowa wersja 0.7.3.
Oficjalna strona projektu:
http://rdgsoft.net
Bezpośredni link do pobrania:
http://www.rdgsoft.net/downloads/RDG%20Packer%20Detector%20v0.7.3.2014.rar
Technologiczna bieda kompilatora PureBasic
Nawiązując do artykułu o kompilatorze Go, nie mogę ominąć kompilatora PureBasic w wersji od 4.xx do 5.30. Jest to kompilator dla kolejnej odmiany języka BASIC, której developerzy są głusi na wszelkie maile dotyczące nieprawidłowości w kodzie i w ogóle „nie rozumieją o co chodzi”.
Więc o co tym razem mi chodzi? Analizowałem plik kompilatora PureBasic.exe w wersji 5.30. Może zacznijmy od optymalizacji.
1. Optymalizacja
Optymalizacja to słowo, które jest obce twórcom kompilatora PureBasic. W poprzednim artykule ktoś sugerował, że biedna optymalizacja kompilatora Go być może ma jakiś „sens”, na pewno jakiś gimbus, który nie rozróżnia assemblera od akumulatora, no więc być może i tutaj znajdziesz jakiś sens (czekam na komentarz):
.code:004F165E mov ebp, [esp+10h+arg_70] .code:004F1665 push ebp .code:004F1666 pop ebp .code:004F1667 mov ebx, [ebp+684h] .code:004F166D cmp ebx, [esp+10h+var_10] .code:004F1670 jnz short loc_4F16A9 .code:004F1672 push 0 .code:004F1677 mov ebp, [esp+14h+arg_70] .code:004F167E push ebp .code:004F167F pop ebp .code:004F1680 push dword ptr [ebp+688h] .code:004F1686 call sub_5743CC
Nie wiem jak można by i to zaklasyfikować? push ebp + pop ebp = zero efektu, wyrównanie kolejnych instrukcji też nie.
Kolejny genialny fragment kodu:
.code:004F1FBE sub_4F1FBE proc near ; DATA XREF: start+50C7o .code:004F1FBE push dword_703918 .code:004F1FC4 pop eax .code:004F1FC5 mov dword_70411C, eax .code:004F1FCA push dword_7039CC .code:004F1FD0 pop eax .code:004F1FD1 mov dword_703EE4, eax .code:004F1FD6 push dword_703F08 .code:004F1FDC pop eax .code:004F1FDD mov dword_703CC0, eax .code:004F1FE2 xor eax, eax .code:004F1FE4 retn 4 .code:004F1FE4 sub_4F1FBE endp
Widać ktoś nie doczytał, że instrukcją mov można również odczytać dane z pamięci, pomijając dodatkowo stos.
Teraz trochę dłuższy fragment, w którym być może zauważycie pewien powtarzający się wzorzec:
.code:00401B0E mov ebp, dword_705314 .code:00401B14 mov dword ptr [ebp+0Ch], 31h .code:00401B1B mov dword ptr [ebp+10h], 5Bh .code:00401B22 add ebp, 8 .code:00401B25 push dword ptr [ebp+0Dh] .code:00401B28 call sub_56E513 .code:00401B2D mov ebp, dword_705314 .code:00401B33 add ebp, 8 .code:00401B36 mov ebp, [ebp+11h] .code:00401B39 mov edx, offset off_696024 ; Str .code:00401B3E lea ecx, [ebp+8] ; int .code:00401B41 call sub_540558 .code:00401B46 mov ebp, dword_705314 .code:00401B4C add ebp, 8 .code:00401B4F mov ebp, [ebp+11h] .code:00401B52 mov dword ptr [ebp+0Ch], 0C80000h .code:00401B59 mov ebp, dword_705314 .code:00401B5F add ebp, 8 .code:00401B62 mov ebp, [ebp+11h] .code:00401B65 mov dword ptr [ebp+10h], 1 .code:00401B6C mov ebp, dword_705314 .code:00401B72 add ebp, 8 .code:00401B75 push dword ptr [ebp+0Dh] .code:00401B78 call sub_56E513 .code:00401B7D mov ebp, dword_705314 .code:00401B83 add ebp, 8 .code:00401B86 mov ebp, [ebp+11h] .code:00401B89 mov edx, offset off_6953F2 ; Str .code:00401B8E lea ecx, [ebp+8] ; int .code:00401B91 call sub_540558 .code:00401B96 mov ebp, dword_705314 .code:00401B9C add ebp, 8 .code:00401B9F mov ebp, [ebp+11h] .code:00401BA2 mov dword ptr [ebp+0Ch], 0CA0000h .code:00401BA9 mov ebp, dword_705314 .code:00401BAF add ebp, 8 .code:00401BB2 mov ebp, [ebp+11h] .code:00401BB5 mov dword ptr [ebp+10h], 2 .code:00401BBC mov ebp, dword_705314 .code:00401BC2 add ebp, 8 .code:00401BC5 push dword ptr [ebp+0Dh] .code:00401BC8 call sub_56E513 .code:00401BCD mov ebp, dword_705314 .code:00401BD3 add ebp, 8 .code:00401BD6 mov ebp, [ebp+11h] .code:00401BD9 mov edx, offset off_691FE6 ; Str .code:00401BDE lea ecx, [ebp+8] ; int .code:00401BE1 call sub_540558 .code:00401BE6 mov ebp, dword_705314 .code:00401BEC add ebp, 8 .code:00401BEF mov ebp, [ebp+11h] .code:00401BF2 mov dword ptr [ebp+0Ch], 0C90000h .code:00401BF9 mov ebp, dword_705314 .code:00401BFF add ebp, 8 .code:00401C02 mov ebp, [ebp+11h] .code:00401C05 mov dword ptr [ebp+10h], 4 .code:00401C0C mov ebp, dword_705314 .code:00401C12 add ebp, 8 .code:00401C15 push dword ptr [ebp+0Dh] .code:00401C18 call sub_56E513 .code:00401C1D mov ebp, dword_705314 .code:00401C23 add ebp, 8 .code:00401C26 mov ebp, [ebp+11h] .code:00401C29 mov edx, offset off_691198 ; Str .code:00401C2E lea ecx, [ebp+8] ; int .code:00401C31 call sub_540558 .code:00401C36 mov ebp, dword_705314 .code:00401C3C add ebp, 8 .code:00401C3F mov ebp, [ebp+11h] .code:00401C42 mov dword ptr [ebp+0Ch], 0C40000h .code:00401C49 mov ebp, dword_705314 .code:00401C4F add ebp, 8 .code:00401C52 mov ebp, [ebp+11h] .code:00401C55 mov dword ptr [ebp+10h], 8 .code:00401C5C mov ebp, dword_705314 .code:00401C62 add ebp, 8 .code:00401C65 push dword ptr [ebp+0Dh] .code:00401C68 call sub_56E513 .code:00401C6D mov ebp, dword_705314 .code:00401C73 add ebp, 8 .code:00401C76 mov ebp, [ebp+11h] .code:00401C79 mov edx, offset off_69AAA0 ; Str .code:00401C7E lea ecx, [ebp+8] ; int .code:00401C81 call sub_540558 .code:00401C86 mov ebp, dword_705314 .code:00401C8C add ebp, 8 .code:00401C8F mov ebp, [ebp+11h] .code:00401C92 mov dword ptr [ebp+0Ch], 10000000h .code:00401C99 mov ebp, dword_705314 .code:00401C9F add ebp, 8 .code:00401CA2 mov ebp, [ebp+11h] .code:00401CA5 mov dword ptr [ebp+10h], 10h .code:00401CAC mov ebp, dword_705314 .code:00401CB2 add ebp, 8 .code:00401CB5 push dword ptr [ebp+0Dh] .code:00401CB8 call sub_56E513 .code:00401CBD mov ebp, dword_705314 .code:00401CC3 add ebp, 8 .code:00401CC6 mov ebp, [ebp+11h] .code:00401CC9 mov edx, offset off_695A52 ; Str .code:00401CCE lea ecx, [ebp+8] ; int .code:00401CD1 call sub_540558 .code:00401CD6 mov ebp, dword_705314 .code:00401CDC add ebp, 8 .code:00401CDF mov ebp, [ebp+11h] .code:00401CE2 mov dword ptr [ebp+0Ch], 0C00000h .code:00401CE9 mov ebp, dword_705314 .code:00401CEF add ebp, 8 .code:00401CF2 mov ebp, [ebp+11h] .code:00401CF5 mov dword ptr [ebp+10h], 20h .code:00401CFC mov ebp, dword_705314 .code:00401D02 add ebp, 8 .code:00401D05 push dword ptr [ebp+0Dh] .code:00401D08 call sub_56E513 .code:00401D0D mov ebp, dword_705314 .code:00401D13 add ebp, 8 .code:00401D16 mov ebp, [ebp+11h] .code:00401D19 mov edx, offset off_68ED54 ; Str .code:00401D1E lea ecx, [ebp+8] ; int .code:00401D21 call sub_540558 .code:00401D26 mov ebp, dword_705314 .code:00401D2C add ebp, 8 .code:00401D2F mov ebp, [ebp+11h] .code:00401D32 mov dword ptr [ebp+0Ch], 4 .code:00401D39 mov ebp, dword_705314 .code:00401D3F add ebp, 8 .code:00401D42 mov ebp, [ebp+11h] .code:00401D45 mov dword ptr [ebp+10h], 40h .code:00401D4C mov ebp, dword_705314 .code:00401D52 add ebp, 8 .code:00401D55 push dword ptr [ebp+0Dh] .code:00401D58 call sub_56E513 .code:00401D5D mov ebp, dword_705314 .code:00401D63 add ebp, 8 .code:00401D66 mov ebp, [ebp+11h] .code:00401D69 mov edx, offset off_69312E ; Str .code:00401D6E lea ecx, [ebp+8] ; int .code:00401D71 call sub_540558 .code:00401D76 mov ebp, dword_705314 .code:00401D7C add ebp, 8 .code:00401D7F mov ebp, [ebp+11h] .code:00401D82 mov dword ptr [ebp+0Ch], 80000000h .code:00401D89 mov ebp, dword_705314 .code:00401D8F add ebp, 8 .code:00401D92 mov ebp, [ebp+11h] .code:00401D95 mov dword ptr [ebp+10h], 80h .code:00401D9C mov ebp, dword_705314 .code:00401DA2 add ebp, 8 .code:00401DA5 push dword ptr [ebp+0Dh] .code:00401DA8 call sub_56E513 .code:00401DAD mov ebp, dword_705314 .code:00401DB3 add ebp, 8 .code:00401DB6 mov ebp, [ebp+11h] .code:00401DB9 mov edx, offset off_6916E6 ; Str .code:00401DBE lea ecx, [ebp+8] ; int .code:00401DC1 call sub_540558 .code:00401DC6 mov ebp, dword_705314 .code:00401DCC add ebp, 8 .code:00401DCF mov ebp, [ebp+11h] .code:00401DD2 mov dword ptr [ebp+0Ch], 1 .code:00401DD9 mov ebp, dword_705314 .code:00401DDF add ebp, 8 .code:00401DE2 mov ebp, [ebp+11h] .code:00401DE5 mov dword ptr [ebp+10h], 100h .code:00401DEC mov ebp, dword_705314 .code:00401DF2 add ebp, 8 .code:00401DF5 push dword ptr [ebp+0Dh] .code:00401DF8 call sub_56E513 .code:00401DFD mov ebp, dword_705314 .code:00401E03 add ebp, 8 .code:00401E06 mov ebp, [ebp+11h] .code:00401E09 mov edx, offset off_68C802 ; Str .code:00401E0E lea ecx, [ebp+8] ; int .code:00401E11 call sub_540558 .code:00401E16 mov ebp, dword_705314 .code:00401E1C add ebp, 8 .code:00401E1F mov ebp, [ebp+11h] .code:00401E22 mov dword ptr [ebp+0Ch], 2 .code:00401E29 mov ebp, dword_705314 .code:00401E2F add ebp, 8 .code:00401E32 mov ebp, [ebp+11h] .code:00401E35 mov dword ptr [ebp+10h], 200h .code:00401E3C mov ebp, dword_705314 .code:00401E42 add ebp, 8 .code:00401E45 push dword ptr [ebp+0Dh] .code:00401E48 call sub_56E513 .code:00401E4D mov ebp, dword_705314 .code:00401E53 add ebp, 8 .code:00401E56 mov ebp, [ebp+11h] .code:00401E59 mov edx, offset off_69776C ; Str .code:00401E5E lea ecx, [ebp+8] ; int .code:00401E61 call sub_540558 .code:00401E66 mov ebp, dword_705314 .code:00401E6C add ebp, 8 .code:00401E6F mov ebp, [ebp+11h] .code:00401E72 mov dword ptr [ebp+0Ch], 1000000h .code:00401E79 mov ebp, dword_705314 .code:00401E7F add ebp, 8 .code:00401E82 mov ebp, [ebp+11h] .code:00401E85 mov dword ptr [ebp+10h], 400h .code:00401E8C mov ebp, dword_705314 .code:00401E92 add ebp, 8 .code:00401E95 push dword ptr [ebp+0Dh] .code:00401E98 call sub_56E513 .code:00401E9D mov ebp, dword_705314 .code:00401EA3 add ebp, 8 .code:00401EA6 mov ebp, [ebp+11h] .code:00401EA9 mov edx, offset off_694F68 ; Str .code:00401EAE lea ecx, [ebp+8] ; int .code:00401EB1 call sub_540558 .code:00401EB6 mov ebp, dword_705314 .code:00401EBC add ebp, 8 .code:00401EBF mov ebp, [ebp+11h] .code:00401EC2 mov dword ptr [ebp+0Ch], 20000000h .code:00401EC9 mov ebp, dword_705314 .code:00401ECF add ebp, 8 .code:00401ED2 mov ebp, [ebp+11h] .code:00401ED5 mov dword ptr [ebp+10h], 800h .code:00401EDC mov ebp, dword_705314 .code:00401EE2 add ebp, 8 .code:00401EE5 push dword ptr [ebp+0Dh] .code:00401EE8 call sub_56E513 .code:00401EED mov ebp, dword_705314 .code:00401EF3 add ebp, 8 .code:00401EF6 mov ebp, [ebp+11h] .code:00401EF9 mov edx, offset off_690ADC ; Str .code:00401EFE lea ecx, [ebp+8] ; int .code:00401F01 call sub_540558 .code:00401F06 mov ebp, dword_705314 .code:00401F0C add ebp, 8 .code:00401F0F mov ebp, [ebp+11h] .code:00401F12 mov dword ptr [ebp+0Ch], 8 .code:00401F19 mov ebp, dword_705314 .code:00401F1F add ebp, 8 .code:00401F22 mov ebp, [ebp+11h] .code:00401F25 mov dword ptr [ebp+10h], 1000h .code:00401F2C mov ebp, dword_705314 .code:00401F32 add ebp, 8 .code:00401F35 push dword ptr [ebp+15h] .code:00401F38 call sub_56E513 .code:00401F3D mov ebp, dword_705314 .code:00401F43 add ebp, 8 .code:00401F46 mov ebp, [ebp+19h] .code:00401F49 mov edx, offset off_697A38 ; Str .code:00401F4E lea ecx, [ebp+8] ; int .code:00401F51 call sub_540558 .code:00401F56 mov ebp, dword_705314 .code:00401F5C add ebp, 8 .code:00401F5F push dword ptr [ebp+15h] .code:00401F62 call sub_56E513 .code:00401F67 mov ebp, dword_705314 .code:00401F6D add ebp, 8 .code:00401F70 mov ebp, [ebp+19h] .code:00401F73 mov edx, offset off_69D178 ; Str .code:00401F78 lea ecx, [ebp+8] ; int .code:00401F7B call sub_540558 .code:00401F80 mov ebp, dword_705314 .code:00401F86 add ebp, 8 .code:00401F89 push dword ptr [ebp+15h] .code:00401F8C call sub_56E513 .code:00401F91 mov ebp, dword_705314 .code:00401F97 add ebp, 8 .code:00401F9A mov ebp, [ebp+19h] .code:00401F9D mov edx, offset off_699E48 ; Str .code:00401FA2 lea ecx, [ebp+8] ; int .code:00401FA5 call sub_540558 .code:00401FAA mov ebp, dword_705314 .code:00401FB0 add ebp, 8 .code:00401FB3 push dword ptr [ebp+15h] .code:00401FB6 call sub_56E513 .code:00401FBB mov ebp, dword_705314 .code:00401FC1 add ebp, 8 .code:00401FC4 mov ebp, [ebp+19h] .code:00401FC7 mov edx, offset off_69770A ; Str .code:00401FCC lea ecx, [ebp+8] ; int .code:00401FCF call sub_540558 .code:00401FD4 mov ebp, dword_705314 .code:00401FDA add ebp, 8 .code:00401FDD push dword ptr [ebp+15h] .code:00401FE0 call sub_56E513 .code:00401FE5 mov ebp, dword_705314 .code:00401FEB add ebp, 8 .code:00401FEE mov ebp, [ebp+19h] .code:00401FF1 mov edx, offset off_692BA4 ; Str .code:00401FF6 lea ecx, [ebp+8] ; int .code:00401FF9 call sub_540558 .code:00401FFE mov ebp, dword_705314 .code:00402004 add ebp, 8 .code:00402007 push dword ptr [ebp+15h] .code:0040200A call sub_56E513 .code:0040200F mov ebp, dword_705314 .code:00402015 add ebp, 8 .code:00402018 mov ebp, [ebp+19h] .code:0040201B mov edx, offset off_68FA1C ; Str .code:00402020 lea ecx, [ebp+8] ; int .code:00402023 call sub_540558 .code:00402028 mov ebp, dword_705314 .code:0040202E add ebp, 8 .code:00402031 push dword ptr [ebp+15h] .code:00402034 call sub_56E513 .code:00402039 mov ebp, dword_705314 .code:0040203F add ebp, 8 .code:00402042 mov ebp, [ebp+19h] .code:00402045 mov edx, offset off_6953C2 ; Str .code:0040204A lea ecx, [ebp+8] ; int .code:0040204D call sub_540558 .code:00402052 mov ebp, dword_705314 .code:00402058 add ebp, 8 .code:0040205B push dword ptr [ebp+15h] .code:0040205E call sub_56E513 .code:00402063 mov ebp, dword_705314 .code:00402069 add ebp, 8 .code:0040206C mov ebp, [ebp+19h] .code:0040206F mov edx, offset off_699AE0 ; Str .code:00402074 lea ecx, [ebp+8] ; int .code:00402077 call sub_540558 .code:0040207C mov ebp, dword_705314 .code:00402082 add ebp, 8 .code:00402085 push dword ptr [ebp+15h] .code:00402088 call sub_56E513 .code:0040208D mov ebp, dword_705314 .code:00402093 add ebp, 8 .code:00402096 mov ebp, [ebp+19h] .code:00402099 mov edx, offset off_695382 ; Str .code:0040209E lea ecx, [ebp+8] ; int .code:004020A1 call sub_540558 .code:004020A6 mov ebp, dword_705314 .code:004020AC add ebp, 8 .code:004020AF push dword ptr [ebp+15h] .code:004020B2 call sub_56E513 .code:004020B7 mov ebp, dword_705314 .code:004020BD add ebp, 8 .code:004020C0 mov ebp, [ebp+19h] .code:004020C3 mov edx, offset off_692706 ; Str .code:004020C8 lea ecx, [ebp+8] ; int .code:004020CB call sub_540558 .code:004020D0 mov ebp, dword_705314 .code:004020D6 add ebp, 8 .code:004020D9 push dword ptr [ebp+15h] .code:004020DC call sub_56E513 .code:004020E1 mov ebp, dword_705314 .code:004020E7 add ebp, 8 .code:004020EA mov ebp, [ebp+19h] .code:004020ED mov edx, offset off_6897C6 ; Str .code:004020F2 lea ecx, [ebp+8] ; int .code:004020F5 call sub_540558 .code:004020FA mov ebp, dword_705314 .code:00402100 add ebp, 8 .code:00402103 push dword ptr [ebp+15h] .code:00402106 call sub_56E513 .code:0040210B mov ebp, dword_705314 .code:00402111 add ebp, 8 .code:00402114 mov ebp, [ebp+19h] .code:00402117 mov edx, offset off_69784A ; Str .code:0040211C lea ecx, [ebp+8] ; int .code:0040211F call sub_540558 .code:00402124 mov ebp, dword_705314 .code:0040212A add ebp, 8 .code:0040212D push dword ptr [ebp+15h] .code:00402130 call sub_56E513 .code:00402135 mov ebp, dword_705314 .code:0040213B add ebp, 8 .code:0040213E mov ebp, [ebp+19h] .code:00402141 mov edx, offset off_69790A ; Str .code:00402146 lea ecx, [ebp+8] ; int .code:00402149 call sub_540558 .code:0040214E mov ebp, dword_705314 .code:00402154 add ebp, 8 .code:00402157 push dword ptr [ebp+15h] .code:0040215A call sub_56E513 .code:0040215F mov ebp, dword_705314 .code:00402165 add ebp, 8 .code:00402168 mov ebp, [ebp+19h] .code:0040216B mov edx, offset off_698C36 ; Str .code:00402170 lea ecx, [ebp+8] ; int .code:00402173 call sub_540558 .code:00402178 push dword_705310 .code:0040217E call sub_56E513 .code:00402183 mov ebp, dword_705314 .code:00402189 mov dword ptr [ebp+0Ch], 32h .code:00402190 mov edx, offset aButton ; "Button" .code:00402195 lea ecx, [ebp+8] ; int .code:00402198 call sub_540558 .code:0040219D mov byte ptr [ebp+14h], 1 .code:004021A1 mov dword ptr [ebp+10h], 59h .code:004021A8 add ebp, 8 .code:004021AB push dword ptr [ebp+0Dh] .code:004021AE call sub_56E513 .code:004021B3 mov ebp, dword_705314 .code:004021B9 add ebp, 8 .code:004021BC mov ebp, [ebp+11h] .code:004021BF mov edx, offset off_696176 ; Str .code:004021C4 lea ecx, [ebp+8] ; int .code:004021C7 call sub_540558 .code:004021CC mov ebp, dword_705314 .code:004021D2 add ebp, 8 .code:004021D5 mov ebp, [ebp+11h] .code:004021D8 mov dword ptr [ebp+0Ch], 200h .code:004021DF mov ebp, dword_705314 .code:004021E5 add ebp, 8 .code:004021E8 mov ebp, [ebp+11h] .code:004021EB mov dword ptr [ebp+10h], 1 .code:004021F2 mov ebp, dword_705314 .code:004021F8 add ebp, 8 .code:004021FB push dword ptr [ebp+0Dh] .code:004021FE call sub_56E513 .code:00402203 mov ebp, dword_705314 .code:00402209 add ebp, 8 .code:0040220C mov ebp, [ebp+11h] .code:0040220F mov edx, offset off_6939FE ; Str .code:00402214 lea ecx, [ebp+8] ; int .code:00402217 call sub_540558 .code:0040221C mov ebp, dword_705314 .code:00402222 add ebp, 8 .code:00402225 mov ebp, [ebp+11h] .code:00402228 mov dword ptr [ebp+0Ch], 100h .code:0040222F mov ebp, dword_705314 .code:00402235 add ebp, 8 .code:00402238 mov ebp, [ebp+11h] .code:0040223B mov dword ptr [ebp+10h], 2 .code:00402242 mov ebp, dword_705314 .code:00402248 add ebp, 8 .code:0040224B push dword ptr [ebp+0Dh] .code:0040224E call sub_56E513 .code:00402253 mov ebp, dword_705314 .code:00402259 add ebp, 8 .code:0040225C mov ebp, [ebp+11h] .code:0040225F mov edx, offset off_68A2BC ; Str .code:00402264 lea ecx, [ebp+8] ; int .code:00402267 call sub_540558 .code:0040226C mov ebp, dword_705314 .code:00402272 add ebp, 8 .code:00402275 mov ebp, [ebp+11h] .code:00402278 mov dword ptr [ebp+0Ch], 1 .code:0040227F mov ebp, dword_705314 .code:00402285 add ebp, 8 .code:00402288 mov ebp, [ebp+11h] .code:0040228B mov dword ptr [ebp+10h], 4 .code:00402292 mov ebp, dword_705314 .code:00402298 add ebp, 8 .code:0040229B push dword ptr [ebp+0Dh] .code:0040229E call sub_56E513 .code:004022A3 mov ebp, dword_705314 .code:004022A9 add ebp, 8 .code:004022AC mov ebp, [ebp+11h] .code:004022AF mov edx, offset off_698054 ; Str .code:004022B4 lea ecx, [ebp+8] ; int .code:004022B7 call sub_540558 .code:004022BC mov ebp, dword_705314 .code:004022C2 add ebp, 8 .code:004022C5 mov ebp, [ebp+11h] .code:004022C8 mov dword ptr [ebp+0Ch], 2000h .code:004022CF mov ebp, dword_705314 .code:004022D5 add ebp, 8 .code:004022D8 mov ebp, [ebp+11h] .code:004022DB mov dword ptr [ebp+10h], 8 .code:004022E2 mov ebp, dword_705314 .code:004022E8 add ebp, 8 .code:004022EB push dword ptr [ebp+0Dh] .code:004022EE call sub_56E513 .code:004022F3 mov ebp, dword_705314 .code:004022F9 add ebp, 8 .code:004022FC mov ebp, [ebp+11h] .code:004022FF mov edx, offset off_69A590 ; Str .code:00402304 lea ecx, [ebp+8] ; int .code:00402307 call sub_540558 .code:0040230C mov ebp, dword_705314 .code:00402312 add ebp, 8 .code:00402315 mov ebp, [ebp+11h] .code:00402318 mov dword ptr [ebp+0Ch], 1003h .code:0040231F mov ebp, dword_705314 .code:00402325 add ebp, 8 .code:00402328 mov ebp, [ebp+11h] .code:0040232B mov dword ptr [ebp+10h], 10h .code:00402332 push dword_705310 .code:00402338 call sub_56E513 .code:0040233D mov ebp, dword_705314 .code:00402343 mov dword ptr [ebp+0Ch], 33h .code:0040234A mov byte ptr [ebp+14h], 1 .code:0040234E mov edx, offset aB_10 ; "B" .code:00402353 lea ecx, [ebp+8] ; int .code:00402356 call sub_540558 .code:0040235B mov dword ptr [ebp+10h], 5Ah .code:00402362 add ebp, 8 .code:00402365 push dword ptr [ebp+0Dh] .code:00402368 call sub_56E513 .code:0040236D mov ebp, dword_705314 .code:00402373 add ebp, 8 .code:00402376 mov ebp, [ebp+11h] .code:00402379 mov edx, offset off_69A590 ; Str .code:0040237E lea ecx, [ebp+8] ; int .code:00402381 call sub_540558 .code:00402386 mov ebp, dword_705314 .code:0040238C add ebp, 8 .code:0040238F mov ebp, [ebp+11h] .code:00402392 mov dword ptr [ebp+0Ch], 1003h .code:00402399 mov ebp, dword_705314 .code:0040239F add ebp, 8 .code:004023A2 mov ebp, [ebp+11h] .code:004023A5 mov dword ptr [ebp+10h], 10h .code:004023AC push dword_705310 .code:004023B2 call sub_56E513 .code:004023B7 mov ebp, dword_705314 .code:004023BD mov dword ptr [ebp+0Ch], 49h .code:004023C4 mov byte ptr [ebp+14h], 1 .code:004023C8 mov dword ptr [ebp+10h], 5Bh .code:004023CF mov edx, offset off_69635E ; Str .code:004023D4 lea ecx, [ebp+8] ; int .code:004023D7 call sub_540558 .code:004023DC add ebp, 8 .code:004023DF push dword ptr [ebp+0Dh] .code:004023E2 call sub_56E513 .code:004023E7 mov ebp, dword_705314
Kolejno ustawiana jest wartość rejestru EBP, nie zważając na to, że z 20 razy jest ta sama operacja wykonywana, być może twórcy PureBasic-a doczytali o loop unrollingu i wdrożyli go w życie z dodatkowymi benefitami…
2. Techniki czyszczenia stosu dla cdecl
Konwencja wywoływania funkcji cdecl bazuje na tym, że parametry są kolejno wrzucane na stos serią instrukcji push, po czym następuje wywołanie funkcji instrukcją call, po której wskaźnik stosu np. ESP jest korygowany o rozmiar wrzuconych wcześniej parametrów np. przez add esp,8 (dla dwóch 4 bajtowych parametrów etc.).
Różne kompilatory różnie radzą sobie z korektą stosu, jedne po każdym wywołaniu funkcji w konwencji cdecl korygują stos, inne potrafią inteligentnie obliczyć rozmiar parametrów i skorygować stos po całej serii wywołań wielu funkcji w konwencji cdecl (np. LCC), a jeszcze inne kompilatory robią to tak:
.code:00445E0D push eax .code:00445E0E push [esp+14h+var_10] ; lpMem .code:00445E12 call sub_59A300 .code:00445E17 pop eax .code:00445E18 add esp, 4 .code:00445E1B xor eax, eax
Mówiąc w skrócie po wykonaniu call-a (2 parametry, czyli 8 bajtów na stosie zostało do tego wykorzystanych) stos korygowany jest przez instrukcję pop eax (która działa jak add esp,4), po czym stos korygowany jest ponownie instrukcją add esp,4, a po niej wartość rejestru EAX, który jak można by się było domyśleć miał być do czegoś wykorzystany – jest zerowany! PureBasic – PureMagic 🙂
3. Sprawdzanie flag
W sumie można by to zaliczyć do optymalizacji, gdyby w ogóle kompilator jakieś stosował:
.code:004B42B2 call sub_50F9BC .code:004B42B7 and eax, eax .code:004B42B9 jz loc_4B43D4
.code:004B42D0 call sub_56E5C6 .code:004B42D5 or eax, eax .code:004B42D7 jz loc_4B43CF
.text:0065062E test eax, eax .text:00650630 jz short loc_65063B
Jak widać kompilator PureBasic w zależności od humoru stosuje 3 metody sprawdzania czy rejestr jest wyzerowany (z czego 2 nie są rekomendowane, gdyż to operacje zapisu czyli and i or, instrukcja test jedynie ustawia flagi).
4. Konwencja „zerocall”
W czasach DOS-u, częstą praktyką była konwencja wywoływania funkcji, w której ustawiano flagę carry (przeniesienia) instrukcją STC lub ją zerowano instrukcją CLC w zależności od wartości zwracanej przez funkcję (true / false). Przypadki użycia takich technik widziałem również w kilku programach na Windows, jednak to taka rzadkość, że można przypuszczać, że to twór programistów mających korzenie programistyczne w DOS-ie.
Wykorzystanie flagi carry miało swoje plusy, gdyż po wywołaniu funkcji nie trzeba było sprawdzać wyniku funkcji np. przez test eax,eax, a jedynie wykonać instrukcję skoku JC lub JNC.
PureBasic stosuje unikalną konwencję, którą nazwałem zerocall:
.text:0059A142 loc_59A142: ; CODE XREF: sub_59A110+22j .text:0059A142 pop ebx .text:0059A143 xor eax, eax .text:0059A145 test eax, eax .text:0059A147 retn .text:0059A147 sub_59A110 endp ... .code:00413667 mov edx, offset ValueName .code:0041366C pop ecx .code:0041366D call sub_59A110 .code:00413672 jz short loc_41367B
Czyli funkcja sama w sobie sprawdza wynik i ustawia flagę zerową, można powiedzieć, że jest to technologia unikalna dla PB, gdyż nigdy wcześniej nie spotkałem się z taką innowacją.
Co ciekawe miałoby to jedynie sens w przypadku optymalizacji pod względem rozmiaru i w przypadku zastosowanie tej techniki do wszystkich wygenerowanych funkcji, a tak niestety nie jest.
5. Zerowanie pustej pamięci
W sumie ten kod zainspirował mnie do kolejnego artykułu po kompilatorze języka Go. Generował go kompilator PB w wersji od 4.xx do 5.30.
.code:00401000 start proc near .code:00401000 .code:00401000 var_C = dword ptr -0Ch .code:00401000 var_8 = dword ptr -8 .code:00401000 .code:00401000 push 1C20h ; Size .code:00401005 push 0 ; Val .code:0040100A push offset hHeap ; Dst .code:0040100F call memset
Krótki opis. Zerowana jest pamięć od zmiennej hHeap, która znajduje się w sekcji danych „.data”. Zerowany bufor pamięci ma rozmiar 0x1C20 bajtów.
Spoglądając na deadlisting można zauważyć, że ten obszar jest pusty! A do tego jego większa część znajduje się w zakresie sekcji „.data”, która jest niezainicjalizowana.
Gwoli wyjaśnienia sekcja „.data” posiada ustawiony rozmiar fizyczny oraz rozmiar wirtualny, który przekracza rozmiar fizyczny co pozwala na zaoszczędzenie miejsca na dysku plikom, które chcą mieć zaalokowane duże obszary pamięci.
System Windows niezainicjalizowane obszary pamięci w sekcjach zawsze wypełnia zerami.
Zerowany obszar w plikach PureBasic jest zatem wypełniony 0x00 od samego początku i nie ma potrzeby dodatkowo go zerować, całej sytuacji smaczku dodaje jeszcze fakt, że obszar ten wykorzystywany jest do trzymania zmiennych globalnych i kompilator zamiast zapisać wartości tych zmiennych od razu do pliku, najpierw generuje kod, który zeruje cały ten obszar, a później dodatkowo generuje instrukcje, które ustawiają te wartości, czyli 2 razy marnowany jest czas procesora po uruchomieniu takiej aplikacji, podczas gdy cały ten proces można zrealizować na etapie kompilacji.
Konkluzje
PureBasic to całkiem ciekawe narzędzie, ale jakość generowanego kodu pozostawia wiele do życzenia i nie zdecydowałbym się na jego używanie do wysokowydajnych aplikacji, poza tym fundamentalne braki w wiedzy twórców kompilatora, w tak podstawowych kwestiach jak alokacja pamięci czy poprawne wykorzystanie instrukcji procesora x86, sprawiają, że można mieć wątpliwości co do samej jakości kodu jak i innych technologii użytych w tym narzędziu.
Technologiczna bieda kompilatora Go
Lubię babrać się w low levelu, to zarówno moja pasja jak i praca. Jestem w trakcie testowania nowego exe protectora i jak to zwykle bywa – testuje nim co popadnie, wliczając wszelkie nietypowe pliki w formacie PE, w tym pliki z różnych kompilatorów i środowisk programistycznych jak np. DMD, Lazarus / FPC, PureBasic, a nawet takie antyki jak WATCOM.
Różne kompilatory stosują całą mase magicznych zabiegów, aby czasami nieświadomie uniemożliwić zabezpieczenie wygenerowanego pliku PE EXE, a ja staram się to ładnie obsłużyć, ominąć, hookować etc.
Dzisiaj przysiadłem sobie do kompilatora języka wprost od Google, czyli – Go.
…i zdębiałem. Ściągnąłem paczkę v1.3.1 dla Windows w wersji 32 bitowej.
O ile protector poradził sobie bez zająknięcia z testowym zabezpieczeniem pliku kompilatora – go.exe, o tyle nie byłbym sobą jeśli bym nie zajrzał do środka, jak strukturalnie zbudowany jest plik z Go (biorąc pod uwagę, że kompilator Go zbudowany jest ze źródeł w C oraz samym języku Go).
A w środku szalone lata 90 :), spójrzmy na to razem:
1. Mania używania CLD
CLD to instrukcja procesora ustawiająca flagę kierunku wykonywania operacji takich jak rep stosb, rep movsd, scasb, jej zresetowanie powoduje, że operacje te wykonują się bajt po bajcie w kolejności rosnącej, jej ustawienie powoduje, że operacje te wykonują się odwrotnie (czyli od końca do przodu).
Problem w tym, że na systemach Windows ta flaga jest domyślnie już wyzerowana, a funkcje WinApi nigdy jej nie ustawiają, rzadkością są w ogóle przypadki, że kiedykolwiek jest ona ustawiana instrukcją STD, chyba jedynie w jakichś egzotycznych przykładach napisanych w assemblerze.
W binarce go.exe znajdziemy tego całą masę:
.text:00473CF8 call runtime_emptyfunc .text:00473CFD cld .text:00473CFE call runtime_check
W 1 bloku kodu (a co, kto bogatemu zabroni):
.text:004BCB1E lea esi, off_742838 .text:004BCB24 lea edi, [esp+88h+var_80] .text:004BCB28 cld .text:004BCB29 movsd .text:004BCB2A movsd .text:004BCB2B lea esi, off_740DF8 .text:004BCB31 lea edi, [esp+88h+var_78] .text:004BCB35 cld .text:004BCB36 movsd .text:004BCB37 movsd .text:004BCB38 mov [esp+88h+var_70], 0FFFFFFFFh
Jeszcze tutaj:
.text:00650751 lea esi, [esp+68h+var_44] .text:00650755 lea edi, [esp+68h+var_24] .text:00650759 cld .text:0065075A call sub_475CA0
I w całej masie kodu wszędzie CLD. Co ciekawe nie znalazłem w binarce ani jednej instrukcji STD, która sprawiałaby, że trzeba użyć CLD 😉
2. Undefined behaviour
W assemblerze znajdziemy instrukcje, których znaczenia nie zna nawet taki wymiatacz assemblera jak Rysio z Klanu. Wśród nich znajdziemy tajemniczą instrukcję UD2, lata temu stosowałem ją w metodach antydebug ze względu na to, że deasemblery w popularnych debuggerach nie wiedziały co z nią robić. Obecnie jest udokumentowana jako celowo nieprawidłowa instrukcja do testowania (sprytnie).
Jej zastosowanie w kodzie generowanym w nowoczesnych kompilatorach praktycznie nie istnieje. Na co komuś instrukcja, która niczego nie robi (oprócz linuxiarzy)? W kodzie kompilatora Go znalazła zastosowanie:
.text:004BC84B loc_4BC84B: ; CODE XREF: path_filepath_volumeNameLen+1BAj .text:004BC84B call runtime_panicindex .text:004BC850 ud2 .text:004BC852 ; --------------------------------------------------------------------------- .text:004BC852 .text:004BC852 loc_4BC852: ; CODE XREF: path_filepath_volumeNameLen+1C8j .text:004BC852 ; path_filepath_volumeNameLen+1CFj .text:004BC852 mov ebp, 1 .text:004BC857 jmp short loc_4BC833 .text:004BC859 ; --------------------------------------------------------------------------- .text:004BC859 .text:004BC859 loc_4BC859: ; CODE XREF: path_filepath_volumeNameLen+198j .text:004BC859 call runtime_panicindex .text:004BC85E ud2 .text:004BC860 ; --------------------------------------------------------------------------- .text:004BC860 .text:004BC860 loc_4BC860: ; CODE XREF: path_filepath_volumeNameLen+184j .text:004BC860 ; path_filepath_volumeNameLen+18Bj .text:004BC860 mov ebp, 1 .text:004BC865 jmp short loc_4BC7EF .text:004BC867 ; --------------------------------------------------------------------------- .text:004BC867 .text:004BC867 loc_4BC867: ; CODE XREF: path_filepath_volumeNameLen+172j .text:004BC867 call runtime_panicindex .text:004BC86C ud2
Jest ona tu zastosowana jako bariera, upewniająca, że po runtime_panicindex nic nie zostanie wykonane. Tak dla picu i marnotrastwa miejsca. To tak jakby po ExitProcess() dać dla bajery int 3.
3. Optymalizacyjne potworki
Są takie instrukcje, których manuale zarówno Intela jak i AMD rekomendują omijać szerokim łukiem. Dlaczego? Bo to archaiczne twory z początków procesorów 386, które przetrwały na liście instrukcji, jednak nikt ich już od tamtych czasów nie optymalizował w silikonie i nikt ich nie używa, oprócz paru szalonych programistów assemblera, którzy optymalizują kod pod względem rozmiaru.
Jedną z takich instrukcji jest xchg, która wymienia zawartość 2 rejestrów, od czasów MS-DOS nie widziałem, żeby jakikolwiek kompilator generował tą instrukcję w kodzie (oprócz ręcznych wstawek w assemblerze). A tutaj mamy tego całą masę:
.text:004BC7C4 xchg eax, ebp .text:004BC7C5 cmp al, 0 .text:004BC7C8 xchg eax, ebp .text:004BC7C9 jz loc_4BC86E .text:004BC7CF inc eax .text:004BC7D0 cmp eax, ecx .text:004BC7D2 jnb loc_4BC867 .text:004BC7D8 lea ebp, [edx+eax] .text:004BC7DB movzx ebp, byte ptr [ebp+0] .text:004BC7DF xchg eax, ebp .text:004BC7E0 cmp al, 5Ch .text:004BC7E3 xchg eax, ebp .text:004BC7E4 jz short loc_4BC860 .text:004BC7E6 xchg eax, ebp .text:004BC7E7 cmp al, 2Fh .text:004BC7EA xchg eax, ebp .text:004BC7EB jz short loc_4BC860 .text:004BC7ED xor ebp, ebp
Na pohybel manualom :P, już nawet nie mówię, że ten kod wygląda tak biednie, że mam ochotę dać darowiznę, nawet niewprawiony programista assemblera lepiej by to napisał.
Kolejny:
.text:00473F19 mov eax, offset runtime_call16 .text:00473F1E jmp eax
Podpowiem tym razem: jmp runtime_call16 i jesteście 2 bajty do przodu, niczego nie tracąc, a wręcz zyskując, jmp w tym wypadku to relatywny skok, więc wynikowy plik PE EXE będzie jeszcze mniejszy o rozmiar relokacji używany w instrukcji mov eax,adres_w_pamieci.
I znowu:
.text:0066A88E and ebx, 7 .text:0066A891 cmp ebx, 0 .text:0066A894 jz short loc_66A89F
Każdy lamer zaczynający programować w assemblerze później czy prędzej dowie się, że instrukcje takie jak and, or, xor ustawiają również flagi w zależności od wyniku wykonanej operacji, nie trzeba dodatkowo stosować porównania do sprawdzenia flagi.
Go lubi nadmiarowy kod:
.text:005FEE6C xor ebx, ebx .text:005FEE6E cmp eax, ebx .text:005FEE70 jz short loc_5FEEA4
Komentarz? 3 instrukcje do sprawdzenia czy rejestr EAX zawiera wartość 0. Na całym świecie kompilatorowym załatwiają to 2 instrukcje test eax,eax lub cmp eax,0 + skok.
4. Na pohybel Windowsowi
Nikt o zdrowych zmysłach nie korzysta w produkcyjnym kodzie ze struktur takich jak np. PEB czy TEB, gdyż wskutek ewolucji systemów operacyjnych, ich pola zawsze mogą się zmienić. Nikt oprócz kompilatora go.exe, który radośnie wykorzystuje go do śledzenia stosu i w razie potrzeby zwiększania jego rozmiaru:
.text:00473E10 mov ecx, large fs:14h .text:00473E17 mov ebx, [ecx+4] .text:00473E1D mov esi, [ebx]
Kto o zdrowych zmysłach i wbrew dokumentacji, która wyraźnie zaznacza, że struktury te mogą się zmienić w kolejnych wersjach systemu operacyjnego decyduje się na korzystanie z nich? Chyba ktoś, kto nigdy swojego oprogramowania nie testował na 2 różnych wersjach systemu Windows, albo nie zakłada, że będzie on musiał działać na innym OS.
5. Mamy swój standard
W świecie Windows istnieje określony sposób wywoływania funkcji WinApi – określany mianem stdcall, który zakłada, że kolejne parametry są wrzucane na stos i następuje wywołanie zadanej funkcji. Seria instrukcji push zakończona instrukcją call. Proste? Za proste! Skomplikujmy to. Oto jak wygląda wywoływanie funkcji WinApi w go.exe:
.text:00458C33 mov eax, SetConsoleCtrlHandler .text:00458C39 mov [esp+14h+var_14], eax .text:00458C3C mov eax, 2 .text:00458C41 mov [esp+14h+var_10], eax .text:00458C45 mov eax, offset runtime_ctrlhandler .text:00458C4A mov [esp+14h+var_C], eax .text:00458C4E mov eax, 1 .text:00458C53 mov [esp+14h+var_8], eax .text:00458C57 call runtime_stdcall
Czyli istnieje sobie wrapper o sweetaśnej nazwie runtime_stdcall(), któremu przekazywana jest funkcja jako wskaźnik, parametry, a wrapper sam wywołuje instrukcję call. Po co? Kto bogatemu zabroni!
Wnioski
Pierwszy jest taki, że jestem cholernie zawiedziony jakością kodu Go, jak widać można mieć górę pieniędzy, a i tak być technologicznie zacofanym do epoki kamienia łupanego.
Wiem, że w dobie nowych procesorów Intel Core i7 mało kto zwraca uwagę na takie detale, jednak nie wiem czy chciałbym aby moje oprogramowanie i algorytmy były kompilowane do takiej formy jaką obecnie oferuje najnowszy kompilator Go. To się po prostu nie dodaje jak mówi Mariusz Max Kolonko…
Na koniec jeszcze dump z programu Hello World w Go:
package main import "fmt" func main() { fmt.Println("Hello, 世界") }
.text:00401000 main_main proc near ; CODE XREF: main_main+1Aj .text:00401000 ; runtime_main+119p .text:00401000 ; DATA XREF: runtime_sighandler+42o .text:00401000 ; .text:off_4CFD0Co .text:00401000 .text:00401000 var_40 = dword ptr -40h .text:00401000 var_3C = dword ptr -3Ch .text:00401000 var_38 = dword ptr -38h .text:00401000 var_1C = dword ptr -1Ch .text:00401000 var_18 = dword ptr -18h .text:00401000 var_14 = byte ptr -14h .text:00401000 var_C = dword ptr -0Ch .text:00401000 var_8 = dword ptr -8 .text:00401000 var_4 = dword ptr -4 .text:00401000 .text:00401000 mov ecx, large fs:14h .text:00401007 mov ecx, [ecx+0] .text:0040100D cmp esp, [ecx] .text:0040100F ja short loc_40101C .text:00401011 xor edi, edi .text:00401013 xor eax, eax .text:00401015 call runtime_morestack_noctxt .text:0040101A jmp short main_main .text:0040101C ; --------------------------------------------------------------------------- .text:0040101C .text:0040101C loc_40101C: ; CODE XREF: main_main+Fj .text:0040101C sub esp, 40h .text:0040101F lea ebx, off_4B1260 .text:00401025 mov ebp, [ebx] .text:00401027 mov [esp+40h+var_1C], ebp .text:0040102B mov ebp, [ebx+4] .text:0040102E mov [esp+40h+var_18], ebp .text:00401032 lea edi, [esp+40h+var_14] .text:00401036 xor eax, eax .text:00401038 stosd .text:00401039 stosd .text:0040103A lea ebx, [esp+40h+var_14] .text:0040103E cmp ebx, 0 .text:00401041 jz short loc_401096 .text:00401043 .text:00401043 loc_401043: ; CODE XREF: main_main+98j .text:00401043 mov [esp+40h+var_C], ebx .text:00401047 mov [esp+40h+var_8], 1 .text:0040104F mov [esp+40h+var_4], 1 .text:00401057 mov [esp+40h+var_40], offset dword_48CEE0 .text:0040105E lea ebx, [esp+40h+var_1C] .text:00401062 mov [esp+40h+var_3C], ebx .text:00401066 call runtime_convT2E .text:0040106B mov edx, [esp+40h+var_C] .text:0040106F lea ebx, [esp+40h+var_38] .text:00401073 mov esi, ebx .text:00401075 mov edi, edx .text:00401077 cld .text:00401078 movsd .text:00401079 movsd .text:0040107A mov [esp+40h+var_40], edx .text:0040107D mov ebx, [esp+40h+var_8] .text:00401081 mov [esp+40h+var_3C], ebx .text:00401085 mov ebx, [esp+40h+var_4] .text:00401089 mov [esp+40h+var_38], ebx .text:0040108D call fmt_Println .text:00401092 add esp, 40h .text:00401095 retn .text:00401096 ; --------------------------------------------------------------------------- .text:00401096 .text:00401096 loc_401096: ; CODE XREF: main_main+41j .text:00401096 mov [ebx], eax .text:00401098 jmp short loc_401043 .text:00401098 main_main endp
Jeśli i wy doceniacie piękno assemblera i gardzicie lipnym kodem wpiszcie w komentarzach waszą wersję tej funkcji, najlepszy wpis czeka nagroda – pierwszy plakat formatu PE (bo już nie mam go gdzie powiesić). Nie spodziewam się rewelacji z waszej strony, ale kto wie 😉
Kompilator Brainfucka w 155 linijkach C
Brainfuck w 155 linijkach
Kompilator Brainfucka (JIT!) w bardzo małej formie:
http://dginasa.blogspot.com/2012/10/brainfuck-jit-compiler-in-around-155.html
Błąd kompilatora
Ciekawy błąd kompilatora, który poprawnie kompiluje kod w C#, który w praktyce nie jest poprawny
http://software.ericsink.com/entries/Not_All_Code_Paths_Return_A_Value.html