- 64비트 CPU

-> IA-64 : Intel과 HP에서 제작한 64비트 CPU(간접적인 x86하위호환)

-> x64 : AMD64와 Intel64 통합하여 말함



- 64비트 OS

-> 32비트 MS Windows : ILP32 데이터 모델 (Int,Long,Pointer 32비트)

-> 64비트 MS Windows : LLP64 데이터 모델 (LongLong, Pointer 64비트)

-> 64비트 UNIX : LP64 데이터 모델 (Long, Pointer 64비트)



- WOW64(Windows On Windows 64)

-> 기존 32비트 응용 프로그램을 64비트 OS에서 실행시켜주는 메커니즘

-> 32비트 응용프로그램을 kernel32.dll과 ntdll.dll(32비트)를 로딩하는데 중간에서 'WOW64'가 ntdll.dll(32비트)의 요청(API 호출)을 ntdll.dll(64비트)로 리다이렉션 해준다.



- 폴더구조 

-> System32 : 64비트 환경에서도 'System32' 라는 이름을 가진다. 32비트 하위 호환을 위하여 'SysWow64' 폴더를 제공한다.

-> System32 : 64비트 시스템 설정파일

-> System64 : 32비트 시스템 설정파일


-> 32비트 프로그램에서 GetSystemDirectory()를 호출하면 폴더이름은 'System32'가 리턴되고 폴더 내용은 'SysWOW64'와 같다. WOW64가 중간에서 API 호출을 가로채서 조작한 결과이다. (이러한 방법때문에 32비트 프로그램이 문제 없이 잘 동작 할 수 있다.)



- 레지스트리

-> 만약 32비트 프로세스에서 HKLM\SOFTWARE 아래의 키를 요청하면 'WOW64'가 32비트 용 HKLM\SOFTWARE\Wow6423Node 키로 리다이렉션 해버린다.



-범용 레지스터

-> x64에서 범용레지스털의 크기는 64비트로 확장 개수도 18개로 늘어남.



- CALL/JMP Instruction

-> FF15 XXXXXXXX -> XXXXXXXX 주소는 IAT 영역의 한 곳을 가르키는 '절대주소(VA)' 이다.

-> x64에서는 XXXXXXXX가 절대주소가 아니라 상대주소로 해석된다.

-> 절대주소로 계산법 Address + 상대주소 + 현재명령어의 길이(6)


- 함수 호출 규약

-> 32비트의 함수 호출 규약에는 cdecl, stdcall, fastcall 등이 있지만 64비트에선느 '변형된 fastcall' 하나로 통일

-> 함수파라미터 4개까지 레지스터에 저장하여 전달하는 것이 특징

-> 5번째부터는 스택에 저장시켜 전달

-> 함수가 리턴할때 스택정리는 Caller에서 정리

-> 첫4개의 파라미터에 대한 공간을 예약해 놓는다


(1st 파라미터 -> RCX(정수), XMM0(실수) )

(2nd 파라미터 -> RDX(정수), XMM1(실수) )

(3rd 파라미터 -> R8(정수), XMM2(실수) )

(4th 파라미터 -> R9(정수), XMM3(실수) )



- PE 32+(PE+, PE64)

-> IMAGE_NT_HEADERS 구조체

-> PE32+ : IMAGE_NT_HEADERS64 구조체 사용

-> PE32 : IMAGE_NT_HEADERS32 구조체 사용

-> 3번째 멤머가 IMAGE_OPTIONAL_HEADER32와 64이다.



- IMAGE_FILE_HEADER 구조체

-> Machine 값이 변경 (PE32: 014C, x64용 PE32+: 8664, IA-64용 PE+: 0200)



- IMAGE_OPTIONAL_HEADER 구조체 (가장 많이 바뀜)

-> Magic : (PE32: 010B, PE32+:020B) PE로더는 이 값을 확인하여 IMAGE_OPTIONAL_HAEDER 구조체가 32비트 64비트 인지 확인한다

-> BaseOfData 제거됨

-> ImageBase 자료형이 ULONGLONG(8바이트로)로 변경 -> 프로세스의 가상 메모리에 대응하기 위함



- 스택&힙

-> 자료형이 ULONGLONG(8바이트)로 변경



- IMAGE_THUNK_DATA 구조체 

-> IMAGE_IMPORT_DESCRIPTOR 구조체의 OriginalFirstThunk(INT)와 FirstThunk(IAT) 멤버의 값

-> IMAGE_THUNK_DATA 구조체 리스트

-> 8바이트로 변경


- IMAGE_TLS_DIRECTORY 구조체

-> StartAddressOfRawData, EndAddressOfRawData, AddressOfIndex, AddressOfCallBack 8바이트로 변경










