'registry hive'에 해당되는 글 5건

  1. 2012.04.12 8. Registry File Parser Source code (6)
  2. 2012.04.10 6. Value Data Storage
  3. 2012.04.06 4. NK Record
  4. 2012.04.05 3. Bin Header (2)
  5. 2012.04.03 1.시작하며... (3)

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

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



신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 6
2012.04.10 20:24

6. Value Data Storage

Value Data Storage는  value-list의 offset을 통해 vk record 의 데이터 구성 형태 입니다.

이전 시간에 설명드린 Subkey 를 통해 폴더를 설정했다면 이제 세부적으로 파일들을 채우는 단계라고 생각하시면 됩니다.

이 장에서는 VK record 와 타입별로 데이터를 출력해보는 샘플 코드를 작성해 보겠습니다. 


이제껏 hive 파일을 통해서 레지스트리 값을 복구 시킬때

전체 Key들을 구성하는 부분을 주로 처리 하였습니다.

이제는 각 Key 들에 들어있는 데이터들을 접근해서 직접적으로 뽑아보는 작업을 해보겠습니다.

(아래 그림에서 오른쪽의 값들을 구성해보는 겁니다 ㅋㅋ)


각각의 레지스트리 키들은 여러개의 Value들을 가질 수 있습니다. 

이런 Value list 들은 subkey 에서 'ri' 같은 Signature를 가지고 있지 않고 

각 Value 들이 위치하는 주소의 값들만 들어 있습니다.

nk Record 에서 Value-List 를 가르키는 주소를 통해 이 Value-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;
		}
	}
}

처음 Value 값을 뽑을 때 Value 값이 하나일지라도 무조건 Value List 를 거쳐야 합니다.

Signature 가 없기 때문에 그렇기도 한 부분이죠 그래서 혹시나 Value List 를 잘 못 읽었을 때에 대한

예외처리도 되어 있는 것을 확인 할 수 있습니다.


Value Record는 'vk'의 Signature 를 가진 구조로 되어 있습니다. 

위에 표를 확인해보면 Value의 이름, 데이터들이 들어 있습니다.

레지스트리에 Value의 값이 없을 경우에는 Name value의 값이 0으로 설정되게 되고

Windows Registry Edtors(레지스트리 편집기) 에서는 Default(기본값) 으로 설정되게 됩니다.

0x000b 에서 Data type이 있는데 이 데이터 타입에 따라 레지스트리 편집기에서 보여줄 때 

문자형태, 숫자, 이진 데이터 등으로  보여줍니다.

다른 타입들은 전부 이진 또는 16진수로 보여지게 됩니다.




Value type 들은 코드상에서 아래와 같이 정의되어 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
#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");

이제 regtypes 들을 통해 아래와 같이 나열이 가능합니다.

hash로 정리해두면 바로 해당값들의 문자열을 쉽게 뽑아내는게 가능하죠 :)

1
2
3
4
5
6
7
8
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);		
		print "\t--> ".$vk{valname}.";".$regtypes{$vk{val_type}}.";".$vk{data}."\n";
	}
	print "\n";
}

readVKRecord를 호출하여 해시값을 뽑아내는 함수는 다음과 같습니다.


# 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);
}

휴.. 소스코드 안의 주석으로 설명을 했습니다!!

원본 소스코드에서 2군데에서 warning이 발생하는 것을 확인했는데 어딘지 찾아보던 중

$vk{val_type} 값이 초기화 되어 있지 않을 경우에 값이 들어가지 않아 생기는 warning 과

_translateBinary 코드에서 배열의 크기보다 더 초과하게 foreach 문을 돌리는 부분을 수정하니

warning 이 사라지더군요 :)


결과값을 확인할 시간입니다.. 두구두구두구...

>perl rega.pl SAM

\SAM

LastWrite time: Sun Jul  4 09:33:18 2010

\SAM\SAM

LastWrite time: Sun Jul  4 09:33:19 2010

        --> C;REG_BINARY;07 00 01 00 00 00 00 00 98 00 00 00 02 00 01 00 01 00 1

4 80 78 00 00 00 88 00 00 00 14 00 00 00 44 00 00 00 02 00 30 00 02 00 00 00 02

c0 14 00 0e 00 05 01 01 01 00 00 00 00 00 01 00 00 00 00 02 c0 14 00 ff ff 1f 00

 01 01 00 00 00 00 00 05 07 00 00 00 02 00 34 00 02 00 00 00 00 00 14 00 31 00 0

