'[forensic]Winproof/Registry'에 해당되는 글 9건

  1. 2012.04.12 8. Registry File Parser Source code (6)
  2. 2012.04.11 7. UserAssist Analysis ( ROT13 ) (1)
  3. 2012.04.10 6. Value Data Storage
  4. 2012.04.09 5. SubKey List
  5. 2012.04.06 4. NK Record
  6. 2012.04.05 3. Bin Header (2)
  7. 2012.04.04 2. Registry hive structure (1)
  8. 2012.04.03 1.시작하며... (3)
  9. 2010.06.10 Registry Data Structure Viewer (4)

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.11 08:36

7. UserAssist Analysis ( ROT13 )

UserAssist 는 시스템에서 실행되었던 프로그램의 목록과 실행 횟수 

그리고 마지막 실행시간 등의 정보를 가지고 있습니다.


UserAssist 정보를 가지는 Registry 키의 전체 경로는 아래와 같습니다.

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist


레지스트리 키로 접근하면 아래와 같이 2개의 서브 키가 있는 것을 확인할 수 있습니다.

Count 안의 값들이 실행한 프로그램의 목록과 횟수들을 가지고 있다고 보시면 됩니다 :)

( 제가 쓰고 있는 Windows 버전은 Windows7 입니다. )

음? 영문자이긴 한데 뭔가 읽을 수 없는 영어단어가 아닌 듯한 목록입니다.


이것은 key의 이름이 ROT13 으로 변환되어 있기 때문에 그렇습니다. 

여기서 ROT13 이 무엇이냐면, A~M의  값들과 N~Z 의 값들이 1:1 매칭이 되어 변환 되는 암호화 방법입니다.


파일:ROT13 table with example.svg


이미지 출처 위키백과 ROT 13 : http://ko.wikipedia.org/wiki/ROT13


알고리즘이 어렵지 않고 간단한 변환이라,

tr 연산자를 통해 문자열을 쉽게 처리 할 수 있습니다.


http://perldoc.perl.org/functions/tr.html 

1
2
3
4
5
6
7
8
9
use strict;
use warnings;

my $value = 'HELLO';
print $value,"\n";
$value =~ tr/N-ZA-Mn-za-m/A-Za-z/;
print $value,"\n";
$value =~ tr/N-ZA-Mn-za-m/A-Za-z/;
print $value,"\n";

Output:
1
2
3
HELLO
URYYB
HELLO


운영체제에 따라 UserAssist 값이 달라 따로 운영체제별로 코드로 정리해 보죠!!

1
2
3
4
5
# 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}/;


그럼 이전에 짠 코드에서 %vk 값 안에서 키값을 확인했을 때 Value Name을 복호화 시켜 출력하도록 작성하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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";
}

> 복호화 하지 않은 값

>perl rega.pl UserAssist

\UserAssist

LastWrite time: Tue Nov 29 01:22:08 2011

\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}

LastWrite time: Tue Nov 29 01:22:08 2011

        --> Version;REG_DWORD;5


\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\Count

LastWrite time: Tue Apr 10 05:27:20 2012

        --> Zvpebfbsg.Jvaqbjf.TrggvatFgnegrq;REG_BINARY;06 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 1b e0 6f 3d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 00 00 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 05 00 00 00 cf e

8 1e 16 35 ae cc 01 00 00 00 00


> 복호화 된 값

>perl rega.pl UserAssist

\UserAssist

LastWrite time: Tue Nov 29 01:22:08 2011

\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}

LastWrite time: Tue Nov 29 01:22:08 2011

        --> Version;REG_DWORD;5


\UserAssist\{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\Count

LastWrite time: Tue Apr 10 05:27:20 2012

        --> Microsoft.Windows.GettingStarted;REG_BINARY;06 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 1b e0 6f 3d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

 00 00 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 05 00 00 00 cf e

8 1e 16 35 ae cc 01 00 00 00 00


복호화가 잘 되어 출력되는 것을 확인 할 수 있습니다 :)

오늘은 하루 잘 보내시구요~


총선!! 모두 투표합시다~^^/


참고 URL : http://malwarelab.tistory.com/tag/UserAssist


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 1 Comment 1
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.09 20:59

5. SubKey List

Nk Record 밑의 Subkey list 들은 하위 목록들을 나열해주는 역활을 합니다. 

Subkey List 까지 뽑았다면 Registry 구조 복구의 전체적인 밑그림이 그려지며 

파일 구성의 절반 이상이 끝났다고 할 수 있습니다.

결국은 Tree 구조로 되어 있으므로 재귀적으로 함수가 호출이 되는 부분을 주목하여 봐주세요 :)


