SHOW:
|
|
- or go back to the newest paste.
1 | AIVD Cyber Challenge: crypt.exe en n.zip.enc, november 2012 | |
2 | ||
3 | ||
4 | Er waren meerdere wegen die naar de oplossing leidden, dit was mijn oplossing. | |
5 | ||
6 | Tools: hiew, ida/hex-rays, ollydbg. | |
7 | ||
8 | Er zitten enkele anti-debug-grapjes in de exe: | |
9 | 1. Het bijhouden van de tijd dmv GetTickCount; als er teveel tijd verstrijkt kan dat wijzen op een debuggende hacker (of iemand die nog op z'n 386 werkt); | |
10 | 2. Enkele trucjes die al dan niet tot (SEH-handled) exceptions leiden afhankelijk van het gedebugt worden: | |
11 | - een CloseHandle met een ongeldig argument; | |
12 | - een OutputDebugString (geen idee waarom dit werkt); | |
13 | - een int3. | |
14 | 3. De meldingen die crypt.exe toont zijn versleuteld, dit maakt het even lastiger zoeken naar waar ze in de code gebruikt worden. | |
15 | ||
16 | En er zitten nog wat anti-disassemblingvalkuilen in, vooral een functie die functiepointers teruggeeft afhankelijk van het argument. Vrijwel alle belangrijke functies in crypt worden indirect via deze functiepointergeverfunctie() aangeroepen. Waarschijnlijk zag de source-code er ongeveer zo uit: | |
17 | ||
18 | ((cast naar juiste fn ptr type)getFunctionPointer(MEM_MOVE))(source,dest,size); | |
19 | ||
20 | Dit kost de hacker wat meer tijd omdat ida niet helpt met reffen. En er zit nog een block 00-bytes midden in de code, zodat de disassembler gelooft dat het data is in plaats van code. Voor het blok nullen wordt eax gelijkgezet aan esp en de cpu interpreteert de dw 0's als "add [eax],al"-instructies die op zo'n manier geen invloed hebben op de uitvoering van het programma. | |
21 | ||
22 | Om te beginnen heb ik met hiew de anti-debugtrucs uit de exe gepatcht totdat ik in ollydbg crypt.exe kon doorlopen alsof hij niet gedebugt wordt: | |
23 | ||
24 | ---------------------------------(patches)-------------------------------------- | |
25 | adres: nieuw oorspronkelijk | |
26 | ||
27 | ;doe alsof de relocations gestript zijn zodat het image altijd op hetzelfde base adress geladen wordt = makkelijker x-reffen tussen debugger en ida | |
28 | 000000EE: 03 02 | |
29 | ||
30 | ;de 0-bytes doen in deze situatie niets behalve ollydbg en ida plagen. -> nops, we zijn nu immers toch bezig | |
31 | 0000077E: 90 00 | |
32 | : | |
33 | 000007D7: 90 00 | |
34 | ||
35 | ;weg met int3-antidebug | |
36 | 0000091A: 90 CC | |
37 | 0000091B: 90 EB | |
38 | 0000091C: 90 11 | |
39 | 0000091D: 90 33 | |
40 | 0000091E: 90 C0 | |
41 | 0000091F: 90 40 | |
42 | 00000920: 90 C3 | |
43 | 00000921: 90 8B | |
44 | 00000922: 90 65 | |
45 | 00000923: 90 E8 | |
46 | ||
47 | ;weg met stoute closehandle | |
48 | 00000A19: 90 68 | |
49 | 00000A1A: 90 C8 | |
50 | 00000A1B: 90 CA | |
51 | 00000A1C: 90 40 | |
52 | 00000A1D: 90 00 | |
53 | 00000A1E: 90 FF | |
54 | 00000A1F: 90 15 | |
55 | 00000A20: 90 08 | |
56 | 00000A21: 90 B0 | |
57 | 00000A22: 90 40 | |
58 | 00000A23: 90 00 | |
59 | ||
60 | ;zet het adres van onze nieuw te maken gettickcount-vervanger in de tabel | |
61 | 00000D55: B8 A1 | |
62 | 00000D57: AD B0 | |
63 | ||
64 | ;ik heb geen idee hoe de outputdebugstring-truc werkt, maar zo doorloop je iig het goede executiepad | |
65 | 00000E8A: 07 0E | |
66 | ||
67 | ;onze gettickcount-vervanger geeft altijd 0 terug als tickcount - de tijd staat stil | |
68 | 0000A100: 33 00 | |
69 | 0000A101: C0 00 | |
70 | 0000A102: C3 00 | |
71 | ---------------------------------(patches)-------------------------------------- | |
72 | ||
73 | Nu is e.e.a. gemakkelijker te debuggen/disassembleren. Over dit proces kan ik niet zoveel vertellen, da's nogal persoonlijk denk ik, maar bij mij bestaat het uit: lezen in ida (zowel asm als hex-rays c), en debuggen om te controleren of wat je denkt te zien ook werkelijk doet wat je ziet. In ieder geval had ik op een bepaald moment de relevante functies, met mijn eigen crappy radende benaming: | |
74 | ||
75 | 0x401000 mysteriousFunctionDispatcher //geeft op een obscure manier fn. ptrs. terug. alle onderstaande functies (en meer) worden via deze functie aangeroepen. | |
76 | 0x401078 strcpywrapper | |
77 | 0x401081 decryptmessage //voor showCodedMessage | |
78 | 0x4010b9 showCodedMessage //toont versleutelde melding adhv een nummer | |
79 | 0x401165 mystfunc17 //op deze functies kom ik uitvoerig terug | |
80 | 0x4011d1 mystfunc16 //: | |
81 | 0x401362 mystfunc18 | |
82 | 0x401444 mystfunc19 | |
83 | 0x401490 ietsmetpassword | |
84 | 0x40153b doDecryptie //== doEncryptie | |
85 | 0x4015cb BerekenTweedeDwordNaEncSlash //op deze functie kom ik uitvoerig terug | |
86 | 0x401656 encrypt | |
87 | 0x401792 decrypt | |
88 | 0x401949 outputDebugStringStdout //OutputDebugString("stdout")-trucje | |
89 | 0x401955 initPointersToLibFuncsAndMystFuncs //init de tabel die mysteriousFunctionDispatcher gebruikt | |
90 | ||
91 | Crypt.exe gebruikt een hoofdsleutel van 4 dwords en een even lange "rolling key". Geen idee of dat laatste de correcte naam is, ik vond het wel lekker klinken. In ieder geval wordt de hoofdsleutel aan de hand van het gegeven wachtwoord gemaakt, wordt de rolling key geseed met een pseudorandom beginwaarde, en wordt tijdens het versleutelen van bestanden de rolling key steeds herberekend. De data van de input (output) wordt in happen van 16 bytes gexord met de rolling key en levert de output (input) op. Een .enc-bestand bestaat uit een marker (ENC/), een checksum-achtig dword (door mij ook wel "tweede dword na enc slash" genoemd), vervolgens 4 dwords die als seed gebruikt worden voor de rolling key en daarna versleutelde data. | |
92 | ||
93 | De code steekt zo ongeveer in elkaar: | |
94 | ||
95 | //versleutelt een bestand | |
96 | encrypt() | |
97 | ietsmetpassword() //maakt de hoofdsleutel adhv het password | |
98 | BerekenTweedeDwordNaSlashEnc() //berekent rollingkey[0], het checksum-achtige ding of het wachtwoord klopt | |
99 | schrijf rollingkey0 | |
100 | mystfun19() //seedt rollingkey[0..3]: hoofdsleutel gexord met GetTickCount, zodat hetzelfde bestand versleutelen niet per se steeds hetzelfde oplevert | |
101 | schrijf rollingkey[0..3 ] //seed | |
102 | doencryptie() //==dodecryptie | |
103 | ||
104 | //maakt passhash en daarmee hoofdsleutel en rollingkey0 | |
105 | ietsmetpassword(password) | |
106 | vult passhash met 0x3c | |
107 | telt bij byte passhash[0] strlen(password) | |
108 | telt bij passhash[1..15] het password byte voor byte op, index is % 15 | |
109 | roept mystfunc18() aan | |
110 | ||
111 | //maakt van passhash de hoofdsleutel en rollingkey | |
112 | mystfunc18() | |
113 | roept mystfunc17(&passhash, hoofdsleutel) //maakt hoofdsleutel (swapt lil/big endian) | |
114 | - | memmove(naar rollingkey, van hoofdsleutel, 16) //key := otherkey |
114 | + | memmove(naar rollingkey, van hoofdsleutel, 16) //key := hoofdsleutel |
115 | memset maakt hoofdsleutel 0 //hoofdsleutel = 0 | |
116 | roept mystfunc16() //update de rollingkey, ingesteld op de passhash, met een nul-hoofdsleutel | |
117 | memmove(naar hoofdsleutel, van rollingkey, 16) //hoofdsleutel := rollingkey | |
118 | ||
119 | //swapt big/lil endian voor 4 dwords | |
120 | mystfunc17(src, dest) | |
121 | doet gekkigheid met src zodat dest endian-gewisseld is | |
122 | ||
123 | //seedt de rollingkey adhv tijd en hoofdsleutel | |
124 | mystfun19() | |
125 | stelt rollingkey in op tickcount xor hoofdsleutel | |
126 | roept mystfunc16() aan | |
127 | ||
128 | //maakt het checksum-achtige ding: rollingkey wordt een xorfeestje van de hoofdsleutel, rollingkey wordt geupdate, en rollingkey[0] is het "checksum". | |
129 | BerekenTweedeDwordNaSlashEnc() | |
130 | rollingkey wordt berekend adhv hoofdsleutel (xor, rolling[0]=hoofd]0]^hoofd[1]^hoofd[2]; rolling[1]= hoofd[0]^[1]^[3], ... etc) | |
131 | roept mystfunc16() | |
132 | return rolling[0]; | |
133 | ||
134 | //de belangrijkste functie. berekent de rollingkey adhv de hoofdsleutel en de vorige waarde van rollingkey | |
135 | //even mijn eigen reconstructie: | |
136 | void mystfunc16() | |
137 | { | |
138 | const unsigned char husselaar[]={0x0E, 3, 0xA, 0xB, 0, 9, 8, 5, 0xC, 0xF, 6, 1, 4, 0xD, 2, 7}; | |
139 | ||
140 | for(signed int o = 0; o < 32; o++) | |
141 | { | |
142 | rollingkey[0] = -109254729 * (hoofdsleutel[0] + rollingkey[0]); | |
143 | rollingkey[1] = -378528053 * (hoofdsleutel[1] + rollingkey[1]); | |
144 | rollingkey[2] = 1880111777 * (hoofdsleutel[2] + rollingkey[2]); | |
145 | rollingkey[3] = -1115618541 * (o + hoofdsleutel[o % 3] + rollingkey[3]); | |
146 | int newkey0 = 0; | |
147 | int newkey1 = 0; | |
148 | int newkey2 = 0; | |
149 | int newkey3 = 0; | |
150 | ||
151 | //voor iedere bit van rollingkey... | |
152 | for(signed int i=0; i < 32; i++) | |
153 | { | |
154 | int j = 31 - i; | |
155 | ||
156 | int k0 = (rollingkey[0] >> j) & 1; //k0 is bit op j van key0 | |
157 | int k1 = (rollingkey[1] >> j) & 1; //k1 is bit op j van key1 | |
158 | int k2 = (rollingkey[2] >> j) & 1; //: | |
159 | int k3 = (rollingkey[3] >> j) & 1; | |
160 | ||
161 | int husselindex= k3 << 3 | k2 << 2 | k1 << 1 | k0; //index is (msb) k3,k2,k1,k0 (lsb) | |
162 | unsigned int v3 = (unsigned __int8)husselaar[husselindex];//map index -> v3 via lut | |
163 | ||
164 | newkey0 = (newkey0<<1) + ((v3 >> 0) & 1); //schuif bit 0 van v3 vanaf rechts in newkey0 | |
165 | newkey1 = (newkey1<<1) + ((v3 >> 1) & 1); //schuif bit 1 van v3 vanaf rechts in newkey1 | |
166 | newkey2 = (newkey2<<1) + ((v3 >> 2) & 1); //: | |
167 | newkey3 = (newkey3<<1) + ((v3 >> 3) & 1); | |
168 | } | |
169 | ||
170 | rollingkey[0] = newkey0; //haha! de husselaar is zo gemaakt dat rollingkey[0] zichzelf blijft! da's handig! | |
171 | rollingkey[1] = newkey1; | |
172 | rollingkey[2] = newkey2; | |
173 | rollingkey[3] = newkey3; | |
174 | } | |
175 | ||
176 | - | rollingkey[0] += otherkey[0]; |
176 | + | rollingkey[0] += hoofdsleutel[0]; |
177 | - | rollingkey[1] += otherkey[1]; |
177 | + | rollingkey[1] += hoofdsleutel[1]; |
178 | - | rollingkey[2] += otherkey[2]; |
178 | + | rollingkey[2] += hoofdsleutel[2]; |
179 | - | rollingkey[3] += otherkey[0]; /haha! otherkey[3] wordt nooit gebruikt! |
179 | + | rollingkey[3] += hoofdsleutel[0]; /haha! hoofdsleutel[3] wordt nooit gebruikt! |
180 | } | |
181 | ||
182 | mystfunc16 heeft dus wat eigenaardigheden die ons leven een stuk makkelijker maken: rollingkey0 hangt alleen van zichzelf en van hoofdsleutel0 af; hoofdsleutel3 wordt niet helemaal niet gebruikt. Rollingkey1 en 2 zijn helaas wel pitas. | |
183 | ||
184 | Maar welke informatie kunnen we nu uit het .enc bestand lospeuteren? | |
185 | ||
186 | We weten rollingkey0 na ENC/: | |
187 | rollingkey0 na ENC/ | |
188 | hoofdsleutel = mystfunc16(passhash, 0) | |
189 | rollingkey0 = hoofdsleutel0 ^ hoofdsleutel1 ^ hoofdsleutel2; | |
190 | rollingkey1 = hoofdsleutel0 ^ hoofdsleutel1 ^ hoofdsleutel3, etc... | |
191 | rollingkey0 = mystfunc16(rollingkey0, hoofdsleutel) | |
192 | ||
193 | We weten rollingkey0..3 als seed: | |
194 | rollingkey0..3 als seed | |
195 | = mystfunc16(hoofdsleutel[0..3] ^ tickcount, hoofdsleutel) | |
196 | ||
197 | We weten uit de plaintekst (de rest van de plaintekst kunnen we gokken, maar is niet zeker): | |
198 | rollingkey0,1 uit plaintekst | |
199 | is 1x gemystfunc16d tov seeded rollingkey00..3 | |
200 | ||
201 | Nu gaan we als volgt te werk: tussen rollingkey0 uit plaintekst en rollingkey0 als seed is er een mystfunc16() gedaan. We kunnen daarmee berekenen wat hoofdsleutel0 moet zijn. Uit de rollingkey0 na ENC/ kunnen we met de bekende hoofdsleutel0 terugrekenen wat het verband is tussen hoofdsleutel1 en hoofdsleutel2: rollingkey0 = hoofdsleutel0 ^ hoofdsleutel1 ^ hoofdsleutel2. Kortom, hoofdsleutel0 is bekend, hoofdsleutel1 is een functie van hoofdsleutel2 (of andersom), en hoofdsleutel3 wordt niet gebruikt. Er zijn dus maar 2^32 combinaties te proberen. Ik heb een brute forcer gemaakt die dat allemaal probeert, en met wat heuristics kijkt of er een fatsoenlijke zip uit lijkt te komen. Na de bruteforcer even gedraaid te laten hebben, is er al snel een patroon te zien, zodat we in stappen van 0x8000 de 2^32 combinaties hoeven te doorlopen (=2^17). De zips die de heuristics doorstaan worden weggeschreven (16 stuks), en er is er maar eentje die correct uitpakt. Met strengere heuristics zouden er minder rommel-zips uitkomen, maar ik wilde niet te veel aannemen over de verwachte eigenschappen van de zip. | |
202 | ||
203 | Hier de uitvoer van m'n kraker: | |
204 | ||
205 | enc: : 0x2f434e45 | |
206 | 2e dword na enc/ : 0x6a6ba51c | |
207 | rolseed0: : 0xe4d6f212 | |
208 | rolseed1: : 0x5c4f1594 | |
209 | rolseed2: : 0xa4aea115 | |
210 | rolseed3: : 0xf453ebb6 | |
211 | rollingkey0 next : 0xf98a31ba | |
212 | hoofdsleutel0 : 0x4099f1a8 | |
213 | hoofdsleutel0^tick : 0x409dfa6a | |
214 | tickcount : 0x00040bc2 (hun systeem had ~4 minuten uptime op het moment van versleutelen) | |
215 | tweededwordvoorcrypt: 0x0ef47b74 | |
216 | hoofd1 ^ hoofd2 : 0x4e6d8adc | |
217 | ||
218 | bf met offset 15509 step 32768 (zZZzz)... | |
219 | heuristic #1 : 0x000d0014 (flags) | |
220 | heuristic #2 : 0x3c3a0000 (00+filename[0,1]) | |
221 | mogelijke oplossing : 0x4099f1a8, 0x17d73c95, 0x59bab649 | |
222 | decrypting n.zip.enc into out.dec.17d73c95... ok | |
223 | heuristic #1 : 0x000d0014 (flags) | |
224 | heuristic #2 : 0x67520000 (00+filename[0,1]) | |
225 | mogelijke oplossing : 0x4099f1a8, 0x1b9b3c95, 0x55f6b649 | |
226 | decrypting n.zip.enc into out.dec.1b9b3c95... ok | |
227 | heuristic #1 : 0x00080014 (flags) | |
228 | heuristic #2 : 0x6b5a0000 (00+filename[0,1]) | |
229 | mogelijke oplossing : 0x4099f1a8, 0x381bbc95, 0x76763649 | |
230 | decrypting n.zip.enc into out.dec.381bbc95... ok | |
231 | heuristic #1 : 0x00000014 (flags) | |
232 | heuristic #2 : 0x65670000 (00+filename[0,1]) | |
233 | mogelijke oplossing : 0x4099f1a8, 0x3c90bc95, 0x72fd3649 | |
234 | decrypting n.zip.enc into out.dec.3c90bc95... ok <--- dit bleek de juiste te zijn | |
235 | heuristic #1 : 0x00080014 (flags) | |
236 | heuristic #2 : 0x677a0000 (00+filename[0,1]) | |
237 | mogelijke oplossing : 0x4099f1a8, 0x42c7bc95, 0x0caa3649 | |
238 | decrypting n.zip.enc into out.dec.42c7bc95... ok | |
239 | heuristic #1 : 0x00000014 (flags) | |
240 | heuristic #2 : 0x65670000 (00+filename[0,1]) | |
241 | mogelijke oplossing : 0x4099f1a8, 0x4c90bc95, 0x02fd3649 | |
242 | decrypting n.zip.enc into out.dec.4c90bc95... ok | |
243 | heuristic #1 : 0x00070014 (flags) | |
244 | heuristic #2 : 0x704d0000 (00+filename[0,1]) | |
245 | mogelijke oplossing : 0x4099f1a8, 0x4d263c95, 0x034bb649 | |
246 | decrypting n.zip.enc into out.dec.4d263c95... ok | |
247 | heuristic #1 : 0x000d0014 (flags) | |
248 | heuristic #2 : 0x67520000 (00+filename[0,1]) | |
249 | mogelijke oplossing : 0x4099f1a8, 0x5b9b3c95, 0x15f6b649 | |
250 | decrypting n.zip.enc into out.dec.5b9b3c95... ok | |
251 | heuristic #1 : 0x00080014 (flags) | |
252 | heuristic #2 : 0x635a0000 (00+filename[0,1]) | |
253 | mogelijke oplossing : 0x4099f1a8, 0x941bbc95, 0xda763649 | |
254 | decrypting n.zip.enc into out.dec.941bbc95... ok | |
255 | heuristic #1 : 0x000d0014 (flags) | |
256 | heuristic #2 : 0x553a0000 (00+filename[0,1]) | |
257 | mogelijke oplossing : 0x4099f1a8, 0x9cd73c95, 0xd2bab649 | |
258 | decrypting n.zip.enc into out.dec.9cd73c95... ok | |
259 | heuristic #1 : 0x00000014 (flags) | |
260 | heuristic #2 : 0x4d670000 (00+filename[0,1]) | |
261 | mogelijke oplossing : 0x4099f1a8, 0xa890bc95, 0xe6fd3649 | |
262 | decrypting n.zip.enc into out.dec.a890bc95... ok | |
263 | heuristic #1 : 0x000d0014 (flags) | |
264 | heuristic #2 : 0x4d3a0000 (00+filename[0,1]) | |
265 | mogelijke oplossing : 0x4099f1a8, 0xb4d73c95, 0xfabab649 | |
266 | decrypting n.zip.enc into out.dec.b4d73c95... ok | |
267 | heuristic #1 : 0x00000014 (flags) | |
268 | heuristic #2 : 0x65670000 (00+filename[0,1]) | |
269 | mogelijke oplossing : 0x4099f1a8, 0xbc90bc95, 0xf2fd3649 | |
270 | decrypting n.zip.enc into out.dec.bc90bc95... ok | |
271 | heuristic #1 : 0x000d0014 (flags) | |
272 | heuristic #2 : 0x67520000 (00+filename[0,1]) | |
273 | mogelijke oplossing : 0x4099f1a8, 0xdb9b3c95, 0x95f6b649 | |
274 | decrypting n.zip.enc into out.dec.db9b3c95... ok | |
275 | heuristic #1 : 0x00070014 (flags) | |
276 | heuristic #2 : 0x604d0000 (00+filename[0,1]) | |
277 | mogelijke oplossing : 0x4099f1a8, 0xdd263c95, 0x934bb649 | |
278 | decrypting n.zip.enc into out.dec.dd263c95... ok | |
279 | heuristic #1 : 0x00000014 (flags) | |
280 | heuristic #2 : 0x65670000 (00+filename[0,1]) | |
281 | mogelijke oplossing : 0x4099f1a8, 0xfc90bc95, 0xb2fd3649 | |
282 | decrypting n.zip.enc into out.dec.fc90bc95... ok | |
283 | ||
284 | Mensen met een lousy rot13 shirt mogen mij mailen: misschien is het leuk om met ons clubje mogelijkheid tot contact te houden voor wat dan ook. Het idee van het maken van een challenge stond me wel aan namelijk. [op ons t-shirt: eerste regel, meest rechtse "woord"] @ xs4all.nl. |