출처 : http://zack-textcube.blogspot.com/2010/04/2.html


이번엔 프로세스를 숨기는 방법에 대해 말씀드리려고 합니다. 

윈도우에는 유저영역과 커널영역이 있습니다. 

SDK를 이용해서 만들어지는 모든 응용 프로그램은 유저 영역에서만 

다루어집니다. 

아니, 윈도우에서는 응용 프로그램이 커널 영역을 건드리는 것을 막아 놓았습니다. 

사실, 핸들이라는 것을 통해서 응용 프로그램은 간접적으로 커널 영역을 건드리는데요, 

커널을 만지지 않고서는 아무 작업도 할 수 없기 때문에 

윈도우가 고안한 방법일 겁니다. 

커널을 직접 건드리기 위해선 DDK라는 것을 통해 시스템 프로그램 내지는 

드라이버(확장자가 .sys입니다)를 만드는게 보통인데요,

제가 설명하고자 하는 방법은 SDK만을 이용한 방법입니다. 

프로세스를 숨기는 기본적인 원리는 이렇습니다. 

윈도우에는 커널영역이 있다고 했죠?

그리고 그 영역에 접근하기 위한 핸들이 있다고 했습니다. 

각각의 핸들에는 대응되는 오브젝트가 있습니다. 

예를 들어 윈도우 핸들에는 윈도우 정보를 담고 있는 구조체가, 

프로세스 핸들에는 프로세스 정보를 담고 있는 구조체가 있습니다. 

이 프로세스의 정보를 담은 구조체의 이름은 EPROCESS입니다. 

EPROCESS에는 엄청나게 많은 멤버가 있는데요, 그 중 이 테마에 중요한 것은

ActiveProcessLinks라는 멤버 하나 뿐입니다. 

이름에서 대략 눈치채셨겠지만, 프로세스들은 연결리스트 구조로 연결되어있습니다. 

때문에 목표하는 프로세스를 연결리스트에서 끊기만 하면 감쪽같이 

목록에서 사라지게 되지요. 

혹시 CPU 사용 권한을 잃게 될 까 걱정하지 않아도 됩니다. 왜냐면, 

작업은 쓰레드를 기반으로 이루어지기 때문에, 프로세스는 이름일 뿐입니다. 

윈도우즈의 버전에 따라 ActiveProcessLinks의 오프셋값이 다른데요, 

XP의 경우 구조체의 시작 번지로 부터 0x088만큼 떨어진(오프셋된) 위치에 

ActiveProcessLinks가 있습니다. 이 멤버의 자료형은 LIST_ENTRY인데, 이는 

SDK플랫폼에도 정의가 되어있는 구조체로, 다음 두 멤버를 가집니다. 

PLIST_ENTRY Flink;
PLIST_ENTRY Blink;

그러므로, 
ActiveProcessLinks.Filnk->Blink = ActiveProcessLinks.Blink;
ActiveProcessLinks.Bilnk->Flink = ActiveProcessLinks.Flink;
의 작업을 거쳐주면 되는 겁니다. 

그럼 문제는 EPROCESS의 주소를 어떻게 알아내는가 인데요..

바로 여기에 여러가지 테크닉이 존재합니다. 

어떤 방법을 사용하든지 Native API를 사용하게 될 텐데요, 

이는 SDK에 정의되어있지 않으므로, GetProcAddress를 통해 직접 주소를 얻어내야 합니다. 

제가 설명할 방법에 쓰이는 API함수는 다음 두 개입니다. 

NTSTATUS __stdcall ZwQuerySystemInformation(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL );

NTSTATUS __stdcall ZwSystemDebugControl(IN SYSDBG_COMMAND SysDbgChunks, IN OUT PVOID pQueryBuff, DWORD dwSize, DWORD, DWORD, NTSTATUS *pResult);

NTSTATUS는 DDK에서 쓰이는 자료형으로, SDK에서 사용하려면 LONG형을 typedef해야 합니다. 

