'registry'에 해당되는 글 10건

  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.05 3. Bin Header (2)
  6. 2012.04.04 2. Registry hive structure (1)
  7. 2012.04.03 1.시작하며... (3)
  8. 2010.07.28 Detail (Registry Analysis) (1)
  9. 2010.07.27 Architecture (1)
  10. 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)  코드를 보면서 

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



신고
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


신고
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 의 중요한 값들을 구성해보는 코드를 작성해 보겠습니다.

신고
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를 통해 데이터 값들을 확인하는 방법을 알아 보겠습니다 :)

신고
Trackback 1 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 를 찾았으니 내일부터는 본격적으로 키를 뽑아보는 과정이 될 것 같네요!! 우히힛(?)

신고
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 파일이 위치한 전체경로를 확인 할 수 있습니다 :)


신고
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. 끝없는 열정!


신고
Trackback 0 Comment 3
2010.07.28 04:09

Detail (Registry Analysis)


레지스트리 정보에 관한 UI 입니다.


왼쪽에는 메인 정보를 나타내는 트리 형식으로 된 메뉴가 존재하며

레지스트리에 분석된 파일에 따라 이 메뉴들이 활성화 됩니다.

총 5개의 카테고리로 구성되어 있고,

첫번째 카테고리는 레지스트리 하이브 정보를 알려줍니다.

레지스트리를 분석하기 위한 파일에 관련된 정보들을 출력 해주며,

각 파일에 따른 하이브 파일 구조도 보기 쉽도록 출력됩니다.


두번째로는 카테고리는 레지스트리를 분석하여 하드웨어의 정보들을

장치관리자와 동일한 형식으로 나열해줍니다.

하드웨어 정보뿐만 아닌 사용자의 정보에도 신경을 더 쓸 계획입니다.



세번째 카테고리인 파일은 포렌식 분석에서

가장 중요한 역활인 사용자와 관련된 파일들을 나열하는 탭입니다.

최근 접근한 파일, 최근 저장된 파일, Explorer에서 열어본 파일 등

3가지 카테고리로 이루어져 있으며,

각 파일에 따른 확장자에 따라 파일들을 확인할 수 있습니다.




세번째 카테고리는 정보에 관련된 카테고리 입니다.

이 카테고리에서는 접근했던 Web Site, 설치된 소프트웨어, 실행되었던 최근 프로그램 으로 나눠집니다.

레지스트리에서 접근했던 Web Site 의 정보들을 얻어 낼 수 있습니다.

이를 바탕으로 사이트를 한눈에 볼 수 있도록 나열하는 탭도 구성하였습니다.



설치된 소프트웨어는 인스톨 되었던

모든 프로그램의 정보들을 가지고 옵니다.

프로그램의 설치 날짜를 통하여

언제 어떤 프로그램이 설치 되었는지를 확인 할 수 있습니다.



실행되었던 최근 프로그램도 알 수가 있는데,

실행파일의 정보가 ROT13 으로 암호화가 되어있습니다.

이것을 프로그래밍으로 복호화 시켜 정보를 확인 할 수 있습니다.



다섯번 째 카테고리인 로그는

USB 매체를 사용한 흔적에 대하여 알려줍니다.

어떤 매체가 접근을 했는지 알려주며, 자세한 정보를 확인하고 싶을 시

더블클릭을 하면 세부정보도 출력이 되도록 구성하였습니다.



기본적으로 제공하는 레지스트리 분석 트리도 제공되며,

윈도우에서 제공되는 레지스트리 편집기와 동일하게 제작하여

사용자가 어색함이 없도록 보여줍니다.

실제 레지스트리 편집기와 다른점이 있다면

각 레지스트리가 어떤 역활을 하는지 부가적인 설명이 출력됩니다. 




레지스트리 뷰어의 전체 구상도입니다.

각 탭들은 독립된 Form으로 되어 있어 도킹이 가능하며

사용자가 선택한 내용들을 확인할 수 있고,

자유로운 변형이 가능하여, 사용자의 기호에 맞도록 분석이 가능합니다.
신고
Trackback 0 Comment 1
2010.07.27 02:41