2 00 01 01 00 00 00 00 00 01 00 00 00 00 00 00 18 00 3f 00 0f 00 01 02 00 00 00

00 00 05 20 00 00 00 20 02 00 00 01 02 00 00 00 00 00 05 20 00 00 00 20 02 00 00

 01 02 00 00 00 00 00 05 20 00 00 00 20 02 00 00

... 중략

 00 00 00 05 20 00 00 00 20 02 00 00 00 00 24 00 44 00 02 00 01 05 00 00 00 00 0

0 05 15 00 00 00 fd 37 42 40 9b 0b c1 1e 23 5f 63 6b f4 01 00 00 01 02 00 00 00

00 00 05 20 00 00 00 20 02 00 00 01 02 00 00 00 00 00 05 20 00 00 00 20 02 00 00

 41 00 64 00 6d 00 69 00 6e 00 69 00 73 00 74 00 72 00 61 00 74 00 6f 00 72 00 0

0 00 f4 ce e8 d4 30 d1 2f 00 c4 b3 54 ba 78 c7 44 c7 20 00 00 ad ac b9 58 d5 c4

b3 5d b8 20 00 30 ae f8 bc 20 00 1c c8 f5 ac 1c b4 20 00 c4 ac 15 c8 ff ff ff ff

 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff fd 97 40 01 02 00 00 07 00 0

0 00 01 00 01 00 01 00 01 00 d4 71 1f d6 c5 fd 97 40 52 1f eb a9 a1 ea e9 4b 01

00 01 00 01 00 01 00


\SAM\SAM\RXACT

LastWrite time: Sun Jul  4 09:33:18 2010

        --> Default;REG_SZ;0


이것으로 레지스트리의 타입에 따라 데이터들의 형식이 다르게 출력 되는 것을 확인 할 수 있습니다!!!

Value Key 까지 구성했다면 분석을 위한 데이터를 뽑는부분 까지는 모두 완료 되었습니다.

다음장에는 Registry 의 중요한 값들을 구성해보는 코드를 작성해 보겠습니다.

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 0
2012.04.06 23:56

4. NK Record

NK Record 들은 레지스트리 구조를 연결하는데 가장 중요한 역활들을 해주는 Record 입니다.

이 Record는 각 Key 에 따른 하위 목록들의 연결값들을 가지고 있고 최종으로 수정한 시간도 담겨 있습니다.

이 장에서는 NK Record 의 세부적인 값들을 추출하여 나열하는 샘플을 만들어 보겠습니다. 람쥐..^^;

최근 일주일동안 블로그에 열심히 글을 올렸습니다. 

오랜만에 강좌를 적어서 그런지 홍보를 안해서 그런지

조회수는 높은데 댓글이 없군요 ㅡ_ㅠ


글을 적는 재미는 쏠쏠합니다~

피드백이 좀 들어왔으면 하는데.. 

딴지도 환영입니다.( 적당히만 해주시면 ㅡ_ㅠ...)


그러면 시작~~!


Key Records 는 각 Cell 에 대한 사이즈나 정보들을 담고 있습니다. 

첫번째 4Byte 는 사이즈가 담겨 있으며, 두번째 값으로는 "nk" 라는 signature를 가지게 됩니다.

여기 nk가 없다면 이건 Key Records가 아닌겁니다!

nk 키로 부터 시작해서 Subkey 를 연결하는 구조로 되어 있기 때문에,

이 정보들을 소중하게 간직하셔야 합니다 :)


이번에는 코드를 먼저 보면서 설명을 드리겠습니다.


use strict;
use warnings;

my $ADJUST  = 0x1004;	
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를 얻는 방법입니다.
# 이 함수를 하나 만들어 두면 앞으로 각 Record 의 타입을 확인하기 편하죠!

