Programming/Win32 API2008. 10. 9. 15:11
[API] Native API의 개념과 용도, 활용 예(Native API를 이용해서 컴퓨터 전원을 꺼버리자!!)

 안녕하세요. 오랜만에 글을 올리는 수학쟁이입니다. ^^

 오랜만에 글을 쓰는것이니 만큼 팁보다는 개념적인 수준의 글을 올려보겠습니다.

 이번 강좌에서는 Native API라는 놈들의 정체좀 밝혀보겠습니다. ^^..

 API는 크게 두 종류로 나뉘어져있습니다. (명칭만 다르지 구조는 같다고 보시면 됩니다.)
 보통 API는 Win32 API Native API 두 종류 로 나뉘어져있는데요.

 우리가 흔히 접할 수 있는 API는 Win32 API 입니다.

 *** Native API라는 것은 대체 무엇일까요?

 우선 Native API를 알기 전에 API 라는 녀석에 대해서 제대로 파헤쳐봅시다.

 API 라는 놈은 Application Programming Interface의 약자로 운영체제(OS)에서 소프트웨어에게 제공하는 기능 정도로 이해하시면 됩니다.

 이 API라는 놈은 내부적으로 DLL이라는 확장자를 가진 파일(동적 연결 라이브러리; Dynamic Link Library)에 함수 형태로 들어있습니다(* 유식한 말로 Export라고 합니다.).

 *** API는 몇개일까?

 그냥 API라고 하면 종류가 다양한데(Windows API, Java API, 네이버 OpenAPI 등등...) 이 여러 API 중 비베에서 함수 형태로 호출이 가능한 Windows API의 개수만 해도 어마어마합니다. 게다가 프로그래머가 이 윈도우 API를 직접 만들 수 있습니다.(Visual C++ 등의 C 컴파일러를 이용하여)

 그렇다면 프로그래머가 만들지 않은 순수하게 제공되는 'Win32 API'는 몇개일까? Native API만 빼도 대략 어림잡아 자그마치 6,000~7,000개 정도로 예상하고 있습니다. (allapi라는 사이트에서만 집필된 api가 5,000개 정도 되는데 이 수치로는 어림도 없죠.)

 *** API의 종류를 보려면?

 
 API는 아까도 언급했듯이 종류가 한두개가 아닐 뿐더러 사용되는 목적도 제각기 각각 다릅니다. (예: FindWindow 라는 API
는 특정 창의 핸들을 찾는데 사용되어지고, OpenProcess 라는 API는 프로세스의 핸들을 구할 때(접근할때)사용되는 함수이며, DDraw.dll라는 dll의 api들은 DirectDraw에서 사용되는 API입니다.) 게다가 그 수가 약 수천개에 이릅니다. 이를 다 외워서 쓰는것은 불가능하며, 다른 문서나 프로그램을 이용해서 찾아보아야합니다. 그 방법은 아래에 짤막하게 써놓겠습니다.

 Visual Basic에서는 API 텍스트 뷰어라는 뷰어를 제공합니다. 단 이 뷰어는 미리 넣어둔 API밖에 사용하질 못합니다.

 그리고 양도 적고 버그도 있습니다. 그래서 대부분의 VB 프로그래머들은 ApiViewer 2004라는 툴을 사용하거나 직접

 선언문을 만들어서 사용합니다. (ApiViewer 2004 다운로드 게시글 바로가기)

 VB의 API 선언문은 아래와 같은 구조이니까 함수 인자와 dll, API 함수 본명만 안다면 작성할 수 있겠죠.

 (함수 본명이나 인자들은 구글 검색이나 MSDN을 이용하시면 쉽게 찾을 수 있습니다. 물론 C언어 기준으로한 선언문이지만요... DLL에 export된 똑같은 함수니 C에서만 사용 가능한것이 아니라 충분히 VB에서도 쓸수있습니다.)


 [Public/Private] Declare [Sub/Function] 함수 이름 Lib "DLL이름" Alias "함수 원래 이름" ( _

     [ByVal/ByRef] 인자1 As [인자형식], _

     [ByVal/ByRef] 인자2 As [인자형식], _

     [ByVal/ByRef] 인자3 As [인자형식], _

     ...

     [ByVal/ByRef] 인자n As [인자형식] _

 ) [As 반환값형식]

 - 선언문은 필요없고 이름이라도 좋으니 이름이라도 보고싶으시다면, VS6에서 제공하는 툴인 Dependency Walker를 이용해보실 수 있겠습니다.