Architecture


전체 시스템에 관한 Architecture 입니다.



하드웨어에 저장 되어 있는 데이터를 추출하여

사용자 정보, 시스템 정보 , 하드웨어 정보 등을 얻어온 뒤

이 정보들을 보기쉽게 나열해 주며 유용한 정보를 모아서

사용자 분석을 해주는 시스템입니다.






윈도우 XP 운영체제에서 닷넷 프레임워크 3.5 C# 으로 제작이 됩니다.


디지털 포렌식 정보수사 과정은 아래와 같이 4가지로 이루어 집니다.

1. Assessment(평가)
2. Acquisition(수집)
3. Investigation(조사)
4. Document(문서화 및 보고)

이 수사과정에 따라 표를 만들어 보았으며,

데이터를 수집시에 무결성, 저장 매체 이미징 기술, 정보 수집 기술을 바탕으로
 
데이터를 수집해 와야 합니다.


수집한 데이터를 바탕으로 복구기술을 거쳐, 필요한 정보들을 뽑아내어

Registry Hive , Web Log, System Log 의 분류에 따라 데이터를

분류하여 분석해 줍니다.

그리고 이 3 카테고리의 분석된 자료들을 통합하여 보고서를 작성합니다.



레지스트리에 관한 정보는 사용자 계정, 컴퓨터의 하드웨어 및 어플리케이션에 대한

설정 정보를 계층적으로 데이터베이스화 되어 있습니다.


하지만 컴퓨터가 종료된 시점에서 레지스트리 정보를 얻어내기란 쉽지 않는데

그 이유는 레지스트리에 관련된 파일(Hive File)들을 직접 분석해야 하기 때문입니다.


위의 그림과 같이 레지스트리와 관련된 레지스트리 하이브 파일들을 추출하는 과정이며,
 
분석을 통하여 실제 레지스트리와 유사한 Tree 구조를 만들어 줍니다.

그리고 레지스트리 구조만 보여주는 것만이 아닌,

레지스트리 안의 세부적인 정보를 쉽게 알 수 있도록

Registry Viewer 도 제공됩니다.



웹 브라우저에 관련된 생성 파일들은

사용자의 흔적 및 패턴을 찾기 위한 매우 중요한 자료들입니다.

하지만 웹 브라우저 생성 파일에는 많은 데이터가 존재하기 때문에

용의자의 행위를 규명하는데 쉽지가 않습니다.


위의 PPT 에서는 사용자가 가장 많이 사용하는

웹사이트( Internet explorer, FireFox , Chrome )에서

각각의 로그파일을 분석하여

URL , 검색어 , 접속시간, 방문횟수 등을 추출 및 분석이 가능합니다.


신속하고 효율적인 수사를 위해서는 방대한 웹 데이터들을

직관적으로 분석하기 위한 툴을 제작을 해야합니다.




로컬 파일 분석은 용의자의 흔적, 패턴 및 당시 상황을 찾기 위한 매우 중요한

디지털 포렌식 수사 방법입니다.

하지만 조사 대상인 로컬 파일에는 무수히 많은 정보가 존재하며

그 종류가 다양하기 때문에 수사의 어려움이 커서 이를 체계적이고

효과적으로 보여주기 위해서는 적절한 필터링과 분석을 통하여

검사관에게 제시하여야 합니다.


로컬상에서 비휘발성 데이터 정보로는 레지스트리나, 웹로그를 제외하고도 운영체제에서

자체적으로 로깅하는 부분, 혹은 남아있는 데이터들을 이용할 수 있습니다.


그 이외에 용의자의 컴퓨터 시간의 조작여부, 부팅 및 종료시간,

파일 생성날짜, 임의로 숨겨진 데이터 ,

시스템이 일으키는 에러사항 등의 정보를 제공합니다.



Architecture 끝 :)

신고
Trackback 0 Comment 1
2010.06.10 07:52

Registry Data Structure Viewer



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

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


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




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

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

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

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




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


도움을 주고 계시는 진원이형 고맙습니다. ㅋ
신고
Trackback 2 Comment 4