הסבר טכני תמציתי, שלב-אחר-שלב, למה שהקובץ עושה בפועל:
- טעינה ע״י ה-Loader
תחילת הקובץ מכילה חתימות MZ ו-PE\0\0.
ה-Loader של Windows מזהה זאת כ-PE, ממפה את ה-Image לזיכרון לפי ה-headers ומקפיץ ל-EntryPoint.
- אין Import Table רגילה
ה-DataDirectory של Imports ריק/לא בשימוש.
לכן אין IAT סטטית.
במקום זאת הקוד פותר APIs בזמן ריצה (manual resolving).
- קבלת כתובת בסיס של מודולים (גישה ל-PEB)
ב-EntryPoint מתבצעת גישה ל-PEB דרך ה-segment register (ב-x86 דרך FS:[0x30]).
מהלך טיפוסי:
קריאה ל-PEB
כניסה ל-PEB_LDR_DATA
מעבר על רשימת InMemoryOrderModuleList
כך מאתרים את:
kernel32.dll (כבר טעון בתהליך)
- מציאת Export Table של kernel32
לאחר שיש BaseAddress של kernel32:
קריאת offset e_lfanew מתוך DOS header
קפיצה ל-NT Headers
גישה ל-DataDirectory → Export Table
חישוב כתובות:
AddressOfNames
AddressOfFunctions
AddressOfNameOrdinals
- לולאת Hash על שמות פונקציות
הקוד עובר על כל שם פונקציה ב-Export Table:
מבנה אופייני שנראה אצלך:
AC
84 C0
74 07
C1 CA 0D
01 C2
זו לולאה שמבצעת:
קריאה תו-תו
rotate right
הוספת ערך
יצירת hash
כאשר ה-hash תואם לערך קשיח שמקודד בקוד → נמצאה הפונקציה.
בדרך זו מאתרים:
LoadLibraryA
GetProcAddress
- טעינת user32.dll
אחרי שיש כתובת של LoadLibraryA:
הקוד קורא לה עם המחרוזת "user32"
מתקבלת כתובת בסיס של user32.dll
- מציאת MessageBoxA
שוב מתבצע:
סריקת Export Table של user32
חישוב hash
התאמה ל-MessageBoxA
קבלת ה-function pointer
- קריאה ל-MessageBoxA
ה-stack נבנה ידנית:
סדר הפרמטרים (stdcall):
push 0 ; hWnd
push "hello world!" ; lpText
push "hello world!" ; lpCaption (או דומה)
push 0 ; uType
call MessageBoxA
לכן מתקבלת תיבת הודעה עם הטקסט.
- יציאה
לאחר הקריאה:
מתבצע ret או קריאה ל-ExitProcess (גם כן נפתר דינמית או פשוט חזרה ל-loader).
למה זה עובד בלי Import Table?
כי:
הכל מחושב בזמן ריצה.
אין תלות ב-IAT.
ה-Loader רק צריך למפות את ה-image ולהריץ את ה-EntryPoint.
מה זה בפועל?
PE מינימלי עם:
API hashing
Manual export resolving
ללא imports סטטיים
ללא sections מורכבות
זו טכניקה נפוצה בקבצים קומפקטיים מאוד וב-shellcode.
פירוט ממוקד לפי הרצף הבינארי ששלחת, משלב ה-EntryPoint והלאה (x86):
שלב 1 — כניסה וניווט ל-Export Table של kernel32
הרצף:
8B 55 3C
8B 54 15 78
01 EA
פירוק:
8B 55 3C
mov edx, [ebp+3Ch]
קריאת e_lfanew (offset ל-NT headers).
8B 54 15 78
mov edx, [ebp+edx+78h]
גישה ל-DataDirectory → Export Table.
01 EA
add edx, ebp
המרה מ-RVA לכתובת מוחלטת.
עכשיו EDX מצביע ל-IMAGE_EXPORT_DIRECTORY.
שלב 2 — שליפת טבלאות ה-Export
הרצף:
8B 4A 18
8B 5A 20
01 EB
49
פירוק:
8B 4A 18
mov ecx, [edx+18h]
NumberOfNames.
8B 5A 20
mov ebx, [edx+20h]
AddressOfNames (RVA).
01 EB
add ebx, ebp
EBX עכשיו מצביע למערך שמות.
49
dec ecx
התחלת לולאה.
שלב 3 — לולאת hash על שמות פונקציות
הרצף:
8B 34 8B
01 EE
52
31 C0
99
AC
84 C0
74 07
C1 CA 0D
01 C2
EB F4
פירוק לוגי:
8B 34 8B
mov esi, [ebx+ecx*4]
קבלת RVA של שם פונקציה.
01 EE
add esi, ebp
המרה לכתובת.
31 C0
xor eax,eax
hash = 0.
לולאת תווים:
AC → lodsb (טעינת תו)
84 C0 → test al,al (סוף מחרוזת?)
74 07 → אם 0 → יציאה
C1 CA 0D → ror edx,13 (rotate)
01 C2 → add edx,eax (עדכון hash)
EB F4 → חזרה ללולאה
זו לולאת hashing טיפוסית לזיהוי פונקציות בלי להשאיר שמות גלויים.
שלב 4 — התאמת hash
הרצף:
39 FA
5A
75 E5
39 FA → cmp edx, edi (השוואת hash לערך קשיח)
75 E5 → אם לא שווה → המשך לולאה
כאשר נמצא hash תואם → ממשיכים לשליפת הכתובת.
שלב 5 — שליפת כתובת פונקציה
הרצף:
8B 5A 24
01 EB
0F B7 0C 4B
8B 5A 1C
01 EB
03 2C 8B
פירוק:
8B 5A 24
AddressOfNameOrdinals
0F B7 0C 4B
movzx ecx, word [ebx+ecx*2]
קבלת ordinal
8B 5A 1C
AddressOfFunctions
03 2C 8B
הוספת offset של הפונקציה
בסיום — מתקבל pointer לפונקציה (למשל LoadLibraryA).
שלב 6 — קריאה ל-LoadLibraryA
בהמשך מופיע:
6A 00
68 30 00 40 00
68 0C 00 40 00
6A 00
FF D0
פירוק:
push 0
push offset string
push offset string
push 0
FF D0 → call eax
EAX מכיל pointer לפונקציה שנפתרה.
כאן נטענת "user32".
שלב 7 — פתרון MessageBoxA
אותו תהליך hash מתבצע על export table של user32.dll עד למציאת MessageBoxA.
שלב 8 — קריאה ל-MessageBoxA
Stack נבנה כך:
push 0
push "hello world!"
push "hello world!"
push 0
call MessageBoxA
ולכן מתקבלת תיבת הודעה עם המחרוזת שמופיעה בקובץ:
68 65 6C 6C 6F 20 77 6F 72 6C 64 21
מה זה בפועל?
אין Import Table
אין IAT
כל ה-API נפתר דינמית
hashing כדי להימנע משמות גלויים
PE מינימלי עם loader ידני
זו טכניקת manual export resolving מלאה.
פירוק מבנה ה-PE עצמו — למה ה-Loader עדיין מוכן להריץ אותו למרות שהוא מינימלי מאוד.
- DOS Header
התחלה:
4D 5A ; "MZ"
...
3C ?? ?? ?? ?? ; e_lfanew
4D 5A = חתימת DOS תקינה.
ב-offset 0x3C נמצא e_lfanew — מצביע ל-NT Headers.
ה-DOS stub כמעט לא קיים — אבל זה לא קריטי. ה-Loader צריך רק:
חתימה תקינה
offset חוקי ל-PE
- NT Headers
בכתובת שמצביע e_lfanew:
50 45 00 00 ; "PE\0\0"
4C 01 ; Machine = 0x14C (x86)
01 00 ; NumberOfSections = 1
זה מספיק כדי להיחשב PE תקני ל-32 ביט.
- Optional Header (PE32)
החתימה:
0B 01 ; PE32 (לא PE32+)
שדות קריטיים שה-Loader חייב:
AddressOfEntryPoint
ImageBase
SectionAlignment
FileAlignment
SizeOfImage
SizeOfHeaders
Subsystem
גם אם הערכים מינימליים מאוד — כל עוד הם עקביים לוגית, ה-Loader ימשיך.
- DataDirectory
Import Directory כאן ריק או לא שמיש.
זה חוקי.
Windows לא מחייב Import Table — רק אם יש אחת, הוא יטפל בה.
במקרה שלך:
אין IAT שימושית
לכן אין צורך ב-relocation imports
הכל נעשה בקוד
- Section Table
מוגדרת Section אחת בלבד (למשל .text או אפילו בלי שם משמעותי).
ה-Loader צריך:
VirtualAddress
VirtualSize
SizeOfRawData
PointerToRawData
Characteristics עם:
IMAGE_SCN_MEM_EXECUTE
IMAGE_SCN_MEM_READ
אם ה-Section מסומנת executable — זה מספיק.
- מיפוי לזיכרון
בעת הרצה:
Windows מקצה זיכרון בגודל SizeOfImage.
מעתיק את ה-Headers.
מעתיק את ה-Section ל-VirtualAddress.
קופץ ל-ImageBase + AddressOfEntryPoint.
אין צורך ב-IAT כי אין Imports לעבד.
- למה אין צורך ב-Relocations?
אם:
ImageBase תואם למה שה-Loader מצליח להקצות
או
הקוד משתמש ב-RVA יחסיים
אז relocation table לא חובה.
ב-PE מינימליים לעיתים:
פשוט מניחים ImageBase סטנדרטי
ואם הוא פנוי — הכל עובד
- למה Windows לא “דורש יותר”?
ה-Loader בודק מינימום:
חתימות תקינות
מבנה NT headers סביר
Section Table חוקית
EntryPoint בתוך טווח section executable
זהו.
כל השאר אופציונלי.
סיכום מבני
הקובץ שלך הוא:
DOS header מינימלי
NT headers תקינים
Section אחת
ללא Import Table
ללא IAT
ללא Relocations משמעותיות
EntryPoint שמבצע manual resolving
לכן:
ה-Loader עושה רק mapping → קפיצה ל-EntryPoint → ומשם הכל קורה בקוד שלך.