Natukene siis kadunud tehnilist hiina keelt :)))
Räägin standardsest Windowsi PE32 failist (*.exe). Antud faili päises on tihti päris palju sektsioone, export section, data, import section jne
Meid huvitabki import section. Seal paikneb info (staatiliselt laetud) dll’ide kohta, mida me kasutame, koos funktsioonide nimedega. Nüüd kui programm käivitatakse, siis hakkab laadija import sektsiooni läbi kammima (staatiliselt lingitud dll) ja kenasti laeb protsessi piirkonda dll’i ning funktsiooni nimede massiiv asendatakse nende funktsioonide aadressitega. Süsteemsed dll’id kernel32, ntdll.dll jne laetakse vaid op.süsteemis vaid korra, aga laadija teab, kuidas käituda.
Kes nö vahet ei tee, siis ntx delphis on staatiline dll laadimine. Ehk programmi käivitamisel toimub laadimine:
function InetIsOffline(dwFlags: DWORD): BOOL; stdcall;
– function InetIsOffline; external ‘url.dll’ name ‘InetIsOffline’;
Dünaamiline, dll laetakse siis, kui vaja antud objekti funktsioone kasutada
type
TInetIsOffline = function (dwFlags: DWORD): BOOL; stdcall;
var
pInetOff :TInetIsOffline ;
pinst:=loadlibrary(‘url.dll’);
pInetOff:=getprocaddress( pinst,’InetIsOffline’);
…
freelibrary(pinst);
Mis vahe kahel laadimisel, esimese eelis see, et laadija teeb ära töö loadlibrary ja hiljem ka freelibrary. Tegelt ta teeb selle igal juhul ära, isegi kui kusagil unustad ära freelibrary ning programm lõpetab töö.
Staatiline linkimine natuke kiirem, samas (!) ntx kui mingi DLL puudu, siis programm ei käivitud ja võib saada ebameeldivat tehnilist sõimu.
Hea küll, on ka nö delay load, mida kasutatakse. Aga see süsteem, et on programmis loader, kui pöördutakse funktsiooni poole, siis alles dllid laetakse mällu. Kui ma ei eksi, siis .NETil on enamus dll’e delay loaded.
Aga nagu mõnel koosolekul tuleb öelda, asume nüüd ikka ka asja kallale / räägime, kus point:
Kujutage ette, laete ntx crypt32.dll mällu ja see tihti laetakse programmides STAATILISELT ! Nüüd mul on vaja kindlaid protseduure hakata jälgima, tahan ikka teie suuri saladusi teada saada.
Kõige lihtsam meetod, teen proxy dll’i. Ehk sisuliselt programmiga samas kataloogis on crypt32.dll, kuna laadija võtab esmalt kataloogis olevad dllid, siis see laetaksegi. Ja see proxy dll omakord laeb õige crypt32.dll’i
Minu soovitus:
turvalisusega tegelev tarkvara (ka meie ID kaardi tarkvara) peaks laadima .dll’e dünaamiliselt ja täispikka otsinguteed kasutades. Tarkvara teeb kindlaks windowsi süsteemi kataloogi ja siis teeb ala loadlibrary(‘c:\windows\system32\crypt32.dll’). See lihtne samm välistaks nö proxy dll häki.
Aus olla, spetsialistid ei viitsi neid proxy dll’e teha, kõiki neid API’sid forwardida, neid võib sadu olla. Mina ka ei viitsiks, samuti ei pruugi olla lihtne programmi kataloogi kirjutada (Vista/W7) Siis tuleb märksõna DLL injection – sisuliselt laen oma DLL’i sinu programmi külge, sisuliselt selle tegevusega saan ligipääsu programmi mälule.
Milleks see ligipääs hea ? Lihtne näide, ma saan nüüd muuta funktsioonide aadresse, mis dll staatilisel laadimisel omistati. Kui ma oleksin rott viljasalves…siis suunaksin vajalikud crypt32.dll API’d läbi injectitud DLLi. Samuti “varastaksin” ära API’d, mis eluliselt suht tähtsad programmile: loadlibrary, getprocessaddress, getmodulehandle . Nüüd kui laeksite programmis suvalist DLLi, siis saaksin mina otsustada, et mida ikka “reaalselt” laeme.
Kunagi mõtlesin, et mis oleks lahendus, kuidas programmi turvalisemaks muuta ! Siis tulin ideele, kasutada suht kaootiliselt dokumenteeritud FS registrit.
Ehk teisisõnu me võrdleme, kas loadlibrary funktsiooni aadress ikka klapib sellega, mida laadija meile tagastas. Juhul, kui ei klapi, siis teame, meid on “häkitud” / hackintosh kallal.
Preudokood:
>
> p:=getprocaddress(getmodulehandle(‘kernel32.dll’),’LoadLibraryA’)
>
peab võrduma läbi FS registri saadud LoadLibraryA aadressiga. Lihtne kas pole :))
FS registri kaudu saame teada kernel32 aadressi ja sealt aadressilt loeme export tablest välja LoadLibrary aadressi. Ehk teisisõnu, kui antud funktsioon annab true väärtuse, siis on programmi häkitud ja targem oleks kohe sulgeda ennast.
Näiteks kunagi tegin programmi multiregion/, tema kasutabki kuupäevade muutmiseks IAT häkki. Samas minu testprogramm koos järgneva turvafunktsiooniga kenasti tuvastas, et teda muudetud.
Kood töötas küll kenasti XP peal, kahjuks W7 veel ei tea.
function detectIATHook(var debbugerAlsoPresent : Boolean):Boolean;
// Author: Ingmar Tammeväli
// http://ingmar.planet.ee/programmeerimine
Type
PIMAGE_DOS_HEADER = ^_IMAGE_DOS_HEADER;
PIMAGE_NT_HEADERS = ^_IMAGE_NT_HEADERS;
PIMAGE_EXPORT_DIRECTORY = ^_IMAGE_EXPORT_DIRECTORY;
Type
PDWordArray = ^TDWordArray;
TDWordArray = Array [0..$FFFFF] of DWORD;
const
CAddrLower = $7000000;
Type
PWordRec = ^DWordRec;
DWordRec = packed record
case Integer of
0: (Lo, Hi: word);
1: (Bytes: array [0..1] of word);
end;
PJmpRec = ^TJmpRec;
TJmpRec = record
OpCode: Word; //$FF25(Jmp, FF /4)
Addr : DWordRec;
end;
type
TIsDebbugerPresent = function : boolean;stdcall;
var
basePointer : Pointer;
dos : PIMAGE_DOS_HEADER;
nt : PIMAGE_NT_HEADERS;
exportdir : PIMAGE_EXPORT_DIRECTORY;
i : Integer;
fFunctName : PAnsichar;
fLoadLibraryA : Pointer;
fGetModuleHandle : Pointer;
fGetProcAddr : Pointer;
fIsDebbugerPresent : Pointer;
// ---
IsDebbugerPresent: TIsDebbugerPresent;
fChkCallPtr : PPointer;
tmp : DWord;
begin
try
result:=false;
{
Sarnase koodi võib ka viirustest leida; kasutame vaenlast enda kasuks ära
Otsime kernel32.dll originaalaadressi Kuna IAT hookide tuvastamiseks peame saama kasutada "originaal kerneli" API'sid.
}
basePointer:=nil;
fLoadLibraryA:=nil;
fGetModuleHandle:=nil;
fGetProcAddr:=nil;
fIsDebbugerPresent:=nil;
debbugerAlsoPresent:=False;
asm
mov eax, dword ptr fs:[30h] // PEB
mov eax, dword ptr [eax+0ch] // PEB_LDR_DATA
mov esi, dword ptr [eax+1ch] // nimistu algus
lodsd
mov ebx, dword ptr [eax+08h]
xchg ebx,basePointer
end;
// ------------
// alla selle aadressi kernelit lihtsalt ei laeta
if dword(basePointer)<CAddrLower then
exit;
// normaalses olukorras peaks need aadressi klappima; vähemalt W2K/WinXP all asjad klapivad
// vista võib aadressitega mängida
result:=(getmodulehandle('ntdll.dll')dword(basePointer)) and // võib olla vanemate SP puhul
(getmodulehandle('kernel32.dll')dword(basePointer));
if result then
exit;
basePointer:=pointer(getmodulehandle('kernel32.dll'));
// okei...nüüd meid huvitavad API'd GetModuleHandle, GetProcAddress
// proovime otsida nüüd originaal aadressid; käime export table läbi
dos:=PIMAGE_DOS_HEADER(basePointer);
//goto the kernel base address
if (dosnil) and (dos^.e_magic=IMAGE_DOS_SIGNATURE) then
begin
nt:=pointer(cardinal(dos)+dos^._lfanew);
if (nt^.Signature=IMAGE_NT_SIGNATURE) then
if nt^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress>0 then
begin
exportdir:=PIMAGE_EXPORT_DIRECTORY(cardinal(dos) +
nt^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
for i:=0 to exportdir^.NumberOfNames-1 do
begin
fFunctName:=(pointer(cardinal(dos)+
PDWordArray(Pointer(dword(exportdir^.AddressOfNames)+dword(dos)))[i]));
// LoadLibraryW selle variandi võite täiendavalt lisada
if fFunctName='LoadLibraryA' then
fLoadLibraryA:=(pointer(cardinal(dos)+
PDWordArray(Pointer(dword(exportdir^.AddressOfFunctions)+dword(dos)))[i]))
else
if fFunctName='GetModuleHandleA' then
fGetModuleHandle:=(pointer(cardinal(dos)+
PDWordArray(Pointer(dword(exportdir^.AddressOfFunctions)+dword(dos)))[i]))
else
if fFunctName='GetProcAddress' then
fGetProcAddr:=(pointer(cardinal(dos)+
PDWordArray(Pointer(dword(exportdir^.AddressOfFunctions)+dword(dos)))[i]))
else
if fFunctName='IsDebuggerPresent' then // vajadusel ka CheckRemoteDebuggerPresent
fIsDebbugerPresent:=(pointer(cardinal(dos)+
PDWordArray(Pointer(dword(exportdir^.AddressOfFunctions)+dword(dos)))[i]));
end;
end;
end;
// meil peavad olema 3 API aadressid, vastasel juhul mingi viga algoritmis
if assigned(fLoadLibraryA) and
assigned(fGetModuleHandle) and
assigned(fGetProcAddr) then
begin
// loadlib check
with PJmpRec(@loadlibrary)^ do
begin
tmp:=0;
DWordRec(tmp).Lo:=Addr.Lo;
DWordRec(tmp).Hi:=Addr.Hi;
fChkCallPtr:=ppointer(ptr(tmp))^;
result:=(fChkCallPtrfLoadLibraryA);
if result then
exit;
end;
// getprocaddress check
with PJmpRec(@getprocaddress)^ do
begin
tmp:=0;
DWordRec(tmp).Lo:=Addr.Lo;
DWordRec(tmp).Hi:=Addr.Hi;
fChkCallPtr:=ppointer(ptr(tmp))^;
result:=(fChkCallPtrfGetProcAddr);
if result then
exit;
end;
// getmodulehandle check
with PJmpRec(@getmodulehandle)^ do
begin
tmp:=0;
DWordRec(tmp).Lo:=Addr.Lo;
DWordRec(tmp).Hi:=Addr.Hi;
fChkCallPtr:=ppointer(ptr(tmp))^;
result:=(fChkCallPtrfGetModuleHandle);
if result then
exit;
end;
IsDebbugerPresent:=getprocAddress(getmodulehandle('kernel32.dll'),'IsDebuggerPresent');
if assigned(IsDebbugerPresent) then
begin
result:=(@IsDebbugerPresentfIsDebbugerPresent);
if result then
exit;
// -----------
debbugerAlsoPresent:=IsDebbugerPresent
end;
// ---
result:=false;
end;
except
end;
end;