sub _getNodeType {
	my $offset = $_[0];
	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 들을 파싱하는 부분입니다. 
# 파싱한 후 nk 의 값들을 traverse 하는 코드들은 다음장에서 :)

sub parseNkRecords {
	my ($name, $offset) = (@_);
	my %nk = readNkRecord($offset);
	#nk record 의 정보들을 출력합니다. 설명까지 앞에 다 붙였습니다! 
	print $name."\\".$nk{keyname}."\n"; 
	print "LastWrite time: ".gmtime(getTime($nk{time1},$nk{time2}))."\n";
	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";
	# 이후에 Key들을 연결해줄 코드들이 쭈욱~~ 붙게 됩니다.
}


# 위의 표에서 본 값들을 전부 읽어와서 각각의 변수들로 저장시킵니다.
# perl 의 hash를 통해서 하나하나 이름을 붙여 깔끔하게 저장하네요 :)

sub readNkRecord {
	my $offset = shift;
	my $record;
	my %nk = ();
	seek($reg,$offset,0);
	my $bytes = read($reg,$record,76);
	if ($bytes == 76) {
		# unpack의 알흠다운 활용법입니다.
		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 = shift;
	my $hi = shift;
	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;
}

!!! 

코드가 엄청 늘어 났습니다. 

나중에 각각의 키들의 offset 에 따라서 재귀적으로 함수를 호출해야 하기 

때문에 코드를 함수로 빼는 작업이 중요합니다.

위의 코드를 설명을 드리자면 

가장 처음의 nk record 를 접근해서 record의 값을 추출한 뒤 해시에 저장한 후에

그 값 들을 출력해주고 있습니다.


코드 실행의 결과는 아래와 같습니다.

>perl rega.pl SAM

\SAM

LastWrite time: Sun Jul  4 09:33:18 2010

Number of subkeys : 1

Pointer to the subkey-list : 504

Number of values : 0

Pointer to the value-list for values : 4294967295

Pointer to the SK record : 120

Pointer to the class name : 4294967295

Key name length : 3

Class name length : 0

음?? value-lis의 offset의 값들이 말도 안되는 숫자가 나오는 이유는

values가 0개기 때문입니다. Class name의 offset 도 마찬가지구요 :)


forensic 관점에서 연결해주는 offset 이나 각 number 들이 중요하진 않습니다.

여기서 빨간색으로 표시한 key 이름과 LastWrite의 시간이 가장 중요하죠.

나중에 분석을 할 때나 UI 도구을 만들때도 이 데이터들이 중요한 역활을 합니다.


그럼 오늘 강좌는 여기까지 하고.. 

불타는 금요일 + 주말  보내세요~~ :)


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 0
2012.04.05 22:00

3. Bin Header

Hive Bins 은 블록 단위로 이루어져 있으며 각각의 Cell 값을 담고 있습니다. 

즉 세부적인 정보를 가진 큰 틀이라고 보시면 됩니다. 

이 장에서는 Hive Bin Header 를 살펴보고 root key 값을 찾는 샘플을 만들어 보도록 하겠습니다.



Hive Bin들은 일반적으로 4096Byte 단위로 할당이 됩니다. 

하지만   Hive Bin의 크기가 꼭 4096Byte 보다 클 수도 있죠 :)

이전 글에서 'hbin' 값을 확인했었는데 bin size는 1000h(4096)Byte 인 것을 확인 할 수 있습니다.


아래와 같이 Hive Bin Header 밑에는 Cell 들이 존재합니다.


Hive Bin Header 밑에 바로 Cell 들이 쭉~ 연결해서 붙어 있습니다.

하지만 이 Cell 들을 바로 다 읽어 들이는 것이 아니라 연결 되어 있는 값들 위주로 불러 오기 때문에

전체 구조만 파악해 두시면 됩니다 :)


프로그래밍 상에서 가장 먼저 해야 할 것은 Cell의 root key 를 찾는 작업인데

키의 타입이 0x2c나 0xac 가 root 키로 예약 되어 있습니다.

아래 그림을 보시면 hbin Header의 nk 레코드 뒤에 Flags 들이 다른것을 확인할 수 있습니다.


자! 그럼 이제 소스코드를 한번 보겠습니다.


use strict;
use warnings;

my $ADJUST  = 0x1004;	# Global adjustment value (4096 + 4 bytes)

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);
print "offset = $offset";

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);
	}
}

locateRecord 함수는 ADJUST 값을 통해 hbin값으로 접근 한 뒤

4byte 씩 읽어 들이면서 nk(0x6b6e)와 rootkey flag 인 0x2c를 찾는 작업을 수행합니다.


소스코드 상에서는 0xac를 제외하고 0x2c 만 설정되어 있는데

Jolanta Thomassen님의 문서와는 다르게 H. Carvey 님의 소스코드에는 0xac는 설정되지 않아 있더군요.

제가 넣는 것보다는 최대한 소스코드를 유지하려는 생각 때문에 0xac는 설정하지 않았습니다^^;


