'Category'에 해당되는 글 88건
- 2008.12.19 Name Mangling에 대한 고찰
- 2008.12.16 SUBNETTING에 대한 고찰
- 2008.12.15 고대하던 Intel Manual이 드디어 도착했네요. 1
- 2008.12.15 C언어 연산자의 정확한 영어 명칭
- 2008.12.14 INTERRUPT PROCESSING VS. POLLING PROCESSING
- 2008.12.14 [User Mode RootKit] CreateRemoteThread를 이용한 DLL INJECTION 1
- 2008.12.13 AVR-ATmega128 Architecture 1
- 2008.12.07 이야~ 드디어 블로그 만들었다ㅋㅋ
Name Mangling에 대한 고찰 Written by Sim-Hyeon, Choe
32비트 컴파일러는 __declspec(dllexport) 지시자를 사용하여 DLL에서 데이터, 함수, 클래스
또는 클래스 멤버 함수를 내보낼 수 있습니다. __declspec(dllexport)는 Object 파일에 익스포트
지시문을 알아서 추가하기 때문에 별도의 모듈 정의파일(.def)를 사용할 필요가 없습니다.
그래서 __declspec(dllexport)를 정의함으로써 컴파일러에게 해당 데이터는 익스포트 될 것임을
알려주게 됩니다. 반대로 익스포트한 모듈의 함수를 사용하기 위해서는 __declspec(dllemport)로
정의하여 알려줘야 합니다.
한가지 예를 들어 보도록 하겠습니다.
MyLibrary.cpp,
MyLibrary.h
2개의 파일이 있다고 가정하겠습니다.
MyLibrary.cpp는 이 모듈의 기능을 정의한 파일입니다.
MyLibrary.h는 이 모듈의 기능에서 필요한 선언부를 담고 있는 헤더 파일입니다.
MyLibrary.h 소스의 선두
----------------------------------------
#ifndef MYSYSAPI
#define MYSYSAPI __declspec(dllimport)
#endif
MYSYSAPI int __stdcall FuncA(int nA, int nB);
MYSYSAPI int __stdcall FuncB(int nA, int nB);
MYSYSAPI int __stdcall FuncC(int nA, int nB);
----------------------------------------
MyLibrary.cpp 소스의 선두
----------------------------------------
#define MYSYSAPI __declspec(dllexport)
#include "MyLibrary.h"
int __stdcall FuncA(int nA, int nB)
{
... ...
}
int __stdcall FuncB(int nA, int nB)
{
... ...
}
int __stdcall FuncC(int nA, int nB)
{
... ...
}
----------------------------------------
위의 두 소스를 비교보면 알겠지만, MyLibrary.h는 MyLibrary.cpp와 반대로
__declspec(dllimport)를 사용한다. 소스를 컴파일한 내용으로 해석하면 MyLib
rary.cpp에서는 MYSYSAPI라는 매크로가 전처리하게 되는데, 이어 인클루드한
MyLibrary.h 헤더 파일의 내용을 전처리하는 과정을 따르게 됩니다.
MyLibrary.h 헤더 파일에서 보면, MYSYSAPI라는 매크로가 정의되어 있는지
확인하고 이미 정의되어 있기 때문에 #define MYSYSAPI __declspec(dllimport)
은 건너뛰게 됩니다. 결국 MyLibrary.cpp에서 선언 및 정의된 세 함수가 __declspec
(dllexport)로 사용되어지게 되는데요.
이 라이브러리를 Implicit Linking할 때에도 MyLibrary.h 헤더 파일을 포함해야 합니다.
하지만 여기서의 전처리 흐름은 MYSYS API 매크로가 정의된 적이 없기 때문에
__declspec(dllimport)가 되는 것입니다.
그렇다면 이렇게까지 복잡하게 전처리하고 익스포트 및 임포트하는 이유가 무엇일까요?
일단, 이렇게 정의함으로써 라이브러리를 링킹할 때에는 소스 파일에서 사용시 헤더 파일
에 컴파일러에게 일일이 __declspec(dllimport)으로 알릴 필요가 없게 되어 라이브러리
의 사용자가 더이상 관심을 가지지 않아도 될 것입니다. Win32 API를 사용하듯 필요한
함수만 호출해서 사용하면 되겠지요.
이미 위에서도 설명드렸습니다만, __declspec(dllexport)를 정의하면 별도의 .def 파일
을 생성하여 외부 정의 함수에 대한 정의도 따로 할 필요가 없게 됩니다.
그렇다면 __declspec(dllexport)로 정의한 함수의 심벌명은 어떻게 결정될까요?
Binary Dump로 익스포트 함수의 심벌 정보를 확인하면 다음과 같습니다.
_FuncA01@4
_FuncB02@4
_FuncC03@4
인간이란 망각의 동물이다.
자신의 부단한 노력으로 얻은 소중한 기억이 결국 시간이 지나갈 수록 잊혀
져버린다는 사실에 마음이 아프다.
먼저 SUBNETTING이 필요한 배경부터 설명드리도록 하겠습니다.
P 개발 회사의 M 사원이 다음의 IP ADDRESS를 할당받았다고 가정하겠습니다.
M사원의 IP ADDRESS : 193 . 60 . 50 . 5
P 개발 회사는 소규모의 게임 개발 회사입니다. 총 종업원의 수가 12명 뿐이지요. 하지만 서브넷에서 실제
할당 가능한 IP ADDRESS의 개수는 255개나 됩니다. 서브넷(Subnet)은 어떤 하나의 네트워크를 구성하고
있는 물리적인 단위라고 정의할 수 있습니다. 우선 왜 255개의 할당이 가능한지부터 따져 보도록 합시다.
IP ADDRESS는 위와 같이 193 . 60 . 50 . 5입니다. 여기서 구분해야 할 것은 네트워크 주소와 호스트 주소
인데 네트워크 주소를 구하기 위해서는 193 . 60 . 50 . 5의 IP ADDRESS가 어떤 CLASS 영역에 포함되는지
를 구해야 합니다. IP ADDRESS 체계는 A, B, C, D 4가지 클래스로 구별됩니다.
각 CLASS의 ADDRESS 범위는 다음과 같이 확인할 수 있습니다.
A CLASS는 0 : 0000 0000 ~ 0111 1111 / 0 ~ 127
B CLASS는 10 : 1000 0000 ~ 1011 1111 / 128 ~ 191
C CLASS는 110 : 1100 0000 ~ 1101 1111 / 192 ~ 223
D CLASS는 1110 : 1110 0000 ~ 1110 1111 / 224 ~ 239
최상위 8비트로 해당 IP ADDRESS가 포함된 클래스를 결정합니다.
IP ADDRESS에서 최상위 8비트가 193이고 C CLASS 범위에 속한다는 것을 확인할 수 있겠지요.
C CLASS에서 NETWORK ADDRESS는 최상위 비트에서부터 기준으로해서 왼쪽으로 24 비트입니다.
그러니까 193 . 60 . 50 . 5 에서 빨간색이 NETWORK ADDRESS가 되고 파란색이 HOST ADDRESS가
됩니다.
그렇다면 P 개발 회사의 네트워크에서 할당 가능한 IP ADDRESS는 193 . 60 . 50 . 0 ~ 193 . 60 . 50 .
255까지입니다. 하지만 이렇게 하나의 NETWORK ADDRESS를 이 소규모의 회사에 통째로 할당해주는
것은 IP ADDRESS 자원의 낭비가 되어버리고 말지요. 그렇기 때문에 네트워크 설계자는 SUBNETTING
이라는 작업을 통해서 여분의 호스트 주소들을 다른 서브넷에게 분배를 하여 보다 효율적으로 IP
ADDRESS 자원을 사용할 수 있게 되는 것입니다.
다시 SUBNETTING을 정의하자면, 2단계(NETWORK, HOST ADDR)로 구성되어 있는 IP ADDRESS 체계를
3단계로 세분화(NETWORK - SUBNET - HOST)하는 작업을 의미합니다. 물리적으로 공급된 IP ADDRESS
를 그대로 할당하여 주는 것이 아니라, 위의 SUBNETTING처럼, 논리적인 주소로 재조합하여 사용자에게
공급을 해주어야 하지요. 재조합 과정에서 필요한 것이 바로 SUBNET MASK라는 녀셕입니다.
이 SUBNET MASK는 IP 주소 체계를 바탕으로 분할하는 논리적인 수단으로 볼 수 있지요.
그럼 본격적으로 C CLASS의 IP ADDRESS 193 . 60 . 50 . 5를 가지고 적절한 배분 작업(?)을 한번 해보겠
습니다. P 개발 회사를 포함하여 Q, W, E, R, T, Y, U, I 까지하여 총 9개 업체의 개발 회사가 있고 각각 총
종업원 수가 12명이라고 가정하겠습니다.
이미 아까전에도 말씀드렸다시피, 각 개발 회사마다 하나의 NETWORK ADDRESS를 부여해 주는 것은
무수한 호스트 주소 개수의 낭비라고 하였습니다. 그렇기 때문에 HOST ADDRESS의 비트를 일부분
활용하여 나눠 준다면 자원의 낭비를 막을 수가 있을 겁니다.
개발 회사는 총 9개 업체이기 때문에 해당하는 수와 같거나 포함하는 비트의 크기를 상위 비트에서
부터 선정해야 합니다.
뿐만 아니라, 논리적으로 할당해주는 주소 크기 범위 내에서 최하위 비트 값과 최상위 비트 값은
사용할 수 없습니다. (그 이유는 저도 잘 모르겠네요. 브로드 캐스팅과 관련이 있다는 것을 어렴풋이
들은 기억이 있습니다)
9개의 할당을 표현하기 위해서는 4비트가 필요하지요. 그러면 16개의 어드레스 구분을 표현할 수 있는
데, 위에서 언급한대로 0000과 1111은 사용할 수 없다고 하여도 14가지니까 9를 포함합니다.
그렇다면 하나의 NETWORK ADDRESS를 가지고 9개의 회사에 대한 서브넷 및 호스트 주소를 다음과
같이 디자인 할 수 있습니다.
P회사의 A사원의 IP ADDRESS - 193 . 60 . 50 . 0001 0001 (17)
P회사의 B사원의 IP ADDRESS - 193 . 60 . 50 . 0001 0010 (18)
P회사의 C사원의 IP ADDRESS - 193 . 60 . 50 . 0001 0011 (19)
Q회사의 A사원의 IP ADDRESS - 193 . 60 . 50 . 0010 0001 (33)
Q회사의 B사원의 IP ADDRESS - 193 . 60 . 50 . 0010 0010 (34)
Q회사의 C사원의 IP ADDRESS - 193 . 60 . 50 . 0010 0011 (35)
E회사의 A사원의 IP ADDRESS - 193 . 60 . 50 . 0011 0001 (49)
E회사의 B사원의 IP ADDRESS - 193 . 60 . 50 . 0011 0010 (50)
E회사의 C사원의 IP ADDRESS - 193 . 60 . 50 . 0011 0011 (51)
............
............
결국 HOST ADDRESS를 논리적으로 쪼개어 마치 또다른 하나의 NETWORK ADDRESS처럼 표현하여
아주 효율적으로 배분이 가능하지요. 바로 이 작업을 SUBNETTING이라고 표현합니다.
중요한 것은 이러한 논리적인 분할을 위해 SUBNET MASK가 존재한다는 사실입니다. 각 회사의 사원
개개인마다 위와 같은 주소의 배분을 위해서 255 . 255. 255. 240 이라는 SUBNET MASK로 서브넷만을
유지해주면 되겠지요.
(그 역할을 담당하는 주체가 라우터인지 정확하게 모르겠군요. 하하)
한가지 예를 들면,
P회사의 A사원의 IP ADDRESS - 193 . 60 . 50 . 0001 0001
SUBNET MASK - 255 . 255 . 255. 1111 0000 (240)
저 9개 회사에 대한 SUBNET MASK는 모두 255 . 255 . 240 . 0으로 동일하다는 것을 알 수 있습니다.
네트워크에 대해서 저도 깊이는 없지만, 이 내용만큼은 알아두면 나름대로 유용한 지식같군요.
무시당한 줄만 알았는데.. 저의 보물중에 하나가 될 것 같네요. 앞으로 열심히 공부해야 하겠습니다. 하하
# : Crosshatch (크로스해치)
* : Asterisk (아스테리스크)
\ : Back Slash (백슬래시)
& : Ampersand (앰퍼센드)
! : Exclamation Point (익스클레메이션 포인트)
" : Quotation Mark (쿼테이션 마크)
$ : Dollar Sign (달러사인)
% : Percent Sign (퍼센트사인)
@ : At Sign (엣 사인, 혹은 엣)
' : Aposterophe (어퍼스트로피)
- : Hyphen (하이픈)
. : Period (피리어드)
/ : Slash (슬래시)
: : Colon (콜론)
; : Semicolon (세미콜론)
^ : Circumflex (서큠플렉스)
` : Grave (그레이브)
{ : Left Brace (레프트 브레이스)
} : Right Brace (라이트 브레이스)
[ : Left Braket (레프트 브라켓)
] : Right Braket (라이트 브라켓)
| : Vertical Bar (버티컬바)
~ : Tilde (틸드)
INTERRUPT PROCESSING VS. POLLING PROCESSING

폴링 처리 방식은 지속적으로 이벤트가 발생하는 것을 감시하고 발생한 이벤트에 따른 명령을 처리하는
것을 의미합니다. 그에 반해서 인터럽트 처리 방식은 비동기적인 이벤트가 발생하는 그 순간에서만
프로세서가 관심을 가지고 해당 인터럽트를 처리합니다. 위의 두 개념만 보아도 폴링 처리 방식이 이벤
트를 지속적으로 감시하기 때문에 인터럽트 처리 방식에 비해 비효율적이라는 것을 알 수 있습니다. 뿐
만 아니라 감시하고자하는 대상이 많아질수록 그에 대한 이벤트 처리는 응답성이 떨어지는 점도 단점이
라고 할 수 있습니다.
그럼에도 불구하고 폴링 처리 방식이 사용되는 이유는 인터럽트 처리 방식에 비해 경제적이기 때문입니
다. 인터럽트 방식은 내부 혹은 소프트웨어 인터럽트 뿐만 아니라 하드웨어 외부에서 발생하는 하드웨어
인터럽트를 처리하기 위한 디지털 회로 설계 및 인터럽트 제어 레지스터와 같은 부품들이 추가적으로 필
요합니다. 결국 처리해야 할 디바이스 장치가 많을 수록 그에 따른 인터럽트 처리를 위와 같은 이유로 하
드웨적으로 구현해야 하기 때문에 하드웨어 자체의 단가가 올라갈 수 밖에 없겠지요. 폴링 방식 자체는
하드웨어 뿐만 아니라 소프트웨어적으로도 그 구현이 가능합니다.
<그림1> 폴링 방식과 인터럽트 방식의 비교
<폴링 처리 방식의 소프트웨어적인 구현>
while( TRUE ) if( RXD != 0x00 ) } |
이것은 마치 윈도우 프로그래밍에서 메시지 루프의 메시지 인출 방식을 GetMessage()와
PeekMessage()로 처리하는 것과 비슷한 메커니즘이라고 할 수 있습니다. GetMessage()와
PeekMessage()의 차이를 안다면 쉽게 이해가 될 수 있는 내용이겠지요.
Written by Sim-hyeon, Choe
[User Mode RootKit] CreateRemoteThread를 이용한 DLL INJECTION

DLL INJECTION 기법에 대해서 예전에 제가 서술한 적이 있었습니다만 다시 기고합니다.
DLL INJECTION이 필요한 배경부터 설명드리겠습니다.
Win32기반의 Virtual Memory는 어떠한 구조를 지니고 있는지 여러분들은 잘 아시리라고 생각하고
있습니다. 4GB의 주소 공간에서 하위 주소 공간 2GB는 독립적인 프로세스를 위한 주소 공간, 그리고
상위 2GB는 커널을 위한 영역으로 존재하지요.
(물론 Windows Version에 따라서 주소 공간의 Limit가 다르기도 합니다.
이것은 또 옵션에 따라 변경이 가능합니다)
잘 아시다시피 Win32 운영체제는 Protected Mode를 기반으로 동작합니다. 커널 영역의 접근에 대한
보호가 필요하기도 하지만 각각의 프로세스들 역시 서로 격리되어 자신의 주소 공간 내에서만 접근이 가
능하지요. 이것은 프로세스들의 주소 공간에서 논리적인 주소(가상 주소)는 동일하나 물리 메모리에 맵
핑되는 주소의 위치는 서로 다르다는 것을 의미합니다. 그리고 두 프로세스가 서로 같은 논리 주소를 참
조하더라도 다른 물리 주소 공간을 참조할 수 있는 것은 페이지 테이블에서 논리 주소와 맵핑된 물리
주소를 가지고 있기 때문이겠지요.
중요한 것은 프로세스간 서로의 주소 공간에 대한 접근이 불가능하다는 점입니다. 물론 IPC와 같은 기법
으로 분명 프로세스간의 통신이 가능합니다. 하지만 이는 어디까지나 두 프로세스가 정상적으로 상호 프
로토콜을 가지고 접근하기 때문에 가능하지요. 그렇다면 방법이 존재하지 않는 것일까요..?
없다면 제가 글을 쓸 이유가 없겠지요.
우선 A Process와 B Process가 존재한다고 가정하겠습니다. 그리고 A Process는 Injector 역할의
Process라고 하겠습니다. 여기서 A Process가 Injector라는 의미는 공격자(악성코드)로 보시면 됩니
다. B Process는 Injection을 당하는(공격당하는 타깃) Process로 보시면 되겠습니다. 공격자(A
Process)의 목적은 B Process가 사용하고 있는 API를 Hook하여 자신의 목적 달성을 위한 코드를 수
행할 수 있도록 설치하는 것이라고 하겠습니다. API를 Hook을 한다는 것은 타깃 프로세스의 메모리를
수정함을 의미하는데, 가능한지 의문이 생기겠지요. 그리고 코드 역시 타깃 프로세스 내에 주입이 가능
한지 의문이 드실 겁니다. 그렇다면 여기서 어떻게 타깃 프로세스에 접근하여 그 메모리 내용을 수정 할
수 있을까요? 그에 대한 해답은 Debug API에 있습니다.
예를 들어, 우리가 VC와 같은 개발툴로 어떤 프로그램 소스를 작성하고 프로그램의 각 루틴들이 문제없
이 동작하는지를 살펴보기 위해서 디버깅을 하게 되는데, 원하는 특정 위치에 Break Point(중단점)을
설정하여 실행을 일시적으로 통제 할 수 있음을 아시고 계실 겁니다. 그리고 디버깅 중에 변수들의 값을
확인 할 수도 있고 변경도 가능하며, 심지어 디버깅 중에 코드의 수정도 가능하다는 사실 역시 아실 겁니
다. 우리가 위의 작업을 하고 있다고 하였을 때, 주시해야 할 부분은 VC는 "디버거 프로세스"가 되고
어떤 프로그램 소스라고 언급하였던 녀석은 "디버기(디버깅을 당하는) 프로세스"가 된다는 것입니다.
풀어서 한번 더 쉽게 설명드리자면, 우리가 어떤 프로그램을 작성하기 위해서는 이를 작성하기 위한
툴이 당연히 실행되고 있어야 할 것입니다. 그 툴이 VC를 말하구요. VC가 프로세스 상태가 되어야 한다
는 것이지요. 그리고 작성한 프로그램이 제대로 돌아가는지 테스트 해보기 위해서는 그 프로그램을 컴파
일하고 실행 파일을 생성하여 실행하게 되는데요.. 이것도 프로세스 상태로 존재함을 말씀드리는 겁니
다. 다만 여기서 작성한 프로그램은 그냥 실행이 아니라 "디버그 모드"로 실행시킴을 의미하지요.
핵심을 정리하자면, VC라는 프로세스로 디버깅이라는 작업을 통해서 작성한 프로그램을 타깃 프로세스
로 생성하여 그 주소 공간에 접근하고 수정(변수 및 코드 변경) 및 실행을 통제(브레이크 포인트) 할 수
있다는 사실입니다. 아무리 Protected Mode 기반의 OS라 할지라도 마이크로소프트사에서는 개발하고
있는 프로그램의 디버깅을 위해 이러한 메커니즘을 제공해 줄 수 밖에 없다는 것이지요. 하지만 DEBUG
API는 말 그대로 DEBUG를 위해서 존재하는 API들입니다.
MS에서도 충분히 보안에 대한 위험성을 인지하고 제약을 걸어 두었는데요.. DEBUG API는 반드시
DEBUG 모드로 프로세스를 ACCESS해야 사용이 가능합니다. 그리고 DEBUG API는 오버 헤드 또한 크
기 때문에 되도록이면 많은 사용을 기피해야 합니다. 디버깅 작업을 할 때 크게 느려지는 이유도 바로 거
기에 있지요. DLL Injection을 위해서 필요한 DEBUG API는 WriteProcessMemory 하나면 충분합니
다. API 이름 자체가 워낙 설명적이라 용도는 생략하겠습니다(MSDN 참고).
그럼 다시 본론으로 돌아와서 DLL INJECTION을 위해 수행되는 절차를 천천히 나열하면서
설명드리도록 하겠습니다..
1. 침투 대상이 되는 프로세스를 찾는다.
2. 디버그 모드의 권한으로 프로세스를 엑세스한다.
3. 프로세스 주소 공간에 인젝션 할 DLL Name 크기의 공간을 VirtualAllocEx로 할당한다.
4. 인젝션 할 DLL Name을 3번에서 할당한 주소 공간에 WriteProcessMemory로 쓴다.
5. CreateRemoteThread로 침투 대상이 되는 프로세스에 Thread를 생성한다.
6. 루트킷 DLL이 로드되는 시점에서 API Hook을 수행한다.
1. 침투 대상이 되는 프로세스를 찾는다.
우선은 우리가 침투할 타켓 프로세스를 엑세스하기 전에 먼저 찾아야 할 것입니다.
이것은 ToolHelp나 ADVAPI를 사용하여 현재 상주하고 있는 프로세스를 나열하고 그 정보를
캡쳐 할 수 있도록 윈도우즈 자체에서 라이브러리를 제공해 주고 있습니다. 캡쳐한 정보에서
는 우리가 원하는 프로세스 이름을 가지고 있는지 확인하고 일치하면 프로세스 아이디를
얻어 올 수 있습니다.
2. 디버그 모드의 권한으로 프로세스를 엑세스한다.
OpenProcess로 접근하고자하는 프로세스를 얻어온 아이디로 엑세스 할 수 있습니다.
그중에 첫 번째 매개 변수는 프로세스를 어떤 권한을 가지고 접근할지에 대한 플래그들을
줄 수 있는데, 우리가 인젝션을 위해 필요한 플래그들을 나열 해보면,
PROCESS_CREATE_THREAD (CreateRemoteThread 허용)
PROCESS_VM_OPERATION (DEBUG 모드로 접근하기 위해서 반드시 적용해야 함)
PROCESS_VM_WRITE (WriteProcessMemory 허용)
위의 3개 플래그가 필수가 되겠지요.
3번과 4번은 5번부터 설명드리고 왜 그러한 작업이 필요한지에 대해서 설명드리도록 하겠습니다.
5. CreateRemoteThread로 침투 대상이 되는 프로세스에 Thread를 생성한다.
CreateRemoteThread는 [프로세스의 핸들 + CreateThread]의 조합이라고 보시면 됩니다.
CreateThread는 현재 프로세스 내에서 쓰레드 생성이 가능한데, CreateRemoteThread는
프로세스의 핸들을 매개 변수로 줄 수 있으므로, 타 프로세스에 쓰레드 생성을 가능케하지요.
CreateRemoteThread에서 중요한 매개 변수들만 보면 아래와 같습니다.
CreateRemoteThread( 프로세스 핸들,
0,
NULL,
쓰레드 함수의 시작 주소,
쓰레드 함수의 파라미터,
NULL,
0,
NULL
);
여기서 유의해야 할 부분이라면 CreateRemoteThread 함수를 실행하는 녀석은 "디버거 프로세스"
라는 점입니다. 처음에 말씀드렸던 공격자 프로세스(Injector)를 의미합니다. 그리고
CreateRemoteThead의 네 번째 매개 변수에는 쓰레드를 생성하여 호출할 함수의 주소가 되는데요.
Caller 역시 "디버거 프로세스"라는 것입니다. 하지만 생성된 쓰레드의 Kernel Object는 "디버기 프
로세스"의 소유며, 쓰레드의 실행도 "디버기 프로세스"입니다. 이것을 잘 구분하고 계셔야 됩니다.
여기서 쓰레드의 시작점으로 호출할 함수의 주소는 LoadLibrary입니다. 그렇게 해야 비로소
루트킷이 되는 DLL을 "디버기 프로세스"에서 로드하도록 만들 수 있을 테니까요.
하지만 네 번째 매개 변수에 LoadLibrary라고 그냥 써버리면 안된다는 것입니다.
왜냐하면, 공격자 프로세스의 LoadLibrary의 주소와 주입당하는 프로세스의 LoadLibrary를 참조하
는 주소는 서로 다르기 때문이지요. 물론 LoadLibrary가 실제 KERNEL32.DLL에 위치하는 주소는
동일합니다만 문제는 두 프로세스에서 LoadLibrary를 참조하는 주소(ILT의 주소)가 서로 다르다는
것입니다. (이해를 위해서 또 많은 설명이 필요한데요. 궁금하시면 저에게 질문해 주시면 친절히 답
변해 드리겠습니다)
결국 GetProcAddress로 KERNEL32.DLL에 존재하는 LoadLibrary의 주소를 동적으로 구해와야 하
지요. 공격자 프로세스나 주입당하는 프로세스나 KERNEL32.DLL이 맵핑되는 시작 번지는 항상 동일
합니다. 그렇기 때문에 공격자 프로세스에서 로드된 KERNEL32.DLL의 GetProcAddress의 주소를
구해도 전혀 문제가 되질 않는다는 겁니다.
자~ 여기까지 왔으면 쓰레드 생성을 당한 디버기 프로세스(B Process)에서 LoadLibrary를 쓰레
드 함수로 호출이 됩니다.
(우리는 흔히 쓰레드를 사용하면 우리가 만든 사용자 함수를 엔트리에 두는데 그 대신에
LoadLibrary라고 보시면 됩니다)
여기서 중요합니다. 우리는 LoadLibrary( "우리의 루트킷 DLL" ) 형태로 호출을 원합니다.
분명 LoadLibrary를 쓰레드의 시작 함수로 호출했지요? 그런데 위의 파라미터에서 존재하는 문자열
은 어디에 존재하는가요? 우리는 "디버기 프로세스(공격당하는 타깃 프로세스)"에서 우리의 루트킷
DLL 문자열을 선언 및 할당한 적은 어디에도 없다는 것입니다. 이해가 되십니까?
그렇기 때문에 디버기 프로세스에 우리의 루트킷 DLL의 문자열을 할당 해 놓고 그 문자열의 시작 주
소값을 아까 공격자 프로세스에서 CreateRemoteThread 할 때, 다섯 번째 매개 변수 파라미터에 함
께 전달해 주어야하는 이유가 여기에 있습니다. 제가 5번부터 설명드린 이유도 여기에 있구요.
이제 CreateRemoteThread 하기전에 3, 4번 과정을 먼저 수행해야 이유를 이해하실 수 있을 것입니
다..
3. 프로세스 주소 공간에 인젝션 할 DLL Name 크기의 공간을 VirtualAllocEx로 할당한다.
Win32 API를 살펴보면, 함수 이름에서 접미사 "Ex"가 붙은 API를 보실 수 있을 겁니다.
함수 이름에서 알 수 있듯이 VirtualAlloc은 가상 주소 공간을 할당하는 API입니다.
물론 현재 프로세스 내에서가 되겠지요. 하지만 이 함수에 Ex가 붙으므로 프로세스 핸들을
매개 변수로 줄 수 있습니다. 결국 다른 프로세스에 가상 주소 공간을 할당 할 수 있음을 의미하지요.
VirtuallAllocEx로 이제 타깃 프로세스로 할당 할 수 있음을 알 수 있습니다.
(함수에 대한 자세한 설명은 MSDN을 참고하시길..)
4. 인젝션 할 DLL Name을 3번에서 할당한 주소 공간에 WriteProcessMemory로 쓴다.
문자열 크기 만큼 타깃 프로세스에 할당했으면 이제 그 문자열을 쓰면 되겠습니다..
이제 1~5번까지 모두 연결이 되나요? 결론은 타깃 프로세스에서 수행되는 쓰레드의 시작 함수가
타깃 프로세스에 맵핑된 KERNEL32.DLL의 LoadLibrary가 될 것이고, LoadLibrary할 DLL은
루트킷이 되는 DLL이 되겠지요. 물론 그 DLL의 이름은 타깃 프로세스 주소 공간에 이미 위치하기
때문에 LoadLibrary( "루트킷 DLL" )가 타겟 프로세스에서 무사히 수행이 될 것입니다.
6. 루트킷 DLL이 로드되는 시점에서 API Hook을 수행한다.
그렇다면 API Hook을 어떻게 할 수 있을까요? 그 비밀은 LoadLibrary API 내부에서 로드하기 위한
여러 과정을 수행하게 되는데요. 중요한건 로드하고 나면 로드된 DLL의 DllMainCRTStartup을 호출
합니다. DllMainCRTStartup은 다시 DllMain을 호출하구요.
DLL에도 메인 함수가 있습니다. 이것은 프로그래밍시 메인 함수를 생략 할 수도 있습니다.
(컴파일 될 때 컴파일러가 묵시적으로 기입하게 되지요)
아무튼 중요한건 DLL 메인 함수를 호출하면서 매개 변수로 DLL_PROCESS_ATTACH라는 파라미터
를 함께 넘겨주게 됩니다. 한마디로 DLL이 로드될 때 발생하는 이벤트라고 보시면 되겠지요.
그 파라미터가 dwReason인데, dwReason == DLL_PROCESS_ATTACH 임을 체크한 후에 우리가
하고자하는 루틴(API Hook)을 수행해주면 끝입니다.
결국은 CreateRemoteThread 한방으로 인젝션 및 후킹까지 이루어 질 수 있음을 의미하지요.
설명이 길었네요. 여기서 잘 이해가 안되시거나 궁금하신 점이 있으신 분들은 여기서 저에게 질문해
주시거나 메일로 질문해 주시면 친절히 설명해 드리도록 하겠습니다... 이상입니다.
이번 시간에는 AVR Micro Controller와 ATmega128에 대한 개론을 학습하는 시간을 갖도록 하겠습니다. 저도 처음 접하는 AVR-ATmega128입니다만, 내부 구조를 살펴봄으로써 임베디드 아키텍쳐를 이해하는데 좋은 계기가 될 것 같습니다. ATmega128로 간단하게 하드웨어를 제어하고 조작해 보는 것도 나쁘진 않군요.
다음글은 'AVR Controller ATmega128 정복'에서 발췌하였음을 사전에 말씀드립니다.
AVR Micro Controller
이 AVR은 Program Memory와 Data Memory를 엑세스하기 위한 Bus를 독립적으로 사용하는 Havard Architecture와 Pipe Line 처리 방식을 기반으로 하는 RISC(Reduced Instruction Set Computer)를 기반으로 하여 빠른 명령 처리 속도를 자랑한다. 뿐만 아니라 AVR은 이를 ATmel사의 장점인 플래시 메모리(Flash Memory) 기술과 접목시켜 칩내에 프로그램 코드용으로 플래시 메모리를 내장하고 여기에 사용자 프로그램을 쉽게 다운로드 할 수 있는 ISP(In-system Programming) 방식을 적용하였다.
ATmega Family
이 패밀리는 mega 패밀리라고도 하며, 모든 모델명이 ATmega로 시작한다. 내부에 8KB~256KB의 플래시 메모리를 가지고 있으며, 가장 규모가 크고 성능이 높은 응용 분야에 사용하는 널리 사용되며 가격이 꽤 높고 기능이나 성능도 높다. 따라서 여기에 해당하는 모델들은 패키지도 28~100핀 정도로 핀수가 상당히 많다. 이중에서 성능이 우수한 모델들은 20MHz 클럭에서 20MIPS의 명령 처리 속도를 갖는다. 내장하고 있는 플래시 메모리의 용량에 따라 기본 모델이 ATmega8, ATmega16, ATmega32, ATmega64, ATmega128, ATmega256 등으로 정착되어 가고 있다.
ATmega128의 주요 특징
① 고성능이면서 저소비전력형의 8비트 마이크로컨트롤러이다.
② 진보된 RISC 구조를 사용하여 16MHz에서 평균적으로 16MIPS의 명령 처리 속도를 낸다.
③ 133종의 명령어 셋을 가지며, 이것들 중의 대부분은 1 클럭 사이클에 실행된다.
④ 32개의 8비트 범용 레지스터를 가지며, 이밖에 2사이클에 실행되는 곱셈기와 많은 I/O 제어용
레지스터를 가지고 있다.
⑤ 128KB의 ISP 방식 프로그램용 플래시 메모리를 가지고 있으며, 이것은 10,000번까지 지우고 다시
쓸 수 있다. ISP를 수행하기 위한 전용 SPI 통신 인터페이스를 가지고 있다.
⑥ 4KB의 데이터 저장용 EEPROM을 가지고 있으며, 이것은 100,000번까지 지우고 다시 쓸 수 있다.
⑦ 4KB의 데이터 저장용 SRAM을 가지고 있다.
⑧ 외부에 약 60KB의 데이터 메모리를 인터페이스 할 수 있다. Memory-Mapped I/O 방식을 사용하므로
외부 I/O도 이 데이터 메모리 영역에 접속하여 사용한다.
⑨ 내장 메모리의 프로그래밍과 온칩 디버그 기능을 수행하기 위하여 JTAG 인터페이스 기능을 가진다.
이제서야 저만의 블로그를 오픈했네요 ^ ^ ;;
언제부터 블로그가 필요하다고 생각하고 있었는데.. 워낙 귀차니즘 때문에 -_-;;
뭐.. 제가 얼마나 관리할지는 모르겠지만 아무튼 저의 블로그에서 많은 것을 얻어 가셨으면 좋겠습니다.^ ^
아..참 저의 블로그는 프로그래밍에 관한 모든 것들을 주제로 다룰 겁니다.ㅎㅎ
지켜봐주세요ㅎㅎ