2013년 8월 17일 토요일






문서화 되지 않은 API NTDLL.DLL(유저 모드 서비스)을 통해 익스포트 디렉토리를 조사하고 API 그룹리스트를 찾아본다.


Generic Table API 네이티브 API에 속하며, RTL함수는 런타임 라이브러리 함수로 운영체제와 상호작용 없이 단순히 문자열이나 데이터 처리와 같은 일상적 기능을 요구한다.

RtlNumberGenericTableElement
RtlDeleteElementGenericTable
RtlGetElementGenericTable
RtlEnumerateGenericTable
RtlEnumerateGenericTableLikeADirectory
RtlEnumerateGenericTableWithoutSplaying
RtlInitializeGenericTable
RtlIsGenericTableEmpty
RtlInsertElementGenericTable
RtlLooupElementGenericTable

리버싱은 데이터 분석부터 시작한다. 코드에 의해 관리되는 핵심 데이터 구조체를 찾아야 한다.



RtlInitializeGenericTable
함수 호출 규약은 함수에 파라미터가 어떻게 전달되고, 함수가 반환될 때 스택이 어떻게 정리되는지 정의한다. 윈도우에서는 기본적으로 stdcall이 사용되며 stdcall은 반환전 자체적으로 스택을 정리한다. 함수의 종료는 RET명령이다. RET명령의 오퍼랜드 값만큼 스택을 정리하고 반환한다. 함수가 반환하기 전에 스택을 정리한다는 것은 cdecl함수가 아님을 뜻한다 cdecl함수는 함수 호출자가 스택 정리를 담당한다.

_fastcall함수는 ECX와 EDX 레지스터를 이용해 파라미터를 전달받으며, ECXMEDX레지스터의 값을 이용하지 않고 함수의 시작부분에서 해당 레지스터를 초기화한다. 

C++함수는 클래스 이름과 함수에 전달되는 각 파라미터의 타입에 따라 내부적으로 이름이 항상 변경된다.

7C921A39 MOV EDI, EDI
7C921A3B PUSH EBP
7C600A3C MOV EBP, ESP
//EBP스택 프레임 사용 EBP레지스터의 현재 값을 스택에 저장하고 EBP레지스터에 EBP의 값을 저장한다.
//일반적 함수는 지역변수를 저장공간 확보를 위해 ESP 레지스터 값을 지역변수 저장에 필요한 바이트 수만큼 빼는 방법을 이용한다.
7C921A3E MOV EAX, DWORD PTS SS:[EBP+8]
//EBP+8위치에 저장
7C921A41 XOR EDX, EDX
//EDX레지스터 XOR연산 수행, 레지스터값 0으로 만듬
7C921A43 LEA EXC, DWORD PTR DS:[EAX+4]
//LEA산술명령 주소 계산을 위해 사용

cf)스택 주소 앞의 SS:는 SS(stack segment)를 이용해서 해당 주소를 읽는다.

7C921A46 MOV DWORD PTR DS:[EAX],EDX 
//레지스터로 구조체 접근하며, 구조체의 첫번째 멤버값을 0으로 설정
7C921A48 MOV DWORD PTR DS:[EXC+4], ECX
//구조체의 세번째 멤버의 값을 두 번째 멤버의 주소(EAX+4)로 설정
7C921A4B MOV DWORD PTR DS:[ECX],ECX
//구조체의 두 번째 멤버도 동일한 주소로 설정
4C921A4D MOV DWORD PTR DS:[EAX+C],ECX
//구조체의 네 번째 멤버도 동일한 주소로 설정 


함수에 전달되는 첫번째 파라미터는 어떤 데이터 구조체에 대한 포인터이며, 그 구조체는 함수안에서 초기화 된다. 함수는 데이터 구조체에 대한 포인터로 EAX와 ECX레지스터를 사용한다. 구조체의 어떤 멤버는 EAX레지스터를 이용해서 접근하고 그 외의 멤버는 ECX 레지스터를 통해 접근하다.

위 연산의 결과는 포인터로 판명된 세 멤버는 일반적이지 않은 형태로 초기화 됐으며, 모두 두 번째 멤버의 주소로 초기화 되었다. 이는 각 멤버는 세 포인터 그룹에 대한 포인터 라는 것이다.



RtlNumberGenericTableElements

7C923FD2 PUSH EBP
7C923FD3 MOV EBP, ESP
7C923FD5 MOV EAX, DWORD PTR [EBP+8]
7C923FD8 MOV EAX, DWORD PTR [EAX]+14
7C923FDB POP EBP 7C923FDC