아!! 월요일입니다!

말이 필요없는 월요일.. 그냥 푹~ 쉬고 싶은 월요일이지만 강좌는 멈추지 않습니다!ㅋ


소스를 잘 짜기 외해서는 기존의 공개된 소스를 어려움 없이 볼 줄 알고 

코드를 짜는 사람의 내면의 고민을 같이 공유 할 수 있는 스킬을 익혀야 합니다.

그러므로 창의적으로 소스를 짜는 능력과, 코드를 세부적으로 분석하는 있는 스킬

두개를 다 골고루 키워야 한다는 것이 저의 생각입니다. ( 너무 기본적인 이야기죠? ㅋㅋ )


그럼 이제 Subkey List를 공부해 볼까요~~


SubKey List는 서브키들의 숫자와 그 값들의 정보를 포함하고 있습니다. 

SubKey List 는  "lf" 와 "lh" 그리고 "ri" 와 "li" 형식으로 2가지 타입이 있습니다.

"lf"와 "lh"는 subkey 들의 주소, Subkey들의 이름 또는 

Subkey들의 문자 체크섬들 중 4자리를 포함하고 있습니다.



위와 같이 "lf" 값 뒤에 하위 Subkey 가 1개 있다는 것을 알 수 있고 

subkey의 주소와 4개의 subkey 체크섬 문자 값을 확인 할 수 있습니다.


1
2
3
4
5
6
7
8
9
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);
		}
	}
}

먼저 parseNKRecord 에서 subkey의 갯수가 0개 이상일 때 노드의 타입을 읽어 올 수 있습니다.

하위의 노드 타입이 lf 나 lf 일 때 하위의 nk record 들의 정보들을 뽑아 올 수 있습니다.

이렇게 뽑은 nk record의 정보를 다시 parseNKRecord 함수를 재귀적으로 호출하여

하위키들을 계속 뽑아 가는 형태입니다.


위에 보시는 것처럼 Parent NK 밑에 Subkey-List 가 존재하고 

그 밑에 Child NK 들이 쭉~ 나열되어 있는 그림이 코드로 구현이 되었습니다.


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

readLfList는 Offset 을 hash 값으로 뽑아오게 되며, 

이 hash 의 갯수가 하위 키의 갯수라는 것을 알 수 있습니다.


두번째로는 타입이 "ri" 일 경우 다른 Subkey들의 리스트에 대한 주소를 가르키고 

"li"일 경우 Subkey의 주소를 나타냅니다.

이 말은 즉! 읽어와야 하는 갯수가 하나인지 여러개인지 차이라 보시면 됩니다 :)


1
2
3
4
5
6
7
8
9
if ($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);
}

parseNKRecord 에서 ri 인지 li 인지에 따라 리스트로 반환해서 하나씩 주소 형식으로 읽거나

바로 아래의 주소를 읽어 오면 되는 코드입니다.


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

readRiList 와 paserLiRecords를 서로 호출하면서 데이터를 구성하는 코드입니다.

이제 오늘 배운 내용을 이전 코드와 함께 아래에 전체 소스를 정리해 보겠습니다.


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를 얻는 방법

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


이제 위의 코드로 Registry Hive 파일을 분석하게 되면 재귀적으로 하위 키를 검색하면서 

데이터가 쭉~~ 뽑히는 것을 확인 할 수 있습니다.


>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

\SAM\SAM\Domains

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

... 중략 

\SAM\SAM\RXACT

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


와우~! 이제 레지스트리값들이 모양새를 갖추기 시작했습니다!

다음장에서는 Value Record를 통해 데이터 값들을 확인하는 방법을 알아 보겠습니다 :)

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 1 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.04 19:10

2. Registry hive structure

Registry Hive 파일을 통해 원본  레지스트리값들을 알아내기 위해서는 먼저 Hive 파일의 구조를 알아야 합니다.

이 장에서는 샘플 Hive 파일을 추출하여 Hex Editor를 통해 값을 확인하고 프로그래밍으로 Hive 파일을 불러와서 

값들을 체크하는 샘플을 만들어 보도록 하겠습니다.


레지스트리 Hive 파일을 분석하기 위해서는 먼저 Hive 파일을 추출해야 합니다.

정확한 레지스트리 Hive 파일을 얻어오기 위해서는 컴퓨터를 종료 한 뒤, 

하드 디스크에 있는 Hive 파일을 추출하여 사용하는게 가장 좋은 방법이지만


하드 디스크를 빼려면 본체도 열어야 하고 나사도 풀어야 하고 ㅡ_ㅠ

보통 힘든일이 아닙니다. (쉬우신 분들도 있겠지만요 ^^;)


