View difference between Paste ID: rRbQDp5w and M94q1Fmm
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.