'registry file paser'에 해당되는 글 1건

  1. 2012.04.12 8. Registry File Parser Source code (6)

Naver Perl Community & Study Cafe


2012.04.12 22:05

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


아래는 원본소스코를 강좌를 쓰기 위해서 재작성했던 코드의 전체소스입니다.


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)  코드를 보면서 

강좌를 다시 시작하겠습니다. ^^/



신고
Trackback 0 Comment 6