그래서 간단하게 샘플 파일을 얻는 방법을 설명해 드리겠습니다.

자신의 현재 컴퓨터에서도 가능합니다.


1. 실행창에서 레지스트리 편집기인 regedit 를 입력합니다.


2. HKEY_LOCAL_MACHINE 의 SAM을 선택하여 내보내기를 누릅니다.

(SAM에 값이 없는 것 같지만 숨겨져 있을뿐 중요한 정보가 많이 담겨져 있습니다)


3. 내보내기를 할 때 파일형식을 레지스트리 하이브파일로 선택해주셔야 합니다!


위와 같이 진행하시면 현재 컴퓨터의 레지스트리 Hive 샘플 파일인 SAM 파일을 얻을 수가 있습니다. +_+


레지스트리 Hive 파일의 전체 구조는 아래와 같습니다. 

첫번째로 Base Block 가 존재하며 'regf'라는 시퀀스를 가지고 있습니다.

이 Base Block은 4096(0x1000)Byte로 이루어져 있으며 

Hive 파일 이름, 첫번째 키 레코드의 주소, 마지막 hbin 파일 주소 등이 담겨 있습니다.

이 블럭에는 hive 파일의 전체적으로 중요한 정보들을 저장하고 있습니다.

Hive 파일의 습니다.


이런건 눈으로 봐야 하는데.. Hex Editor로 파일을 한번 열어 보겠습니다.

처음에 'regf' 보이시죠? 이 시퀀스가 레지스트리 Hive 파일이라는 것을 알려주는 부분입니다.


그리고 4096Byte를 봤을 때 시퀀스 값이 'hbin' 이라는 것도 확인을 할 수 있습니다 :)


레지스트리의 Hive 파일의 스펙은 정확히 공개되지 않았습니다. 

그래서 지속적으로 그 값들을 확인하기 위한 작업들이 이루어지고 있으며

그래도 제일 분석이 잘 된 The WindowsNT Registry File Format.pdf 파일의 맨 마지막장에 보시면 

각 구조에 대한 값들을 자세하게 확인 할 수 있습니다. ( 정확하지 않은 부분은 Unknown 이나 물음표를 넣어놨더군요 ^^;)



앞서 설명드린 Base Block 파일은 위와 같은 정보들을 가지고 있으며, 

이 값들을 perl을 사용하여 추출하여 보도록 하겠습니다.

( timestamp 는 기록이 정확하게 되어 있지 않아 제외 시켰습니다. )


> rega.pl 


use strict;
use warnings;

my $record;
my $offset=0;
my $file = shift || die "You must enter a filename.\n";
open my $reg, '<', $file  or die "cannot open [$file] file: $!";

# Magic Number 
read($reg,$record,4);
my $magic = unpack("A4",$record);
print $magic,"\n";

#root key offset
seek($reg,0x24,0);
read($reg,$record,4);
my $rootkey = unpack("L",$record);
print $rootkey,"\n";

#file name
seek($reg,0x30,0);
read($reg,$record,64);
my $filename = unpack("A*",$record);
printf $filename;


코드의 문법상 어려운 부분은 없을 것입니다. 
unpack 문서는 아래 링크를 참고 하시면 됩니다.

rega.pl 파일을 실행을 시켰을 때 결과값이 아래와 같이 나오면 정상적으로 추출 된 것입니다.

>perl rega.pl SAM

regf

32

\S y s t e m R o o t \ S y s t e m 3 2 \ C o n f i g \ S A M

※ 현재 컴퓨터에서 추출한 샘플 파일일 경우에는 아래 경로가 나오지 않습니다


레지스트리 Hive 파일을 알리는 'regf' 와 root key 값을 나타내는 32(0x20) 그리고 

SAM 파일이 위치한 전체경로를 확인 할 수 있습니다 :)


신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 0 Comment 1
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
2010.06.10 07:52

Registry Data Structure Viewer



자료를 찾다보니 국내에서는 아직 Hive 파일을 이용하여,

레지스트리를 분석한 논문이나 글이 없더군요.


(국내 뿐만 아니라 해외에서도 찾기 어렵습니다ㅡ _ㅠ)




그나마 영어로 된 논문하나를 찾게 되어

이 문서를 보면서 Hive 파일을 분석하는 Tool 을 만들고 있습니다.

(이 문서도 완벽하지는 않네요 ^^;)
 

C# 으로 프로토타입으로 작업한 스샷입니다.




완성단계까지 99.9% 남았네요 ^^;;


도움을 주고 계시는 진원이형 고맙습니다. ㅋ
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
Trackback 2 Comment 4


티스토리 툴바