'hive file'에 해당되는 글 3건

  1. 2012.04.09 5. SubKey List
  2. 2012.04.04 2. Registry hive structure (1)
  3. 2010.06.10 Registry Data Structure Viewer (4)

Naver Perl Community & Study Cafe


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.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
2010.06.10 07:52

Registry Data Structure Viewer



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

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


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




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

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

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

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




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


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