위치: 시작 메뉴 - Microsoft VIsual Basic 6.0 - Microsoft Visual Studio 6.0 도구들 - Depends

사용자 삽입 이미지

 *** 그렇다면 Native API라는 녀석은 무엇이냐?

 - Windows 9x 시절로 돌아가보면, 9x 시절에는 블루스크린이 유난히 많았습니다. 심지어 빌게이츠 회장이 98 시연중 장치 드라이버 검색 도중에 블루스크린(BSOD; Blue Screen Of Death)가 뜬것만 봐도 알수가 있죠. (http://kr.youtube.com/watch?v=RgriTO8UHvs)

 이는 윈도우 9x의 동작 체계가 매우 불안정했음을 보여주는 단적인 예입니다. 그런데 어째서 그렇게 불안정할까요?
 그리고 NT계열의 OS(NTx, 2000, XP, ...)들은 블루스크린 보기가 왜이렇게 힘들까요? 그 이유은 간단합니다.

 9x는 16비트와의 연계를 할 수 있는데 (그래서 고전 게임의 대다수가 동작할 수 있었죠.), XP와 달리 운영체제 레벨(Ring0)에서 바로 실행해버려서 16비트 프로그램과 32비트 OS 모듈이 충돌할 수가 있었습니다. 충돌되면 그게 블루스크린이 뜨는것이죠.

 Windows NT 계열의 경우 16비트 프로그램을 실행하면 직접 메모리에 올려놓고 실행하는것이 아닌, WOW(Windows-On-Windows)라는 체계에서(wowexec.exe) 16비트 메모리를 에뮬레이팅하고 명령어를 에뮬레이션해서 위험한 명령어를 제외하고 실행시킵니다. 그래서 도스 프로그램의 일부는 XP에서 호환되지 않는것입니다.

 그런데 이 XP가 갑자기 16비트를 에뮬레이트하게 된 내부 요인은 무엇일까요? 이것은 NT에서부터 Intel CPU(R) 및 그의 호환기종(AMD 등)만 지원하게 된 이유와도 관련이 깊습니다. 9x까지는 대부분의 CPU를 지원했으나, NT에서는 인텔 CPU만을 수용하기 시작했습니다. 이 이유중의 하나는 인텔 CPU의 링 보호 체계(Ring Protection System)를 활용하여 운영체제를 보호하는 코드를 추가하기 시작해서 인데요. 이 링 보호 체계는 간단합니다. CPU의 권한을 네등급으로 분류하고(Ring 0 ~ Ring 3) 각 링의 권한에서 실행가능한 명령어를 제한하는것입니다. 가장 좋은 권한이 Ring0이고 가장 낮은 권한이 Ring3이죠. 현재의 WinNT 계열의 OS에서는 프로그램이 실행되면 Ring3이 실행되고, 운영체제(커널)은 RIng0에서 실행됩니다. [ 보통 Ring3은 유저 모드라고 부르고 Ring 0은 커널 모드라고 부릅니다. RIng1-2는 디바이스를 위해 예약되었지만, 현재 대부분의 OS에서는 사용하지 않는 계층입니다. ]

 어떤분은 이렇게도 말하기도 합니다. " 그러면 Ring 3 프로그램에서 CreateFile()을 이용해서 파일을 읽거나 쓸수도 있는데, 그렇게 되면 결과적으로 하드디스크에 접근하게 되는데 이건 어떻게 된거냐 "

 이 질문에 대한 답도 간단합니다. Ring3 권한에서는 '하드웨어 포트 입/출력', '몇몇 일부 레지스터 접근 제한(CRx,DRx,IDTR,EFLAGS...)', '인터럽트 발생', '물리 메모리 접근', '커널 메모리(0x80000000~0xFFFFFFFF) 접근' 등이 제한됩니다. 하드디스크에 직접적으로 데이터를 읽거나 쓰기 위해서는 하드디스크에 해당하는 포트(Hardware I/O Port)에 데이터를 입출력하여 바이너리 레벨에서 데이터를 읽거나 쓸 수 있는데요. 문제는 Ring3에서는 포트 입출력 권한이 없기 때문에 Ring0으로의 권한 이행이 필요합니다. 이 떄 Ring3 API에서 Ring0으로의 권한 이행의 중간 매개 API가 'Native API'입니다.

 CreateFile()이 호출되면 내부적으로 여러 함수들이 호출되는데, 호출되는 과정을 요약하여 그림으로 나타내면 아래와 같습니다.

사용자 삽입 이미지

 (그림에서 XXX()는 생략하였음을 의미합니다. 즉 Ob,Hal,Fs,Iop로 시작하는 내부함수들을 호출하는것을 의미)

 (참고: Ob, Hal, Fs, Iop 등은 각각 Object, HAL(Hardware Abstract Layer), File System, Input/Output [private] 의 약자입니다. 이렇듯 Zw*를 제외한 Native APi들은 모두 의미있는 prefix(접두어)들을 가지고 있습니다.)

 상당히 복잡합니다만... VB에서 Native API라고 하면 위의 ZwCreateFile() 부분일겁니다.

 * Native API는 NTSTATUS라는 이상한 값(?)들로 오류 코드를 반환합니다.
 
함수 반환값 자체가 오류 코드죠. Win32 API는 GetLastError()함수나 Err.LastDllError로 얻어야하지만요.
 
Native API는 성공 했을 때가 STATUS_SUCCESS (0)의 값 (혹은 양수의 Long 값)을 가집니다.
* 여기서 알 수 있으시다시피 Win32 API는 Native API를 더욱 안전하게 쓸 수 있도록 가죽만 씌운것에 불과합니다.

 *** Native API는 활용할 수 없나?

* VB에서도 Native API를 활용할 수 있습니다. 유저 모드에서만 호출 가능한 Native API는 ntdll.dll에 커널 모드에서만 호출이 가능한 API는 ntoskrnl.exe에 들어있기 때문이죠.

 예를 들어 직접적으로 Native API를 호출하여 컴퓨터의 파워를 꺼지게 할 수 있습니다. 파워를 끄는 API는 없지만 Native API에서는 이 기능을 지원하고 있습니다.

 윈도우 종료 API로는 ExitWindows가 있고 이 API는 내부적으로 ZwShutdownSystem이라는 함수를 호출합니다.

 이 함수를 직접 호출하면 간단하게 파워를 종료시킬 수 있습니다.

 * Windows 98에서도 지원하도록 하기 위해 호출에 실패하면 Windows 98의 krnl386.exe::ExitKernel을 호출하도록 지시하게 만들었습니다.

Private Declare Function RtlAdjustPrivilege Lib "ntdll.dll" _
     (ByVal Privilege As Long, _
     ByVal bEnablePrivilege As Long, _
     ByVal IsThreadPrivilege As Long, _
     ByRef PreviousValue As Long) As Long

Private Declare Function ZwShutdownSystem Lib "ntdll.dll" _
    (ByVal Action As Long) As Long

Private Declare Function ExitWindowsEx Lib "user32.dll" _
    (ByVal uFlags As Long, ByVal dwReturnCode As Long) As Long

Private Declare Sub ExitKernelWin9x Lib "krnl386.exe" Alias "EXITKERNEL" ()

Private Const ShutdownPowerOff As Long = 2&
Private Const EWX_FORCE As Long = 4
Private Const EWX_POWEROFF As Long = &H8
Private Const EWX_SHUTDOWN As Long = 1

Private Sub PowerOff()
    On Error Resume Next
    RtlAdjustPrivilege 19, 1, 0, 0&
    If ZwShutdownSystem(ShutdownPowerOff) Then
        If ExitWindowsEx(EWX_POWEROFF Or EWX_FORCE, 0&) = 0 Then
            ExitKernelWin9x
            ExitWindowsEx EWX_SHUTDOWN, 0&
        End If
    End If
End Sub

Posted by skensita