Recent Posts
Recent Comments
Link
12-24 00:08
동글동글 라이프
8. Registry File Parser Source code 본문
지금까지 Harlan Carvey님의 Offline Registry File Parser 버전 1.1의 코드를
조금씩 코드를 추가시키며 완성해보는 작업을 하였습니다.
이 코드는 2006년에 작성 되었으며, NT Registry Hive access library 인 ntreg.h 를 참고하여 만들었다고 합니다.
Harlan Carvey님은 다들 아시겠지만, http://windowsir.blogspot.com/ 블로그를 운영하고,
Forensic 책으로 유명한 'Windows Forensic Analysis' 를 쓰신 분입니다.
코드도 직접 짜시고 책까지 쓰셨으니 대박이죠!!
국내에는 '인사이드 윈도우즈 포렌식' 으로 2010년에 번역서가 나왔습니다
저는 이때 알았네요 ^^;;
( 번역서는 나오자말자 친한 동생을 통해 볼 수 있었습니다. ㅋㅋㅋ Link )
아래는 원본소스코를 강좌를 쓰기 위해서 재작성했던 코드의 전체소스입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
| use strict;
use warnings;
my $ADJUST = 0x1004;
#registry Value Type
my %regtypes = (0 => "REG_NONE",
1 => "REG_SZ",
2 => "REG_EXPAND_SZ",
3 => "REG_BINARY",
4 => "REG_DWORD",
5 => "REG_DWORD_BIG_ENDIAN",
6 => "REG_LINK",
7 => "REG_MULTI_SZ",
8 => "REG_RESOURCE_LIST",
9 => "REG_FULL_RESOURCE_DESCRIPTOR",
10 => "REG_RESOURCE_REQUIREMENTS_LIST");
# Special list for translating the UserAssist (ROT-13) key value names
my @WinXPua = qw/{5E6AB780-7743-11CF-A12B-00AA004AE837}
{75048700-EF1F-11D0-9888-006097DEACF9}/;
my @Win7ua = qw/{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}
{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}/;
my $file = shift || die "You must enter a filename.\n";
open my $reg, '<', $file or die "cannot open [$file] file: $!";
my ($node,$offset) = locateRecord($ADJUST);
my $nt = _getNodeType($offset);
if ($nt == 0x6b6e) { # 'nk' record 일 경우 파싱을 시작
parseNkRecords("",$offset);
}
else {
printf "Node not an nk node: 0x%x\n",$nt;
die;
}
close $reg;
# nk 의 root key 값을 찾는 코드
sub locateRecord {
my $offset = shift;
my $record;
seek($reg,$offset,0);
while(1) {
read($reg,$record,4);
my ($tag,$id) = unpack("SS",$record);
if ($tag == 0x6b6e && $id == 0x2c) {
# print "nk record located.\n";
return("nk",$offset);
}
$offset = $offset + 2;
seek($reg,$offset,0);
}
}
# offset으로부터 2byte를 읽어와 unpack 을 사용하여 node의 ID를 얻는 방법
sub _getNodeType {
my $offset = shift;
my $record;
seek($reg,$offset,0);
my $bytes = read($reg,$record,2);
if ($bytes == 2) {
return unpack("S",$record);
}
else {
print "_getNodeType error - only $bytes read.";
return;
}
}
# nk record 들을 파싱하는 부분입니다.
sub parseNkRecords {
my ($name, $offset) = (@_);
my %nk = readNkRecord($offset);
print $name."\\".$nk{keyname}."\n";
print "LastWrite time: ".gmtime(getTime($nk{time1},$nk{time2}))."\n";
=subkey infomation 정보는 출력에서 제외 시켰습니다
print "Number of subkeys : ".$nk{no_subkeys}."\n";
print "Pointer to the subkey-list : ".$nk{ofs_lf}."\n";
print "Number of values : ".$nk{no_values}."\n";
print "Pointer to the value-list for values : ".$nk{ofs_vallist}."\n";
print "Pointer to the SK record : ".$nk{ofs_sk}."\n";
print "Pointer to the class name : ".$nk{ofs_classname}."\n";
print "Key name length : ".$nk{len_name}."\n";
print "Class name length : ".$nk{len_classname}."\n";
=cut
if ($nk{no_values} > 0) {
my @ofs_vallist = readValList(($nk{ofs_vallist} + $ADJUST),$nk{no_values});
foreach my $i (0..(scalar(@ofs_vallist) - 1)) {
my %vk = readVkRecord($ofs_vallist[$i] + $ADJUST);
if ($nk{keyname} eq "Count") {
foreach my $u (@Win7ua) {
if (grep(/$u/,$name)) {
$vk{valname} =~ tr/N-ZA-Mn-za-m/A-Za-z/;
}
}
}
print "\t--> ".$vk{valname}.";".$regtypes{$vk{val_type}}.";".$vk{data}."\n";
}
print "\n";
}
if ($nk{no_subkeys} > 0) {
my $nt = _getNodeType($nk{ofs_lf} + $ADJUST);
if ($nt == 0x666c || $nt == 0x686c) { # lf = 0x666c , lh = 0x686c
my %lf = readLfList($nk{ofs_lf} + $ADJUST);
foreach my $ofs_lf (keys %lf) {
parseNkRecords($name."\\".$nk{keyname},$ofs_lf + $ADJUST);
}
}
elsif ($nt == 0x6972) { # ri = 0x6972
my @ri = readRiList($nk{ofs_lf} + $ADJUST);
foreach (@ri) {
parseLiRecords($name."\\".$nk{keyname},$_ + $ADJUST);
}
}
elsif ($nt == 0x696c) { # li = 0x696c
parseLiRecords($name."\\".$nk{keyname},$nk{ofs_lf} + $ADJUST);
}
else {
printf "***Unrecognized node type : 0x%x\n",$nt;
}
}
}
# nk record를 읽어온 뒤 각각의 변수들로 저장시킨다.
sub readNkRecord {
my $offset = shift;
my $record;
my %nk = ();
seek($reg,$offset,0);
my $bytes = read($reg,$record,76);
if ($bytes == 76) {
my (@recs) = unpack("SSL3LLLLLLLLLL4LSS",$record);
$nk{id} = $recs[0];
$nk{type} = $recs[1];
$nk{time1} = $recs[2];
$nk{time2} = $recs[3];
$nk{time3} = $recs[4];
$nk{no_subkeys} = $recs[6];
$nk{ofs_lf} = $recs[8];
$nk{no_values} = $recs[10];
$nk{ofs_vallist} = $recs[11];
$nk{ofs_sk} = $recs[12];
$nk{ofs_classname} = $recs[13];
$nk{len_name} = $recs[19];
$nk{len_classname} = $recs[20];
# key의 이름을 알아내어 저장한다
seek($reg,$offset + 76,0);
read($reg,$record,$nk{len_name});
$nk{keyname} = $record;
# 여기까지 읽은 전체 바이트의 수는 ($num_bytes + $nk_rec{len_name}) 입니다.
# 다시 말하면 nk의 76byte + nk의 keyname 길이만큼 입니다.
# 이 값들을 활용하기 위해 hash 값을 return 시킵니다.
return %nk;
}
else {
print "readNkRecord bytes read error: ".$bytes;
return;
}
}
# 2개의 4Byte time값을 64-bit NT timestamp 값으로 변환해주는 코드
sub getTime() {
my ($lo,$hi) = (@_);
my $t;
if ($lo == 0 && $hi == 0) {
$t = 0;
} else {
$lo -= 0xd53e8000;
$hi -= 0x019db1de;
$t = int($hi*429.4967296 + $lo/1e7);
};
$t = 0 if ($t < 0);
return $t;
}
# lf/lh 의 주소를 읽어와서 하위 nk Record의 리스트를 뽑는 코드
sub readLfList {
my $offset = shift;
my $record;
my $num_bytes = 4;
seek($reg,$offset,0);
my $bytes = read($reg,$record,$num_bytes);
if ($bytes == $num_bytes) {
my($id, $no_keys) = unpack("SS",$record); #id 는 lf/lh
seek($reg,$offset + $num_bytes,0);
$bytes = read($reg,$record,(2 * 4 * $no_keys));
my $iterations = ($bytes/4);
my $step = 1;
my $temp;
my %lf;
# 4개의 문자를 읽어온다.
foreach my $i (0..($iterations - 1)) {
my $str = substr($record,$i*4,4);
if ($step%2) {
$temp = unpack("L",$str);
}
else {
$lf{$temp} = $str;
}
$step++;
}
return %lf;
}
else {
print "readLfList bytes read error: ".$bytes;
return;
}
}
# ri 의 사이즈만큼 주소를 읽어와 리스트로 반환한다.
sub readRiList {
my $offset = shift;
my $record;
seek($reg,$offset,0);
my $bytes = read($reg,$record,4);
my ($id,$num) = unpack("SS",$record);
seek($reg,$offset + 4,0);
$bytes = read($reg,$record,$num * 4);
return unpack("L*",$record);
}
# li record들의 주소가 nk record 나 subkey 일때 다시 재귀적으로 탐색한다.
sub parseLiRecords {
my ($name,$offset) = (@_);
my @li_list = readRiList($offset);
foreach my $ofs_nk (@li_list) {
my $nt = _getNodeType($ofs_nk + $ADJUST);
if ($nt == 0x6b6e) {
parseNkRecords($name,$ofs_nk + $ADJUST);
}
elsif ($nt == 0x696c) {
parseLiRecords($name,$ofs_nk + $ADJUST);
}
else {
printf "**Unrecognized node type : 0x%x\n",$nt;
}
}
}
# Value Record 의 이름 및 값들을 형식에 따라 읽어온 뒤 해시 값으로 반환
sub readVkRecord {
my $offset = shift;
my $record;
my %vk = ();
seek($reg,$offset,0);
my $bytes = read($reg,$record,20);
if ($bytes == 20) {
my (@recs) = unpack("SSLLLSS",$record);
$vk{val_type} = 0;
$vk{id} = $recs[0];
$vk{len_name} = $recs[1];
$vk{len_data} = $recs[2];
$vk{ofs_data} = $recs[3];
$vk{val_type} = $recs[4];
$vk{flag} = $recs[5];
# value 의 이름을 읽어온다.
if ( $vk{len_name} == 0) {
$vk{valname} = "Default";
#vk{val_type} 값이 잘 못된 값이 들어가기도 한다.
$vk{val_type} = 1; # REG_SZ 로 설정
$vk{data} = 0;
return %vk;
}
else {
seek($reg,$offset + 20,0);
read($reg,$record,$vk{len_name});
$vk{valname} = $record;
}
# 각 valuetype에 따라 데이터를 출력하는 방법을 다르게 하기 위한 작업
if ($vk{len_data} & 0x80000000 || $vk{val_type} == 4) {
# $vk{len_data}가 0x80000000거나 REG_DWORD 일 경우 아래 형식으로 저장
$vk{data} = $vk{ofs_data};
$vk{data} = "" if ($vk{val_type} == 7);
$vk{data} = chr($vk{data}) if ($vk{val_type} == 1);
}
else {
# REG_SZ , REG_EXPAND_SZ , REG_MULTI_SZ 일 시에 ASCII 값으로 변환
$vk{data} = _getValueData($vk{ofs_data} + $ADJUST,$vk{len_data});
$vk{data} = _uniToAscii($vk{data}) if ($vk{val_type} == 1 ||
$vk{val_type} == 2 ||
$vk{val_type} == 7);
}
# REG_BINARY , REG_RESOURCE_LIST , REG_RESOURCE_REQUIREMENTS_LIST 일 시 이진값으로 변환
$vk{data} = _translateBinary( $vk{data}) if ($vk{val_type} == 3 ||
$vk{val_type} == 8 ||
$vk{val_type} == 10);
return %vk;
}
else {
print "readVkRecord bytes read error: ".$bytes;
return;
}
}
#넘겨받은 길이 만큼 데이터를 읽어 값을 반환
sub _getValueData {
my ($offset,$len ) = (@_);
my $record;
seek($reg,$offset,0);
my $bytes = read($reg,$record,$len);
if ($bytes == $len) {
return $record;
}
else {
print "_getValData error: $bytes of $len bytes read.";
return;
}
}
# 유니코드 문자열을 ASCII로 변환
sub _uniToAscii {
my $str = shift;
my $len = length($str);
my $newlen = $len - 1;
my @str2;
my @str1 = split(//,$str,$len);
foreach my $i (0..($len - 1)) {
if ($i % 2) {
# 아스키코드 일경우에는 홀수값이 \00이라 값을 읽을 필요가 없다.
}
else {
push(@str2,$str1[$i]);
}
}
return join('',@str2);
}
# 데이터를 2진 값으로 변환하여 출력
sub _translateBinary {
my $str = unpack("H*",$_[0]);
my $len = length($str);
my @nstr = split(//,$str,$len);
my @list;
foreach (0..($len/2)-1) { # 기존소스에는 -1이 없어 warning 발생
push(@list,$nstr[$_*2].$nstr[($_*2)+1]);
}
return join(' ',@list);
}
# value 리스트의 사이즈를 만큼의 주소를 @ofs_val배열하여 반환한다.
sub readValList {
my ($offset,$num_vals) = (@_);
my $record;
my $bytes_to_read = $num_vals * 4;
seek($reg,$offset,0);
my $bytes = read($reg,$record,$bytes_to_read);
if ($bytes == $bytes_to_read) {
my @ofs_val = unpack("L*",$record);
if (scalar(@ofs_val) == $num_vals) {
return @ofs_val;
}
else {
print "readValList bytes read error: ".$bytes;
return;
}
}
}
|
2006년에 작성된 코드지만, 레지스트리 Hive 파일구조가 변하지 않으므로
시간이 지나도 여전히 좋은 자료라 생각되네요!!
여기까지의 작업은 Registry Hive 파일의 복구하여 데이터를 뽑아내는 작업만 하고 있어,
Forensic의 중요 데이터를 뽑는 작업은 하지 않고 있습니다.
Forensic에 필요한 데이터가 젤 중요한데 그냥 연결만 시켜놓고 끝낼 순 없겠죠??
다음주 부터는 이 다음 버전인 Registry Ripper (Version 2.2) 코드를 보면서
강좌를 다시 시작하겠습니다. ^^/
'개발자 이야기 > [forensic]Winproof' 카테고리의 다른 글
7. UserAssist Analysis ( ROT13 ) (1) | 2012.04.11 |
---|---|
6. Value Data Storage (1) | 2012.04.10 |
5. SubKey List (1) | 2012.04.09 |
4. NK Record (0) | 2012.04.06 |
3. Bin Header (2) | 2012.04.05 |
Comments