결과는 아래와 같이 나오네요 :) offset 은 파일에 다르게 나올 수도 있습니다.

>perl rega.pl SAM

nk record located.

offset = 4132


이제 rootkey 를 찾았으니 내일부터는 본격적으로 키를 뽑아보는 과정이 될 것 같네요!! 우히힛(?)

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 2
2012.04.03 16:33

1.시작하며...


안녕하세요 h0ney 입니다!

이번 강좌에 설명할 내용은 레지스트리 Hive 파일 포멧 분석기(Registry Hive File Format Analyzer)  입니다.

사이버 포렌식에서 Registry Hive 파일 분석은 선택이 아니라 필수일 정도로 중요도가 높습니다.

이를 프로그래밍 도구를 만들면서 전체 구조를 파악하며 공부하는 글을 적어보려고 합니다.

프로그래밍 분석에 사용될 언어는 perl 이며 Windows Forensic Analysis의 regpl.pl 을 참고하여 설명드리겠습니다.


보안 공부를 할때 기본적으로 배우는 개념 중 PE 구조 라는 것이 있습니다.

(PE구조란 윈도우 운영체제에서 지원되는 실행 파일의 형식을 뜻하며 실행을 할 수 있는 구조를 가진 파일입니다. )


이런 PE구조를 분석 및 공부를 하려면 

PE구조에 관련된 서적을 열심히 읽어야 하는데

책을 아무리 읽어도 16진수가 난무하는 알 수 없는 값들 때문에

눈꺼플이 무거워지고 꾸벅꾸벅 잠만 쏟아집니다 ^^;;


PE구조 같은 파일구조를 공부하기에 좋은 방법을 소개하자면

첫 번째로는 샘플 파일을 Hex Editor를 통해 직접 값들을 확인하며 공부하는 방법.

두 번째로는 파일에 관련된 도구를 직접 사용해서 내부 구조를 확인하는 방법

세 번째로는 프로그래밍으로 직접 파일구조 분석 도구를 만들어 보는 방법입니다.


Hex Editor 나 도구를 이용해서 구조를 확인하고 넘어가도 상관이 없지만 

도구를 직접 만들어 본다는 것만으로 설레이지 않나요?? ㅎㅎ


좋은 점에 대해서 부연 설명을 드리자면...

1. 분석도구를 만드려면 파일 구조가 온전한지 값이 잘 출력되는지 

    테스트를 하며 만들어야 하기 때문에 자연스럽게  파일구조가 머릿속에서 그려집니다.


2. 책에서 봤던 혹은 알고있는 내용과는 다르게 다양한 변수들이 많이 생기는데

   이런 변수들을 하나하나 해결할 수록 단단한 내공이 쌓여지게 됩니다.


3. 파일의 구조를 생성하는 것에 대한 알고리즘을 알게되어 다른 형식의 파일을 접하더라도

   쉽게 이해할 수 있는 지식도 생기게 되죠!!


도구로 만들고 싶다는 생각이 불쑥불쑥 솟아 나시죠??!!


레지스트리 Hive 파일 분석기를 잘 만들어 두면 레지스트리 값들을 모두 

얻을 수도 있고, 지워진 레지스트리 값도 복구가 가능하기 때문에

많은 분야로 확장 가능할 것이라 생각됩니다.


강좌는 아래와 같이 2가지로 나누어서 진행하려고 합니다.

1. perl을 이용하여 내부 값들을 알아보며 각각의 키와 값들을 뽑아내어 보는 파트 


2. C#을 이용하여 UI를 꾸며 분석도구를 만들어가는 파트


두 파트 모두 도구는 만들어져 있으니 어떻게 설명하느냐가 

중요할 것 같습니다.. 부담이 되네요 ㅡ_ㅠ

모르는 부분도 많으니 피드백 주시면 감사하겠습니다 ^^/


준비물

1. Perl 설치 ( 링크를 참고 하시면 됩니다 ^^ 이럴때 카페 홍보!! )

2. Registry Hive File 문서 ( 문제가 될 시 삭제 하겠습니다.)

The WindowsNT Registry File Format.pdf

FORENSIC ANALYSIS OF UNALLOCATED SPACE.pdf

3. Windows Forensic Analysis의 perl 소스코드

4. Hex Editor - ICY Hexplorer ( 제가 좋아하는 Hex Editor 입니다 )

hex_setup216.exe

5. 끝없는 열정!


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 3


티스토리 툴바