(아시는 분은 알겠지만, 참고로 예기해 드립니다. IN, OUT, OPTIONAL은 아무 의미없는 #define으로, 인자가 입력인지, 출력인지, 그리고 사용하지 않으므로 NULL을 넣어도 되는가 등을 예기해주는 겁니다)

SYSTEM_INFORMATION_CLASS 는 enum으로 정의된 자료형으로, 여러가지 값이 

정의되어 있습니다. 어떤 분이 찾아내셨는지는 모르겠지만;;

그 목록에 나와있지 않은 값들 중에서 16을 넣게 되면, 재미있는 일이 벌어지는데요, 

이걸 이용할 것입니다. 사용할 값은 16뿐이므로 SYSTEM_INFORMATION_CLASS를 

다음과 같이 정의해서 사용하면 됩니다. 

typedef enum _SYSTEM_INFORMATION_CLASS
{
SystemHandleInformation = 16
} SYSTEM_INFORMATION_CLASS;

너무 길어지네요;; 2부에서 계속합니다.

 

 

SystemHandleInformation이란 이름에서 알 수 있듯, 

16은 시스템 상에 로드되어있는 모든 핸들에 대한 정보를 얻어오게 합니다. 

그 정보는 다음과 같은 구조체의 배열에 저장됩니다. 

typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

눈여겨 보아야 할 것은 첫번째, 두번째, 다섯번째 인자인데요, 

첫번째 인자는 이 핸들을 소유한 프로세스의 아이디입니다. 두번째 인자는 

이 핸들의 타입인데요, 핸들에는 여러 종류가 있으니 이를 구분해 주는 겁니다. 

윈도우 핸들, 쓰레드 핸들 등 많은 종류가 있지만, 프로세스는 5번입니다. 

즉, 이 핸들이 우리가 찾고자 하는 프로세스인지 아닌지 조사하려면, 

우선 첫번째 인자와 찾고자 하는 프로세스의 아이디를 조사하고, 

두번째 인자가 5인지를 조사하면 되는 겁니다. 

그리고 대망의 다섯번째 인자는 바로 EPROCESS구조체의 주소입니다. 

이제 ZwQuerySystemInformation의 각 인자에 대해 설명드리겠습니다. 

첫번째 인자는 어떤 종류의 정보를 얻어올 것인가로, 16을 입력할 경우 시스템 핸들 정보를

얻어온다고 했습니다. 

두번째 인자는 정보를 입력받을 버퍼의 포인터입니다. 

세번째 인자는 버퍼의 크기입니다. 

네번째 인자는 정보의 크기입니다. 

시스템 상에 핸들이 얼마나 많은지 모르기 때문에, 적당한 버퍼의 크기를 알 수가 없습니다. 

그래서 1바이트 부터 시작해서 계속 2씩 곱해가면서 적당한 사이즈를 찾는데요, 

버퍼의 크기가 부적절한 경우 ZwQuerySystemInformation은 

STATUS_INFO_LENGTH_MISMATCH라는 값을 반환합니다. 

이 값 역시 DDK에서 사용하는 매크로로, ((NTSTATUS)0xC0000004L)와 같이 정의하면 됩니다. 

사이즈가 적당치 않은 경우는 버퍼를 free하고 크기를 두배로 늘린 뒤 다시 시도하는 

반복문을 돌려서, 적당한 크기를 찾습니다. 

크기가 충분해서 Query Information이 성공하면, 

ZwQuerySystemInformation은 0이상의 값을 반환합니다. 

네번째 인자는 OPTIONAL이므로 그냥 0을 주면 됩니다. 

아무튼 이렇게 해서 얻어낸 정보의 앞 4바이트는 배열의 인자 개수를 나타냅니다. 

다시말하면 시스템상에 존재하는 핸들의 숫자입니다. 

이제 이 숫자만큼 루프를 돌면서 원하는 핸들을 찾아내면 됩니다. 

헥헥.; 여기까지가 EPROCESS의 주소를 알아내는 방법입니다. 

이제 다 끝난게 아닌가 하시겠지만, 사실 더 있습니다. 

이렇게 얻어낸 EPROCESS의 주소는 선형 주소라는 것으로, 직접 접근할 수가 없습니다. 

주소에는 물리 주소, 선형 주소, 논리 주소 이렇게 3개가 있는데요, 

보통 그냥 사용해 왔던 포인터 변수는 논리 주소입니다. 

선형 주소는 가상 주소라고도 하는데요, ZwSystemDebugControl은 여기서 쓰입니다. 

이녀석도 첫번째 인자로 enum자료형을 받는데요, 여기서 사용할 값은 

8, 9 두 개이므로, 다음과같이 정의해서 쓰면 됩니다. 

typedef enum _SYSDBG_COMMAND
{
SysDbgCopyMemoryChunks_0 = 0x08, SysDbgCopyMemoryChunks_1 = 0x09
} SYSDBG_COMMAND;

두번째 인자는 데이터를 받을 구조체의 포인터입니다. 

이 구조체는 MEMORY_CHUNKS라는 구조체로, 다음과 같이 정의됩니다. 

typedef struct _MEMORY_CHUNKS
{
PVOID pVirtualAddress;
PVOID pBuffer;
DWORD dwBufferSize;
} MEMORY_CHUNKS, *PMEMORY_CHUNKS;

첫번째 멤버로 접근하고자 하는 메모리의 주소를 입력하고, 

두번째 멤버와 세번째 멤버에 데이터를 저장할 버퍼의 주소와 그 크기를 입력합니다. 

가상메모리를 읽는 경우(SysDbgCopyMemoryChunks_0), 

이 버퍼로 가상 메모리의 데이터가 쓰여집니다. 

가상메모리를 쓰는 경우(SysDbgCopyMemoryChunks_1), 

이 버퍼에 쓰인 내용이 가상 메모리에 쓰여집니다. 

한편 ZwSystemDebugControl의 세번째 인자는 읽고자 하는 데이터의 크기입니다. 

포인터를 읽을 것이기 때문에 여기에는 4를 지정하면 됩니다. 

네번째와 다섯번째 인자는 여기서 중요하지 않기 때문에 그냥 비워두었습니다. 

그리고 여섯번째 인자로 성공의 여부가 들어오게 됩니다. 

이렇게 해서 가상메모리를 읽거나 쓸 수가 있습니다. 

그럼 3부에서 계속하겠습니다.

 

 

이제 진짜 끝인가 하시겠지만 사실 조금 더 있습니다. 

가상메모리는 그냥 읽고 쓸 수 없습니다. 이걸 읽고 쓰려면 그에 맞는 권한(Privilege)

를 획득해야 하는데, 이 권한을 조절할 줄 알면 많은 것을 할 수 있습니다(^^훗)

이 권한을 통해 할 수 있는 일에 비해 이를 얻는 방법은 매우 쉽습니다. 

Native API따위를 이용하지 않아도 됩니다.

이부분은 인터넷에 자료가 많이 있으므로 소스만 올리겠습니다.

TOKEN_PRIVILEGES priv = { 1, {0, 0, SE_PRIVILEGE_ENABLED} };
LookupPrivilegeValue(0, lpName, &priv.Privileges[0].Luid);
HANDLE hToken;
OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof(TOKEN_PRIVILEGES), 0, 0);
CloseHandle(hToken);