RET 4 함수는 단순히 가르키는 포인터로부터 값을 가져오는 작업을 수행하며, 구조체 안의 오프셋은 14를 반환한다.


RtllsGenericTableEmpty 

7C92715B PUSH EBP
7C92715C MOV EBP, ESP
7C92715E MOV ECX, DWORD PTR [EBP+8]
7C927161 XOR EAX, EAX
7C927163 CMP DWORD PTR [ECX],EAX
7C927165 SETE AL


 ECX레지스터에 첫번째 파라미터 값을 저장하고 EAX를 0으로 설정 한다. 첫 번째 멤버의 값을 0으로 설정하고, 첫번째 멤버의 값과 EAX 레지스터 값을 비교한다. 두 값이 동일하면 SETE명령을 이용해 AL값을 1로 설정한다.



셋업과 초기화 

7C9624E3 MOV ECX,DWORD PTR [EBP+8]
4C9624E6 MOV EDX,DWORD PTR [ECX+14]


RtlGetElementGenericTable은 스택 프레임 셋업과정부터 시작한다. ESP 레지스터 대신 EBP 레지스터를 이용하여 해당 함수의 파라미터에 접근한다. 모든 루트 테이블 데이터 구조체를 첫 번째 파라미터로 전달하고 ECX레지스터에는 루트 테이블 포인터가 저장되며 해당 구조체의 오프셋 +14의 값이 EDX 레지스터에 저장된다

7C9624EC PUSH EBX
7C9624ED PUSH ESI
7C9624EE MOV ESI,DWORD PTR [ECX+10]
7C9624F1 PUSH EDI
//오프셋+10의 값을 ESI레지스터에 저장하고 그 값 사용을 위해 PUSH
7C9624F2 MOV EDI,DWORD PTR [EBP+C]
//EDI레지스터에 EBP+C의 값이 저장
7C9624F5 CMP EDI,-1
//함수로 전달된 두 번째 파라미터를 EDI레지스터에 저장 후 -1과 비교
//CMP명령 다음에 바로 조건분기명령이 없는 것은 최적화를 위해 CPU가 변경한 것이다.
7C9624F8 LEA EBX,DWORD PTR [EDX+1]
//EDI레지스터의 값을 1증가시켜 값을 EBX에 저장한다
7C9624FB JE SHORT ntdll.7C962559
7C9624FD CMP EBX,EDX
//EBX와 EDX를 비교하여 EBX값이 더 크면 ntdll.7C962559로 점프
7C9624FF JA SHORT ntdll.7C962559
//JA명령이 사용된것은 부호없는 정수, 부호가있따면 JG사용됨


ntdll.7C962559주소가 두번 사용된것은 소스상에서 복합적인 조건을 검사하는 단일 조건문의 결과라는 뜻이다. 또한 EBX에는 구조체 안의 오프셋 +14값이 저장됐는데 그 값은 테이블의 전체 엔트리 개수를 나타낸다.따라서 마지막 두 명령은 단순히 입력된 인덱스 번호를 전체 엔트리 개수와 비교하여 입력된 인덱스 번호가 유효한 것인지를 체크하는 것이다. 

EBX레지스터가 사용된 것을 통해 함수내 반복적으로 사용됨을 알수있다. 컴파일러는 지역 변수를 저장하기 위해서 스택을 사용하지 않고 EBX레지스터를 사용했다.

7C962501 CMP ESI,EBX
7C962503 JE SHORT ntdll.7C962554
7C962505 JBE SHORT ntdLL.7C96252B
7C962507 MOV EDX,ESI
7C962509 SHR EDX,1
7C96250B CMP EBX, EDX
7C96250D JBE SHORT ntdll.7C96251B
7C96250F SUB ESI,EBX
7C962511 JE SHORT ntdll.7C96254E 

EBX 레지스터 값(오프셋 +10에 저장됨)
오프셋+10은 인덱스 포인터
ESI==EBX면 ntdll.7C962554위치로 점프하고
ESI<=EBX이면 ntdll.7C96252B위치로 점프한다


[ntdll.7C962554위치코드]
7C962554 ADD EAX,0C
7C962557 JMP SHORT ntdll.7C96255B

EAX = EAX + 12 수행 후 ntdll.7C96255B위치로 점프한다



중간요약
- 루트 데이터 구조체 안에 3개의 포인터가 있다.
- 세 포인터 그룹은 초기화 되어있다.
- 포인터 중 하나에 12를 더해서 반환한다 
- 두 개의 파라미터, 하나는 테이블 데이터 구조체에 대한 포인터이고 하나는 테이블에 대한 인덱스 번호이다