권한에는 여러 종류가 있는데, 그 중에는 셧다운 권한, 디버그 권한 등이 있습니다. 

이름에서 예측할 수 있지만, 셧다운 권한은 컴퓨터를 시스템 종료할 수 있는 권한입니다. 

이를 획득한 후 ExitWindows와 같은 함수를 이용하면 간단히 시스템 종료를 할 수 있습니다. 

중요한 것은 디버그 권한입니다. 

이 권한은 매우 강력한 권한으로, 이 권한을 획득해야만 가상메모리를 읽거나 쓸 수 있습니다. 

(여담이지만)추가로, 이 권한을 얻으면 작업관리자도 종료하지 못하는 중대한 

프로세스들을 죽일 수 있습니다. 

이러한 프로세스들이 그냥은 Terminate되지 않는 이유는 OpenProcess함수가 NULL을

리턴하기 때문인데요, 그 이유는 PROCESS_ALL_ACCESS권한으로 프로세스를 열려면

디버그 권한이 필요하기 때문입니다. 

따라서 디버그 권한을 얻은 뒤 OpenProcess를 하면 정상적으로 핸들을 얻게 되고, 

TerminateProcess도 정상적으로 동작합니다. 

이 방법을 이용하면 smss.exe, winlogon.exe등을 죽일 수가 있는데, 

실험해 본 결과 이럴 경우 블루스크린이 뜨면서 바로 재부팅됩니다;;

출처 : http://ziscuffine.tistory.com/74



ZwQuerySystemInformation()

NTSTATUS ZwQuerySystemInformation(

IN SYSTEM_INFORMATION_CLASS SystemInformationClass,

IN OUT PVOID SystemInformation,

IN ULONG SystemInformationLength

OUT PULONG ReturnLength

);


IN, OUT, OPTIONAL은 아무 의미없는 #define으로, 인자가 입력인지, 출력인지, 그리고 사용하지 않으므로 NULL을 넣어도 되는가 등을 예기해주는 겁니다.



NTSTATUS : 윈도우 커널에서 제공하는 API들을 호출한 후 해당 결과값들을 체크하기 위해 DDK에서 제공하는 리턴값 형식 해당 API의 성공/실패 뿐만이 아니라 실패했으면 해당 실패의 원인등의 각종 정보가 미리 정해진 비트 포맷으로 제공 (SDK에서 사용하려면 LONG형을 typedef해야 합니다.)  


ZwQuerySystemInformation() API는 실행 중인 모든 프로세스의 정보(구조체)를 연결 리스트 형태로 얻을 수 있음


이 연결 리스트를 조작하여, 해당 프로세스를 은폐 (탐지 가능한 것들을 다 없애버림으로써 은폐되는 효과)


'Reversing(리버싱 핵심 원리) > API 함수' 카테고리의 다른 글

WaitForSingleObject() 함수  (0) 2018.09.04

+ Recent posts