로직과 구조


정보를 얻어내기 어려운 곳으로 분기하는 경우에는 코드의 로직과 구조를 생각하고 모든 조건 분기의 위치를 논리적으로 배치하려고 노력해야 한다.

ESI와 EBX의 비교문은 원래 소스코드에서 if(ESI!=EBX)이다
JE명령으로 점프하는 위치의 코드는 조건문 블록 바로 다음에 위치하고 있다.
ESI와EBX를 비교해서 if ESI<=EBX이면 ntdll.7C96252B는 if(ESI>EBX)

cf) 조건 점프 명령과 그 조건 점프 명령에 의해서 건너뛰어지는 코드 블록의 끝에 JMP명령이 있는 구조라면 else-if이다 

7C962501 CMP ESI,EBX 에서 비교연산은 빼기 연산과 동일한데, 비교 연산은 비교를 수행하기 위해 빼기 연산 수행 후 그 결과 값을 무시하고 단지 플래그에만 영향을 준다.


검색루프1
7C962513 EDC ESI
7C962514 MOV EAX,DWORD PTR [EAX+4]
7C962517 JNZ SHORT ntdll.7C962513
7C962519 JMP SHORT ntdll.7C96254E 

위 코드는 반복문임을 알수있다. JNZ는 ESI!=0이면 계속해서 ntdll.7C962513위치로 점프함을 명령한다. 또한 EAX에 계속해서 오프셋 +4의 값을 저장하고 저장되는 값이 포인터이므로 리스트 구조임을 알 수 있다.

cf) ESI<EBX인지 이중으로 체크해야 한다. ESI<=EBX 경우 반복문에 진입하면 ESI 음수가 된다. 이로인해 잘못된 포인터를 반환하게 되어 에러를 발생 레지스터 값이 0 때까지 수행한다. 32BIT컴퓨터에선 4294967296 수행될 것이다.

[ntdll.7C96254E위치 코드]7C96254E MOV DWORD PTR [ECX+C],EAX
7C962551 MOV DWORD PTR [ECX+10], EBX

오프셋+C 찾아낸 엔트리에 대한 포인터가 저장되고 오프셋+10에는 엔트리에 대한 인덱스가 저장된다. 함수가 호출될 때마다 원하는 엔트리를 찾기 위해 반복적으로 수행하는 작업을 최소한으로 줄이기 위함이다.


검색루프2if EDI > EDX 경우 실행되는 ntdll.7C962541

7C962541 TEST EDX,EDX
7C962543 LEA EAX,DWORD PTR [ECX+4]
7C962546 JE SHORT ntdll.7C96254E
7C962548 DEC EDX
7C962549 MOV EAX,DWORD PTR [EAX+4]
7C96254C JNZ SHORT ntdll.7C962548

EDX!=0인지 체크하고 루트 테이블 데이터 구조체의 오프셋 +4 가리키는 엔트리를 시작으로 반복문을 수행한다.


검색루프3 조건문에 해당하지 않는다면 EDI < EDX 경우, 오프셋 +C 캐싱된 엔트리부터 시작해서 인덱스가 증가되는 방향으로 진행하는 코드
7C962513 DEC ESI
7C962514 MOV EAX,DWORD PTR [EAX+4]
7C962517 JNZ SHORT ntdll.7C962513
7C962519 JMP SHORT ntdll.7C96254E

오프셋 +0 이용했는데, 링크드 리스트구조라는 것을 의미한다. 오프셋 +0에는 다음 NextElement 대한 포인터가 저장되고 오프셋 +4에는 PrevElement 대한 포인터가 저장되는 것이다.


검색 루프4 인덱스 번호가 LastIndexFound/2 결과보다 작을 실행되는 코드

7C96251B TEST EBX, EBX
7C96251D LEA EAX,DWORD PTR [ECX+4]
7C962520 JE SHORT ntdll.7C96254E
7C962522 MOV EDX,EBX
7C962524 DEC EDX
7C962525 MOV EAX,DWORD PTR [EAX]
7C962527 JNZ SHORT ntdll.7C962524
7C962529 JMP SHORT ntdll.7C96254E

마지막 엔트리 오프셋+4부터 시작한 다음 엔트리 헤더의 오프셋 +0 이용해 반복문을 시작한다 , 오프셋 +4 마지막 엔트리가 아니라 환형 링크드 리스트이다.


현재까지의 결과


RtlGetElemntGenericTable()소스코드

0 개의 댓글:

댓글 쓰기