원래 기본 스킨이 검은색 스킨이었는데, 왠지 조금 무거워 보여서 스킨을 조금 고치려했습니다.... 만... ㅡ_ㅡa...어쩌다보니 색깔 부분은 다 손대게 되었습니다. ㅜ_ㅜ... 할 것도 많은데, 왜 이 짓을 했는지는 모르지만... 스킨은 백업 차원에서 올려둡니다.

DIV 박스 추가한 부분과 색깔 부분을 수정한게 전부입니다.

ps) 나중에 시간나면 스킨 부분 고치는 요령 같은 것도 하나 써야겠군요. 저처럼 HTML에 거의 무지한 사람도 필요한 부분은 쉽게 손볼 수 있도록요... ^^


04 System Monitor

원문 : http://kkamagui.springnote.com/pages/569963

 

들어가기 전에...

 

 1년 전에 우리 애들과 함께 만든 시스템 감시 프로그램이다. SDT를 Hooking 하여 시스템 전체 프로세스 실행 및 종료를 감시하며 파일 및 폴더 접근을 감시한다. 또한 파일 및 폴더 자동 숨김 기능을 가지고 있어서 중요한 자료를 숨기는데도 그만이다. ^^;;;

 프로토타입으로 개발된 후, 여러 기능을 추가하여 다양한 버전으로 업그레이드 되었다.(System Monitor 만세!!, 우리 애들 만세!!!). System Monitor 프로토타입은 아래와 같은 기능을 가지고 있다.

  • 파일 및 폴더 숨김 기능
  • 파일 및 폴더 접근 제한 및 접근 알림 기능
  • 프로세스 실행 및 종료 모니터링 기능
  • 환경 설정기능

 

 파일_관리자.PNG

<파일 관리자 화면>

 

시스템_모니터링.PNG

<시스템 모니터링 화면>

 

환경설정.PNG

<환경설정 화면>

 

실행파일 : SystemMonitor.zip

주의!! : XP Service Pack 2 사용자가 아니면 재부팅이 될 수 있음!!!. 또한 XP Service Pack 2 사용자일지라도 Anti-Virus(백신) 프로그램에 의해서 재부팅이 유발될 수 있음

버그 리포트 : X... (더이상 개발 안해요 ㅜ_ㅜ)

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

온라인 게임 해킹( Cheating in On-line Games )에 관한 내용 자료들  (0) 2007.11.16
01 PE 파일 분석-헤더분석  (2) 2007.11.16
03 Network Monitor  (2) 2007.11.15
02 Key Launcher  (6) 2007.11.15
01 Virtual Desktop  (8) 2007.11.15

03 Network Monitor

원문 : http://kkamagui.springnote.com/pages/569916

 

들어가기 전에...

 

 

 네트워크 모니터링과 프로그램 테스트 및 디버깅용으로 만든 드라이버 및 프로그램이다. TCP/UDP 패킷을 캡쳐하여 화면에 표시하고 로그를 남겨주는 기능과 강제로 데이터를 송신하는 기능을 갖추고 있다.

 이 프로그램의 기능은 아래와 같다.

  • TCP/UDP 데이터 표시
  • IP/Port 정보 및 해당 포트의 상태정보 표시
  • 드라이버 수정 시 개인 방화벽 또는 네트워크 바이러스 필터로 적용 가능

 

로그화면.PNG

<로그 화면>

패킷_송신.PNG

<패킷 송신 화면>

 

실행파일 : 비공개

버그 리포트 : 비공개

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

01 PE 파일 분석-헤더분석  (2) 2007.11.16
04 System Monitor  (0) 2007.11.15
02 Key Launcher  (6) 2007.11.15
01 Virtual Desktop  (8) 2007.11.15
00 KKAMALENDAR(KKAMAGUI Calendar)  (11) 2007.11.15

02 Key Launcher

원문 :  http://kkamagui.springnote.com/pages/546563

 

들어가기 전에...

 

 내가 사용할 용도로 만든 간단한 단축 키 프로그램이다. 윈도우에서 사용이 그리 잦지 않는 "윈도우 키"를 사용해서 프로그램을 실행할 수 있도록 했다. 커맨드 라인 옵션까지 지원하므로 사용하는데 큰 무리는 없다.

 

 이 프로그램의 기능은 아래와 같다. 

  • F1 ~ F12, A ~ Z, 0 ~ 9 까지의 키 사용 가능 
  • 최대 12개까지 사용 가능
  • 커맨드 라인 옵션 및 윈도우 PATH를 이용한 프로그램 실행 가능
  • 트레이 아이콘 기능 및 프로그램 숨김 기능 

 

단축키.PNG

<실행 화면>

 

 사용방법은 아래와 같다.

  • 시작

    • 프로그램을 더블 클릭한 뒤 트레이 아이콘에 보면 검은색 컴퓨터 모양의 아이콘이 표시되는데, 그것을 왼쪽으로 클릭하면 위와 같은 화면이 표시된다.
  • 키 및 프로그램 or 폴더 등록 

    • 원하는 키를 앞부분의 콤보박스에서 선택하고 뒷부분의 에디트 박스에 실행할 프로그램 명이나 폴더 명을 explorer와 함께 적어주면 실행할 수 있다.
  • 윈도우 숨김 or 표시 

    • 최소화 버튼을 누르면 윈도우가 숨겨지면서 트레이 아이콘으로 이동한다.
    • 윈도우를 다시 표시하고 싶으면 트레이 아이콘에 왼쪽 버튼을 클릭한다. 
  • 프로그램 종료 

    • X 버튼이나 트레이 아이콘을 왼쪽으로 더블클릭한다.

 

실행파일 : KeyLauncher.zip

버그 리포트 : http://kkamagui.tistory.com/94

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

04 System Monitor  (0) 2007.11.15
03 Network Monitor  (2) 2007.11.15
01 Virtual Desktop  (8) 2007.11.15
00 KKAMALENDAR(KKAMAGUI Calendar)  (11) 2007.11.15
60 유용한 팁  (0) 2007.11.14

01 Virtual Desktop

원문 :  http://kkamagui.springnote.com/pages/391434

 

들어가기 전에...

 

 내가 사용할 용도로 만든 가상 데스크탑 프로그램이다. 최대 9개까지 가상화면을 만들어주고 남들한테는 없는 "항상 위" 기능을 가지고 있어서 창을 항상 위에 표시하게 하는 역할을 한다. 문서 작성시에 창을 겹쳐놓고 작업할때 유용하다.

 실행 시에 즉각 트레이아이콘으로 숨겨져서 화면에 아무것도 안보이는 당황(??)스러운 상황도 연출되는데... 일단 나뭇잎 아이콘을 왼쪽으로 더블클릭하면 화면에 표시된다.

 

 이 프로그램의 기능은 아래와 같다.

  • 가상 데스크탑 9개 생성기능
  • 컨트롤 + 알트 + 방향키로 3x3 매트릭스 이동가능. 이동 시에 화면 가운데 현재 창 번호 표시
  • 트레이 아이콘 기능. 트레이 아이콘을 오른쪽 마우스 버튼으로 더블클릭하면 종료 가능. 왼쪽으로 더블 클릭하면 토끼 윈도우 표시.
  • 토끼 윈도우의 좌우 버튼으로 이동 가능
  • 가상 데스크탑 실행 후 윈도우 창에 오른쪽 버튼을 클릭하면 토순이 메뉴와 항상위 메뉴를 추가.
  • 토순이 메뉴를 통해 창 이동 가능. 항상위 메뉴를 통해 항상 위 기능 사용/제거 가능

Virtual_Desktop.PNG

 

 

실행파일 : 가상_데스크탑.zip

버그 리포트 : http://kkamagui.tistory.com/93

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

03 Network Monitor  (2) 2007.11.15
02 Key Launcher  (6) 2007.11.15
00 KKAMALENDAR(KKAMAGUI Calendar)  (11) 2007.11.15
60 유용한 팁  (0) 2007.11.14
10 WinDbg 간단 사용법  (0) 2007.11.14

00 KKAMALENDAR(KKAMAGUI Calendar)

원문 :  http://kkamagui.springnote.com/pages/391432

 

들어가기 전에...

 

 학교에서 텀프로젝트를 해야 한다길래... 뭘 할까 고민하다가 복잡한 일정 때문에 일정관리가 필요해져서 만든 일정관리 프로그램이다.  기능은 아래와 같다.

  • 레인렌더(Rainlendar) 스타일의 나름 깔끔한 화면
  • 일정에 파일을 덧붙여 저장 가능
  • FTP를 통한 파일 업로드/다운로드 및 일정 업로드/다운로드 기능
  • 간단한 메모 저장 기능

 

수정사항

  • Version 1.1 (2008.04.04)

    • 윤달 문제 해결

 KKAMALENDAR.png

 

 실행파일 다운로드 : KKAMALENDAR.zip

 버그 리포트 : http://kkamagui.tistory.com/92

This article was written in springnote.

'Windows System Application' 카테고리의 다른 글

02 Key Launcher  (6) 2007.11.15
01 Virtual Desktop  (8) 2007.11.15
60 유용한 팁  (0) 2007.11.14
10 WinDbg 간단 사용법  (0) 2007.11.14
02 Device Driver Code  (0) 2007.11.14

00 오른쪽 마우스 메뉴에 도스창 열기 추가하기

참고 : http://zextor.tistory.com/2669790

들어가기 전에...

  • 이 글은 kkamagui에 의해 작성된 글입니다.

1.추가 방법

DOS 시절부터 컴퓨터를 이용하였거나 프로그래머 개발자의 경우 아직도 Dos이용하고 있습니다.

하지만 DOS를 실행하고 원하는 폴더에 접근하기 위해서는 원도우키 + R  또는 [시작]→[실행] 후 “ CMD “ 를 입력 후 도스창이 실행되면 CD 명령어를 이용하여 원하는 폴더에 이동할 수 있습니다.

레지스트리를 수정하여 탐색기를 이용하여 먼저 가고자 하는 폴더에 접근 후 DOS를 실행 하여 바로 해당 폴더의 경로로 연결되어 사용하기 편리합니다.

1. [시작]→[실행]에서 “regedit “를 입력하고 레지스트리 편집기를 실행한 후, 다음 키 값을 찾는다. HKEY_CLASSES_ROOT\Directory\shell

2. Shell 키 위에서 마우스 오른쪽 마우스 클릭(또는 shell 키 선택 후 오른쪽 공백에서 오른쪽 마우스 클릭) 후 [새로 만들기(N)] → [키]를 선택합니다.

3. 새로운 키의 이름을 DOS(이름은 원하시는 이름으로 하셔도 됩니다.)로 수정합니다.

4. 만들어진 DOS 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 오른쪽 마우스에 표시될 이름을 입력하여 주십시오. ( 예를 들어 도스창이라 입력합니다. )

5. 다음 새로 만들어진 DOS에서 Shell 과 마찬가지로 새로운 키를 만들어 Command 이름으로 수정합니다. ( DOS 와는 달리 반드시 command 이름으로 하여야 합니다. )

6. 만들어진 Command 키를 클릭 후 오른쪽의 기본값을 더블 클릭하여 cmd.exe /k cd "%1" 이라는 문자열을 입력하여 주십시오.

7. 컴퓨터를 재 시작하여 탐색기 실행 후 가고자 하는 폴더를 선택 후 오른쪽 마우스 클릭 후 도스창을 클릭하시면 해당 폴더의 경로로 도스창이 열리는 것을 확인 할 수 있습니다.

본 자료는 (주)웰비아닷컴 의 커뮤니티 - 활용팁 에서 스크랩한 것입니다.

이 자료 외에도 많은 정보가 있으니 필요하신 분은 직접 방문해 보시길 바랍니다.

본 페이지에서는 재부팅 후에 사용할 수 있다고 기술하였지만 바로 사용하고 싶으시면 아래와 같이 해주시면 됩니다.

1. '시작' 이 있는 작업표시줄에 마우스 오른쪽 버튼을 눌러 작업관리자를 띄움니다.

2. 프로세스 탭에서 explorer.exe 를 선택하여 프로세스 끝내기를 합니다. 경고가 뜰 경우 그냥 "예" 를 선택하십시오.

3. 그럼 밑에 작업표시줄이 없어질 것 입니다.

4. 그럼 아까 작업관리자의 응용 프로그램 탭으로 이동한 후 새 작업을 클릭합니다.

5. 열기 옆에 있는 입력창에 "explorer" 를 입력합니다.

6. 이제 탐색기의 오른쪽 메뉴에 DOS를 사용할 수 있습니다.

이 글은 스프링노트에서 작성되었습니다.

09 서브버전(Subversion) 윈도우 서버 설치

 

들어가기 전에...

  • 이 글은 kkamagui에 의해 작성된 글입니다.
  • 마음껏 인용하시거나 사용하셔도 됩니다. 단 출처(https://kkamagui.tistory.com)는 밝혀 주십시오.
  • OS 제작에 대한 상세한 내용은 책 "64비트 멀티코어 OS 구조와 원리"를 참고하시기 바랍니다.
64비트 멀티코어 OS 원리와 구조

0.시작하면서...

 개발자라면 한번쯤 소스 관리에 대해서 고민해 봤을 것이다. 특히나 수정이 잦은 프로그램이라면 더욱 더 버전 관리가 중요한데, 이걸 일일이 폴더나 날짜별로 관리하다가는 낭패를 보기 십상이다. 그래서 버전관리 프로그램을 사용하는데, 대표적인 것으로 소스세이프(Source Safe), CVS, Subversion 있다.

 윈도우 프로그래머라면 소스세이프가 굉장히 유용한데(Visual Studio를 설치하면 자동으로 깔리므로... ㅡ_ㅡa..), 사용해보니 속도가 무지 느리고 VC 또한 한참 뒤에 뜨게하는 무시무시한 단점이 있어서 다른 것을 찾다가 서브버전을 선택하게 되었다.

 서브버전의 장점은 인터넷에 잘 나오므로 굳이 이야기하지 않겠고, 실제 윈도우 버전 설치 및 설정에 대해서 알아보자.

 

1.Tortoise 서브버전(Subversion) 클라이언트 설치

1.1 Tortoise 서브버전(Subversion) 클라이언트 다운로드

 서브버전 서버를 설치한다면서 왜 클라이언트를 설치하는 것일까? 그것은 서버 설치후 나머지 작업을 편리하게 할 수 있기 때문이다.

 서브버전 클라이언트는 http://tortoisesvn.net/downloads 에서 다운 받을 수 있다.

Tortoise 서브버전 클라이언트

1.2 Tortoise 서브버전(Subversion) 클라이언트 설치

 클라이언트 설치는 간단하다. 무조건 "Next" 버튼을 눌러서 완료를 하면 알아서 다 해준다.

Tortoise 서브버전(Subversion) 클라이언트 설치

 설치 후 탐색기에서 오른쪽 버튼을 클릭했을 때 아래와 같은 메뉴가 뜨면 정상적으로 설치된 것이다. 

설치 완료

 

2.서브버전(Subversion) 서버 설치

2.1 서브버전(Subversion) 서버 다운로드

 서브버전의 윈도우용 설치 파일은 http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=91 에서 찾을 수 있다. 위 사이트로 이동하면 아래와 같은 화면을 볼 수 있는데, 최신버전의 설치 파일을 다운받아서 설치하도록 하자.

서브버전 서버 파일 다운로드

 

2.2 서브버전(Subversion) 설치

 설치 과정은 아주 간단하다. 윈도우 인스톨 파일을 더블클릭해서 기본 옵션으로 설치하면 된다. "Next" 를 계속해서 클릭하여 완료하자.

인스톨 화면

2.3 서브버전(Subversion) 설정

2.3.1 서브버전(Subversion) 데이터 폴더 생성(Repository)

 서버 설치가 끝났으니 데이터를 저장할 폴더를 생성해야한다. 저장소는 하드디스크의 특정 폴더로 하면 되고, 임의의 이름으로 선택 가능하다. 일단 D:\Repository로 해서 생성하자.

저장소 생성

2.3.2 서비스(Service) 등록

 서브버전 서버를 서비스로 등록하여 윈도우 부팅시에 자동으로 실행되도록 하자. 윈도우 서비스 등록은 sc.exe 프로세스로 등록가능하다. cmd.exe를 실행해서 아래와 같이 입력하도록 하자. 

서비스 등록

 아래는 서브버전 서비스를 등록하고 서비스를 해제하는 명령이다.

서비스 등록 : sc create svn binpath= "C:\Program Files\Subversion\bin\svnserve.exe --service -r D:\repository" displayname= "Subversion Server" depend= Tcpip start= auto
서비스 해제 : sc delete svn displayname= "Subversion Server"

 별다른 문제가 없다면 성공적으로 등록했다는 메시지가 출력될 것이다.

2.3.3 방화벽(Firewall) 해제

 서브버전은 TCP 3690 포트와 UDP 3690 포트를 사용한다. 윈도우 방화벽 및 Anti-Virus의 방화벽을 해제하도록 하자.

 아래는 윈도우 방화벽에서 포트를 추가하는 방법이다. TCP와 UDP 각각 등록해서 모두 가능하도록 하자.

방화벽 해제

2.4 저장소(Repository) 생성

 이제 서버 설정이 끝났으니 실제로 소스 또는 데이터를 관리할 저장소(Repository)를 생성해야 한다. 서브버전 관련 데이터는 D:\Repository 에 저장하기로 했으므로 하위 폴더에 저장소를 생성하자.

 

2.4.1 커맨드 라인(Command Line) 방식

 cmd.exe 를 실행한 뒤 D:\Repository 폴더로 이동하여 아래와 같이 입력한다.

"C:\Program Files\Subversion\bin\svnadmin" create --fs-type fsfs test

 위의 파란색으로 표시된 test를 유의해서 보자. test 대신에 생성을 원하는 폴더명으로 바꿔서 입력하면 된다.

 아래는 위의 명령을 실행한 후 결과 화면이다.

저장소 생성

2.4.2 Tortois Subversion 클라이언트를 사용한 방식

 Tortois Subversion 클라이언트를 설치했다면 좀더 편한 방법으로 생성할 수 있다. 아래는 Tortoise Subversion 클라이언트를 통해 생성하는 방법이다. 

저장소 생성

D:\Repository 폴더에 생성할 저장소 이름(Test)의 폴더를 미리 생성한 후 Tortoise Subversion 클라이언트에서 "Create repository here"를 클릭하면 된다.

 파일 시스템을 선택하는 다이얼로그가 뜨면 "Native filesystem(fsfs)"를 선택한 후 OK를 눌러서 생성하면 된다.

 

2.5 저장소 접근 설정

 저장소를 생성하고 나면 아래와 같은 폴더와 파일들이 생긴다.

저장소 폴더 상태

 이 중에서 접근 권한을 제어하기위해서는 2개의 파일을 손봐야 하는데 다음 항목을 보자

 

2.5.1 svnserve.conf

### This file controls the configuration of the svnserve daemon, if you
### use it to allow access to this repository.  (If you only allow
### access through http: and/or file: URLs, then this file is
### irrelevant.)
### Visit http://subversion.tigris.org/ for more information.\

[general]
### These options control access to the repository for unauthenticated
### and authenticated users.  Valid values are "write", "read",
### and "none".  The sample settings below are the defaults.

anon-access = none     <== 로그인 하지 않은 사용자는 아무것도 못하도록 한다.
auth-access = write
### The password-db option controls the location of the password
### database file.  Unless you specify a path starting with a /,
### the file's location is relative to the conf directory.
### Uncomment the line below to use the default password file.

password-db = passwd   <== ID와 Password를 저장하는 파일 이름

### The authz-db option controls the location of the authorization
### rules for path-based access control.  Unless you specify a path
### starting with a /, the file's location is relative to the conf
### directory.  If you don't specify an authz-db, no path-based access
### control is done.
### Uncomment the line below to use the default authorization file.
#authz-db = authz
### This option specifies the authentication realm of the repository.
### If two repositories have the same authentication realm, they should
### have the same password database, and vice versa.  The default realm
### is repository's uuid.

realm = KKAMAGUI Repository   <== 접근했을 때 클라이언트에게 보여줄 저장소 메시지

 위와 같이 파일을 수정한 다음 저장한다.

 

2.5.2 passwd

### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.
[users]
# harry = harryssecret
# sally = sallyssecret
kkamagui = 사용할 패스워드

 위와 같이 ID = Password의 형태로 입력한 뒤 저장하면 된다.

 

3.간단한 서브버전(Subversion) 서버 테스트

 테스트 방법은 간단하다. 탐색기에서 오른쪽 버튼을 눌러서 표시되는 메뉴에서 "Repo-Browser" 를 클릭하여 아래와 같은 창이 뜨면 서브버전 서버가 설치된 주소와 저장소 이름을 같이 입력해주면 된다.

Repo-browser 메뉴

 이제 주소와 저장소의 이름을 입력하자. 주소를 kkamagui.egloos.com, 그리고 저장소를 test라고 가정하고 입력하면 아래와 같이 될 것이다.

서브버전 주소 입력

"OK" 버튼을 누르면 실제 서버에 접속해서 저장소 정보를 얻어오는데, 아래와 같은 화면이 표시될 것이다. 실제로 정상적으로 접속이 된다면 "test" 항목 아래에 아무것도 표시되지 않을 것이지만 문제가 발생한다면 아래와 같이 에러메시지가 표시될 것이다.

Repo-Browser 실행-오류 발생

 위와 같은 에러 메시지가 표시되면 처음 단계부터 설정을 다시 한번 확인하자. 아무런 에러 메시지가 없다면 정상적으로 설치된 경우이므로 열심히 Check-out, commit, update를 반복하면 된다.

 

4.서브버전(Subversion) 서버를 설치하지 않고 로컬(Local)에서 소스 관리하기

 서브버전 서버가 설치되어있어야 꼭 소스 버전 관리가 가능한 것일까? "답은 그렇지 않다" 이다.

 Tortoise Subversion 클라이언트를 설치했다면 로컬에 저장소를 만들고 file:/// 키워드로 접근하여 소스 버전을 관리하는 것이 가능하다.

4.1 저장소 생성

 위의 "2.4 저장소(Repository) 생성" 부분을 참고해서 로컬에 Tortoise를 이용하여 저장소를 생성하자. 그리고 파일들을 수정해서 특정 유저만 접근가능하도록 수정하자.

4.2 저장소 접근 테스트

 위의 "3.간단한 서브버전(Subversion) 서버 테스트" 부분을 참고하여 "Repo-browser"를 실행하고 주소에 아래와 같이 입력한 후 OK를 누르자.

file:///d:\repository\test

 정상적으로 실행되면 아래와 같은 화면이 표시될 것이다.

Repo-browser 실행
 아무런 에러가 없으므로 정상적으로 실행되었음을 알 수 있다. 이렇게 함으로써 서버를 설치하지 않고도 로컬에서 소스 버전관리를 할 수 있다.

5.기타 팁

5.1 버전 관리시 무시할 파일 확장자 설정

 소스를 컴파일해서 나오는 object 파일이나 기타 필요없는 부산물들은 버전관리를 할 필요가 없다. 그런 파일들을 일일이 수작업으로 제외하기는 상당히 귀찮은 작업인데, 다행이 Tortoise에서 이것을 편리하게 할 수 있는 옵션이 있다.

 "Settings" 메뉴에 가면 아래와 같은 화면이 표시된다. 여기에 "Global Ignore Pattern" 항목에 무시할 파일의 확장자나 파일명을 입력하면 된다.

*.scc *.sbr *.pch *.pcb *.ilk *.idb *.res *.o *.obj *.ncb *.opt *.plg
확장자 및 파일명 입력

 

6.마치면서...

 이로서 그 지긋지긋하게 느린 소스세이프(SourceSafe)에서 벗어날 수 있게 되었다(이렇게 좋을 수가... ㅜ_ㅜ). 소스 버전 관리를 통해 프로젝트를 보다 효율적으로 관리하고 협업의 능률을 최대한 활용하자. @0@)/~~!!

 

7.참고 사이트

설치에 대해서 아주 자세하게 잘 나와있다. 부족한 부분은 여기서 참고하도록 하자.

08 BSD 소켓 프로그래밍(Socket Programming) 예제

원문 : http://kkamagui.springnote.com/pages/551093

 

들어가기 전에...

 

0.시작하면서...

 BSD 소켓 프로그래밍은 MFC 소켓에 비하면 쓰기가 까다롭고 알아야할 부분이 많다. 특히나 윈도우 프로그래밍을 하면 MFC 소켓에 익숙해지기때문에 까먹기가 십상이다. 

 이번에 NDS 소켓 프로그래밍을 하면서 우연히 다시 볼 기회가 생겨 정리한다.

 

1.참고 함수들 

1.1 select 함수 

 Single Thread로 Multi-Socket을 컨트롤 하는 방법은 여러가지가 있겠지만, 가장 대표적인 것이 select이다. select는 아래와 같은 원형을 가지고 있다.

  1. int select(
      int nfds,
      fd_set FAR* readfds,
      fd_set FAR* writefds,
      fd_set FAR* exceptfds,
      const struct timeval FAR* timeout
    ); 

 select 함수는 nfds에 설정된 소켓의 수만큼 소켓을 체크하므로 반드시 가장 큰 소켓 번호 + 1의 크기만큼을 nfds로 넘겨줘야 함을 잊지 말자( ex: fd + 1 )

 return 값은 아래와 같은 의미를 가진다. 

  • 양수 : readfds or writefds or exceptfds 중에 양수 개의 fd가 이벤트가 발생했다.

    • fds에 이벤트가 발생한 fd만 플래그가 설정되므로 FD_ISSET 매크로를 이용해서 해당 socket을 찾을 수 있다.
    • timeout에 남은 시간이 저장되므로 이를 활용하면 추가적인 처리가 가능하다
  • 0 : timeout이 되었다. timeout 값의 경우 0으로 설정되면 무한대로 대기한다. 
  • 음수 : readfds에 닫힌 소켓이 있거나 기타 에러가 발생했다.

 

 fd_set 및 timeval은 구조체로 아래와 같은 형태를 가진다. 

  1. typedef struct fd_set {
      u_int fd_count;
      SOCKET fd_array[FD_SETSIZE];
    } fd_set; 
  2. struct timeval {
      long tv_sec;  // second 단위
      long tv_usec; // millisecond 단위
    }; 

 timeval은 위에서 보는 것 그대로 second/millisecond 단위로 설정해서 사용하면 된다.

 

 하지만 fd_set과 같은 경우 어떻게 사용해야할지 좀 막막하다. 다행이 이를 처리해주는 매크로가 있으니 아래와 같다.

  • FD_ZERO( fd_set* fdset ) :  fdset을 초기화. 처음에는 반드시 한번 호출해야 함
  • FD_SET( int fd, fd_set* fdset ) : fdset에 fd 소켓을 등록한다. 
  • FD_CLR( int fd, fd_set* fdset ) : fdset에 fd 소켓을 삭제한다.
  • FD_ISSET( int fd, fd_set* fdset ) : fdset에 fd 소켓이 있는가 확인한다. 

 자주 사용하는 매크로이니 한번쯤은 읽어두자. 자세한 사용 방법은 아래의 Linux 예제를 보면 된다.

 

2.윈도우(Window) 환경

2.1 서버(Server)

  1. #include <winsock2.h>
    #include <stdio.h>
    #include <string.h>
  2. #define DEFAULT_PORT 2924
    #define DEFAULT_BUFFER_SIZE 4096
  3. int main()
    {
        char Buffer[DEFAULT_BUFFER_SIZE + 1];
        WSAData wsd;
  4.     if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
            printf("Winsock 초기화 에러!\n");
            return -1;
        }
  5.     SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  6.     if (ls == INVALID_SOCKET) {
            printf("소켓 생성 실패!\n");
            return -1;
        }
  7.     sockaddr_in service;
        memset(&service, 0, sizeof(service));
        service.sin_family = AF_INET;
        service.sin_addr.s_addr = INADDR_ANY;
        service.sin_port = htons(DEFAULT_PORT);
       
        if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {
            printf("bind 실패!\n");
            return -1;
        }
       
        if (listen(ls, 1) == SOCKET_ERROR) {
            printf("listen() 실패!\n");
            return -1;
        }
  8.     SOCKET as;
  9.     printf("클라이언트 연결 대기.\n");
  10.     while (1) {
            as = accept(ls, NULL, NULL);
            if (as == SOCKET_ERROR) continue;
            printf("클라이언트 연결됨.\n");
  11.         int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
  12.         if (nbyte <= 0) {
                printf("recv 에러!\n");
                break;
            }
           
            Buffer[nbyte] = '\0';
            printf("에코 : %s\n", Buffer);
  13.         send(as, Buffer, nbyte, 0);
  14.         if (strncmp(Buffer, "quit", 4) == 0) {
                printf("클라이언트의 요청에 의해 서버 종료\n");
                break;
            }
           
            closesocket(as);
            printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");
        }
  15.     closesocket(ls);
        WSACleanup();
       
        return 0;
    }

 

2.2 클라이언트(Client)

  1. #include <winsock2.h>
    #include <stdio.h>
    #include <string.h>
  2. #define DEFAULT_PORT 2924
    #define DEFAULT_BUFFER_SIZE 4096
  3. int main(int argc, char** argv)
    {
        char Buffer[DEFAULT_BUFFER_SIZE + 1];
        WSAData wsd;
  4.     if (argc != 2) {
            printf ("사용법 : %s [IP 주소]\n", argv[0]);
            return -1;
        }
       
        if(WSAStartup(MAKEWORD(2, 2), &wsd) != 0) {
            printf("Winsock 초기화 에러!\n");
            return -1;
        }
       
        SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
       
        if (cs == INVALID_SOCKET) {
            printf("소켓 생성 실패!\n");
            return -1;
        }
       
        sockaddr_in client;
        memset(&client, 0, sizeof(client));
     
        client.sin_family = AF_INET;
        client.sin_addr.s_addr = inet_addr(argv[1]);
        client.sin_port = htons(DEFAULT_PORT);
       
        if (connect(cs, (SOCKADDR *)&client, sizeof(client)) == SOCKET_ERROR) {
                printf("connect 에러!\n");
                return -1;
        }
  5.     printf("입력 : ");
        gets(Buffer);
  6.     send(cs, Buffer, strlen(Buffer), 0);
        int nbyte = recv(cs, Buffer, DEFAULT_BUFFER_SIZE, 0);
  7.     Buffer[nbyte] = '\0';
        printf("에코 : %s", Buffer);
  8.     closesocket(cs);
        WSACleanup();
       
        return 0;
    }

 

3.Linux or Unix or NDS

3.1 서버(Server) 

 윈도우쪽 소스를 조금 수정했다. 

  1. #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
  2. int main()
    {
        char Buffer[256 + 1];
  3.     int ls = socket(AF_INET, SOCK_STREAM, 0);
        if (ls == INVALID_SOCKET) {
            printf("소켓 생성 실패!\n");
            return -1;
        }
  4.     sockaddr_in service;
        memset(&service, 0, sizeof(service));
        service.sin_family = AF_INET;
        service.sin_addr.s_addr = INADDR_ANY;
        service.sin_port = htons(DEFAULT_PORT);
       
        if (bind(ls, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) {
            printf("bind 실패!\n");
            return -1;
        }
       
        if (listen(ls, 1) == SOCKET_ERROR) {
            printf("listen() 실패!\n");
            return -1;
        }
        int as;
        printf("클라이언트 연결 대기.\n");
        while (1) {
            as = accept(ls, NULL, NULL);
            if (as == SOCKET_ERROR) continue;
            printf("클라이언트 연결됨.\n");
            int nbyte = recv(as, Buffer, DEFAULT_BUFFER_SIZE, 0);
            if (nbyte <= 0) {
                printf("recv 에러!\n");
                break;
            }
           
            Buffer[nbyte] = '\0';
            printf("에코 : %s\n", Buffer);
            send(as, Buffer, nbyte, 0);
            if (strncmp(Buffer, "quit", 4) == 0) {
                printf("클라이언트의 요청에 의해 서버 종료\n");
                break;
            }
           
            close(as);
            printf("클라이언트 연결 해제.\n새로운 클라이언트 연결 대기.\n");
        }
        close(ls);
       
        return 0;
    }

 

3.2 클라이언트(Client)

 NDS에서 사용하는 예제를 조금 수정했다.

  1. #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
  2. int main(void)
    {
  3.     //////////////////////////////////////////////////////////////////////////
        // Let's send a simple HTTP request to a server and print the results!
  4.     // store the HTTP request for later
        const char * request_text =
            "GET / HTTP/1.1\r\n\r\n";
    //        "Host: www.akkit.org\r\n"
    //        "User-Agent: Nintendo DS\r\n\r\n";
  5.     // Find the IP address of the server, with gethostbyname
        // DNS를 이용해서 Name으로 IP를 얻는다.
        // 2007/10/24 현재, 아직 잘 안되는 것 같다.
        iprintf( "DNS Resolve Start\n" );
        //struct hostent * myhost = gethostbyname( "www.google.org" );
        //iprintf("Found IP Address![www.google.org] [%08X]\n",
        //        myhost->h_addr_list[0] );
     
        // Tell the socket to connect to the IP address we found, on port 80 (HTTP)
        struct sockaddr_in sain;
        sain.sin_family = AF_INET;
        sain.sin_port = htons(80);
        // Host Resolve가 끝났으면 아래와 같이 사용한다.
        //sain.sin_addr.s_addr= *( (unsigned long *)(myhost->h_addr_list[0]) );
        // 아래는 google의 IP 주소이다.
        sain.sin_addr.s_addr = inet_addr( "72.14.235.99" );   
       
        // Create a TCP socket
        int my_socket;
        fd_set readfd;
        fd_set tempfd;
        struct timeval stTime;
        struct timeval stTempTime;
        int iRet;
       
        stTime.tv_sec = 5;
        stTime.tv_usec = 0;
    Retry:
       
        my_socket = socket( AF_INET, SOCK_STREAM, 0 );
        iprintf("Created Socket!\n");
       
        iprintf( "Try To Connect\n" );
        connect( my_socket,(struct sockaddr *)&sain, sizeof(sain) );
        iprintf("Connected to server!\n");
  6.     // send our request
        send( my_socket, request_text, strlen(request_text), 0 );
        iprintf("Sent our request!\n");
  7.     // Print incoming data
        iprintf("Printing incoming data:\n");
  8.     int recvd_len;
        char incoming_buffer[256];
  9.     iprintf("Recv Start\n");
        FD_ZERO( &readfd );
        FD_SET( my_socket, &readfd );
        while( 1 )
        {
            tempfd = readfd;
            stTempTime = stTime;
            iRet = select( my_socket + 1, &tempfd, NULL, NULL, &stTempTime );
            if( iRet > 0 )
            {
                recvd_len = recv( my_socket, incoming_buffer, 255, 0 );
                iprintf("Recv End Size[%d]\n", recvd_len );
                if( recvd_len > 0 )
                { // data was received!
                    incoming_buffer[ recvd_len ] = 0; // null-terminate
                    iprintf( incoming_buffer );
                }
            }
            // Time Expired
            else if( iRet == 0 )
            {
                iprintf( "Time Expired If You Press B, Exit Receiving Process\n" );
                if( ~REG_KEYINPUT & KEY_B )
                {
                    break;
                }
            }
            else
            {
                iprintf( "Error~~!!\n" );
                break;
            }
        }
       
        iprintf("Other side closed connection!\n");
  10.     shutdown(my_socket,0); // good practice to shutdown the socket.
  11.     close(my_socket); // remove the socket.
       
        iprintf( "Press Any A Key To Retry\n" );
        while( REG_KEYINPUT & KEY_A ) ;
        
        goto Retry;
  12.     while(1);
  13.     return 0;
    }

 

4.마치면서... 

 간단하게나마 코드 조각을 정리해 보았다. 이제 구글링할 필요없이 바로 붙여넣고 쓰면된다. 다시한번 네트워크의 세계로 빠져보자. 

 

 

이 글은 스프링노트에서 작성되었습니다.

07 이클립스(Eclipse) 단축키 및 환경설정

원문 : http://kkamagui.springnote.com/pages/432649

 

들어가기 전에...

 

0.시작하면서...

 이클립스 단축키 및 환경설정은 Window -> Preferences 메뉴를 통해 설정가능하다. 여기에 설정된 내용은 프로젝트와 관계없이 Global 하게 설정되는 내용이며 해당 프로젝트에만 특수하게 적용되는 설정은 Project -> Properties 메뉴를 통해서 설정가능하다.

 그럼 이제부터 이클립스 환경을 Visual Studio 환경과 비슷하게 설정하는 방법을 알아보자.

 

 

1.윈도우 폰트 설정

 설정1.PNG

<폰트 설정 화면>

  우측의 Basic 항목에서 Text Font와 C/C++에서 Editor Font를 수정하면 된다.

 

 

2.단축키 설정

2.1 설정 윈도우

 단축키 설정은 아래의 화면에서 설정가능하다.

 설정2.PNG

<단축키 설정화면>

우측 상단에 에디트 박스에는 단축키 설정 항목에 대한 검색을 수행할 수 있는 에디트 박스가 있다. 위 항목에 설정하고 싶은 검색어를 넣으면 해당 항목이 포함된 부분만 필터링하여 표시해 준다. 주의할 점은 아래의 Include unbound commands 체크 박스를 설정하여서 모든 기능에 대해서 검색이 수행되게 해야 한다는 것이다. 위 체크박스를 클릭하지 않으면 이미 단축키가 할당된 항목에서만 검색을 하므로 단축키가 설정되지 않은 기능에 대해서는 검색할 수 없다.

 검색의 결과로 나온 항목 중에 필요한 항목을 클릭한 후 제일 아래의 Binding: 에디트 박스를 클릭하여 포커스를 이동한 후 입력하고 싶은 키를 누르면 단축키로 설정된다.

 

2.2 단축키 참고

 Visual Studio와 비슷하게 동작하기 위해서는 아래의 각 항목을 검색하여 단축키를 수정하면 된다(개인적인 취향이 포함된 설정이라 참고만 하도록하자.)

 간혹 설정한 단축키가 먹지 않는 경우가 있는데, 중복 설정된 경우에 흔히 발생하므로 해당 키로 다시 검색하여 다른 기능에 맵핑된 키를 삭제하면 된다.

  • Build Project : F5
  • Clean Project : F6
  • Toggle Source/Header : ALT + O 
    • 소스파일과 헤더를 번갈아 가면서 보여주는 옵션
  • Next Editor : Control + Tab
    • 에디터 탭간의 이동
  • Content Assist : Control + Enter
    • Visual Studio의 자동완성과 같은 기능, 아래의 Content Assist 설정 참조
  • Bookmark Plugin 관련 : Bookmark 플러그인 설치에 대해서는 아래의 북마크 플러그인 참조
    • Go to next bookmark : F2
    • Go to previous bookmark : Shift + F2
    • Toggle bookmark : Control + F2
  • Show Outline : Control + O
    • 해당 파일에 있는 모든 함수 및 변수 표시
  • Open Declaration : F3 
    • 정의로 이동

 

 

3.Content Assist 설정

3.1 Indexer 설정

 Content Assist 기능을 사용하려면 Indexer를 이용하여 인덱스를 생성해야 한다. 아래는 Indexer를 설정하는 예제이다.

설정6.PNG

<Indexer 설정>

 메모리 여유가 좀 있다면 위와 같이 Full로 설정하는 것이 좋다. 파일이 변경되는 즉시 업데이트되어 아주 편리하게 사용할 수 있다.

 

3.2 Content Assist 설정

 ., ->, ::와 같은 확장 가능한 문자열 다음에 얼마의 Delay 뒤에 Content Assist를 표시할 것인지를 설정한다.

설정7.PNG

<Content Assist 설정>

 

3.3 단축키 설정

Zelon 님의 제보로 키보드 타입을 USB 3으로 할 시에 Control + Space 가 한자 키로 맵핑되어 안되었다는 사실을 알았다.

Zelon 님께 감사드린다.

 Control + Enter로 수정하면 잘 동작한다.

Eclipse.PNG

<Content Assist 단축키 설정>

 

 

 

4.플러그인(Plugin) 설치

4.1 북마크 플러그인(Bookmark Plugin) 설치

 이클립스의 북마크는 Visual Studio의 북마크보다 약간 불편하다. 북마크를 생성할 때마다 이름을 입력해야 하며 Quick하게 이동하기도 불편하게 되어있다. 이러한 기능을 보강하고자 나온 것이 북마크 플러그인이다. http://wimy.com/tt/106에서 내용을 찾아볼 수 있다.


http://etc.to/eclipse_bookmarks_plugin에 보면 아래와 같은 기능 설명과 업데이트 경로가 나온다.

I just created my second Eclipse plugin which adds several bookmark methods to the editor. The "default" bookmark methods of Eclipse ask for a name and this is often not very useful. The code here adds bookmarks a la "Borland" and bookmarks a la "JCreator". The code has the following options which can all be bound/rebound with the keys dialog under the "Edit" category:

  • Add a numbered bookmark at the cursor: Alt+[digit]. This creates a quick bookmark with the specified number using a "single" keypress. Only one bookmark of the given number can be present in the workspace. Setting the same number bookmark again will clear the earlier instance.
  • Goto a numbered bookmark: Alt+Shift+[digit]. Moves to the bookmark as it was set using Alt+[digit].
  • Toggle Bookmark: Ctrl+B. This drops an unnamed bookmark at the current location. Pressing Ctrl+B again releases the bookmark at that location again. This is often used with "Goto next bookmark" and "Goto previous bookmark".
  • Goto next bookmark in file: Ctrl+N. This locates the next bookmark in the current file and moves there.
  • Goto previous bookmark in file: Ctrl+P. This locates the previous bookmark in the current file and moves there. warning: the Ctrl+P binding is a suggestion only; since Ctrl+P is currently assigned you must add your own key binding using Window -> Preferences -> General -> Keys.

I'll be posting the source of the plugin later on; the plugin can be installed using the update site

http://eclipse.etc.to/updates/

 설치 방법은 아래와 같이 메뉴에서 Install/Update를 이용하면 된다.

설정3.PNG

<Plugin 설치메뉴>

 

 위 메뉴를 클릭하면 Install/Update 다얼로그가 표시되는데 Search for new features to install 항목을 선택하여 Next 버튼을 누르면 아래와 같은 화면이 표시된다.

설정4.PNG

<새로운 사이트 추가>

 우측 상단의 New Remote Site를 클릭하고 다이얼로그를 위와 같이 체운 후 OK 버튼을 누르면 etc.to 사이트가 추가된다. 북마크 플러그인을 설치하기위해서는 Java Development Tools(JDT)을 설치해야 하므로 Europa Discovery Site 항목을 체크한 뒤에 Finish 버튼을 클릭한다.

 잠시 시간이 지나면 아래와 같은 검색 결과가 표시되는데 Java Development Tools를 체크하여 Next 버튼을 누르면 JDT가 설치된다.

설정5.PNG

<JDT 설치화면>

 JDT의 설치가 끝나면 동일한 방법으로 etc.to 항목을 클릭하고 검색결과로 나온 Eclipse Editor Extentions를 설치하면 정상적으로 완료된다. 북마크 키에 대한 설정은 위의 단축키 설정을 참고하자.

 

5.기타 기능

5.1 빌드 시에 수정된 파일 자동으로 저장하기

 Eclipse1.PNG

 

5.2 필요없는 자동완성 막기

설정8.PNG

 

5.3 소스 폴딩(Folding) 기능 설정

설정9.PNG

 

 

 

5.4 탭(Tab)을 공백(Space)로 변경 및 80 컬럼(Column) 가이드 라인 설정

 

설정10.PNG

 

 

6.마치면서...

 이상으로 이클립스의 단축키 설명 및 환경 설정방법에 대해 알아보았다. 윈도우 프로그래밍에는 Visual Studio를 사용하고 기타(임베디드 or 자바 or 기타 등등) 프로그래밍에는 이클립스를 사용하여 프로그래밍을 편리하게 하자. @0@)/~

이 글은 스프링노트에서 작성되었습니다.

06 이클립스(Eclipse) CDT 설치

원문 : http://kkamagui.springnote.com/pages/446531

 

들어가기 전에...

 

0.시작하면서..

 이클립스는 Java기반의 개발환경으로써 편리한 Java 프로그램 개발을 위한 IDE였다. 그러나 다양한 플러그인이 개발되면서 점점 범위를 넓혀가고 있으며 다양한 언어(C/C++, Java, PHP 등등)을 지원하고 있다. 윈도우 프로그래머에게 대중적으로 사용되고 있는 Visual Studio 만큼의 강력한 Editor와 자동완성 기능을 가지고 있으므로 편리하게 프로그래밍 할 수 있으며, 다양한 툴 체인을 설정할 수 있으므로 윈도우 프로그래밍 외의 다른 용도로도 사용 가능하다.

 그럼 이제부터 설치 방법에서 설정까지 전 과정을 알아보자.

 

1.툴체인(Tool Chain) 설치

 이클립스 자체는 컴파일러와 링커를 포함하고 있지 않으므로 따로 설치해야 한다. 많이 사용하는 툴 체인인 Cygwin이나 MingW을 설치하거나 djgpp와 같은 DOS용 GCC같은 컴파일러를 설치하도록 한다. 나는 도스용 GCC인 DJGPP를 설치하였으므로 DJGPP를 기준으로 설명하겠다.

 

2.이클립스 설치

2.1 이클립스 다운로드 및 설치

 이클립스는 http://www.eclipse.org/downloads/ 에서 다운 받을 수 있고, 각 언어에 맞는 개발 suit를 받으면 된다.

이클립스1.PNG

<이클립스의 다양한 언어 지원>

 C/C++ 언어를 주로 사용하므로 C/C++ 버전인 CDT를 다운받아서 설치하도록 하자. 이클립스 사이트에서 Eclipse IDE for C/C++ Developers 버전을 다운받은 후 간단히 압축을 풀어서 특정 폴더로 이동하면 설치가 끝난다. 별도의 설치 프로그램은 없고 Java Runtime Environment(JRE)가 필요한데, Sun 사이트에서 받을 수 있다.

 

 

2.2 Java Runtime Environment(JRE) 설치

 이클립스는 Java 환경의 개발툴이므로 JRE가 필요하다. JRE는 http://java.sun.com/javase/downloads/index.jsp에서 최신버전을 다운 받을 수 있으니 받아서 설치하면 된다.

이클립스2.PNG

 <Java Download>

 

 

3.이클립스 실행

 이클립스 폴더에 있는 eclipse.exe를 실행시키면 이클립스 IDE를 실행할 수 있다.

이클립스3.PNG

<이클립스 실행화면>

 이클립스를 처음 실행하는 경우는 아래와 같은 화면이 보이는데, 워크벤치(Workbench)를 생성하고 나면 별로 볼일이 없다.

 이클립스4.PNG

<처음 실행화면>

 제일 오른쪽에 있는 아이콘을 클릭하여 워크벤치(Workbench)로 이동하자. 워크벤치(Workbench)로 이동하면 아래와 같은 화면이 보인다.

 이클립스5.PNG

<이클립스 프로젝트화면>

 왼쪽에 보이는 것이 프로젝트를 표시하는 뷰이고 가운데 부분이 코드를 표시해 주는 코드 에디터 부분이다. 오른쪽 부분은 함수를 보여주거나 하는 기능을 하며 아래 부분은 콘솔의 실행 결과 및  콘솔에서 발생한 에러를 표시해준다. 상당 부분이 Visual Studio와 비슷하니 Visual Studio에 익숙한 사람이라면 그리 낯설지 않을 것이다.

 

4.makefile 프로젝트 생성

 많이 사용하는 makefile 프로젝트를 생성해보자. File->New->Project를 이용해서 생성할 수 있다.

이클립스6.PNG

<새 프로젝트 생성>

 

이클립스7.PNG이클립스8.PNG

<프로젝트 설정> 

 위와 같이 프로젝트 이름을 적당히 입력해주고 아래의 Hellow World C++ Project 로 설정한 뒤 Finish를 누르면 프로젝트가 생성된다. 프로젝트의 경로를 수정하려면 Use default location 체크 박스를 해제하고 경로를 입력해 주면 된다.

이클립스9.PNG

<결과화면>

 프로젝트를 생성하였으니 프로그램을 빌드하여 실행할 차례이다. 좌측에 Project Explorer에 있는 makefile을 더블클릭하여 열어보면 아래와 같이 되어있는데, 컴파일을 위해 CXX를 djgpp로 설정한 후 Project 메뉴에 Build All 이나 Build Project를 누르면 build가 되어 Test1.exe 파일이 생성된다.

  1. CXXFLAGS = -O2 -g -Wall -fmessage-length=0
    CXX = djgpp <== 추가된 부분
    OBJS =  Test1.o
  2. LIBS =
  3. TARGET = Test1.exe
  4. $(TARGET): $(OBJS)
     $(CXX) -o $(TARGET) $(OBJS) $(LIBS)
  5. all: $(TARGET)
  6. clean:
     rm -f $(OBJS) $(TARGET)

 아래는 Build 후 결과 화면이다.

이클립스10.PNG

 <Build 실행 결과>

좌측의 Project Explorer를 통해 Test1.exe 파일을 실행해 볼 수 있다.

 

5.단축키 설정 및 환경설정

 이클립스 환경은 Visual Studio와 상당히 다르기 때문에 자신의 스타일에 맞게 단축키를 설정하는 것이 필수이다. 단축키 설정에 대한 자세한 내용은 07 이클립스(Eclipse) 단축키 및 환경설정문서를 참고하도록 하자.

 

6.마치며...

 간단하게 이클립스를 설치하고 이를 활용하는 방법을 알아보았다. 단축키와 환경 설정만 자신에게 맞도록 설정한다면 이클립스는 메모장(??)을 대체하는 아주 좋은 툴이 될 수 있다. 100% 활용해서 코드 노가다를 줄이도록 하자. @0@)/~

 

7.첨부

 

 

이 글은 스프링노트에서 작성되었습니다.

05 VC 6.0으로 유니코드(UNICODE) 프로젝트 만들기

원문 : http://kkamagui.springnote.com/pages/421193

참고 : http://www.npteam.net/297

 

들어가기 전에...

 

1.생성 방법

  • 프로젝트를 생성한 다음 메뉴의 Build->Configurations로 이동해서 Add 버튼을 누른다.
  • Debug로 생성하려면 Debug_Unicode로, Release로 생성하려면 Release_Unicode로 프로젝트를 추가한다.
  • Projtect->Settings에 가서 아래와 같이 _MBCS 매크로를 _UNICODE로 수정한다.

Setting1.PNG

  •  빌드를 실행했을때 msvcrtd.lib(crtexew.obj): error LNK2001:unresolvec external symbol _WinMain@16 라는 에러가 나오면 Project->Settings에서  /entry:"wWinMainCRTStartup" 를 추가함

 Setting2.PNG

  • 다시 Build 하면 이상없이 완료 @0@)/~

 

이 글은 스프링노트에서 작성되었습니다.

04 윈도우 콘솔(console) 입출력 리다이렉션(Input/Out Redirection)

원문 : https://kkamagui.tistory.com/85

들어가기 전에...

첫번째 방법

** 원문 :** http://cafe.naver.com/winmain.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=62

HOWTO: Spawn Console Processes with Redirected Standard Handles

Q190351

SUMMARY

This article describes how to redirect the input and output of a child process that receives input from the standard input handle or sends output to the standard output handle. The Win32 API enables applications to spawn a child console process with redirected standard handles. This feature allows a parent process to send and receive the input and output of the child process.

이 글은 표준 입력 핸들에서 입력을 가져오거나 표준 출력 핸들로 출력을 보내는 자식 프로세스의 입출력을 리다이렉트(redirect)하는 방법을 기술한다. Win32 API는 애플리케이션이 자식 콘솔 프로세스를 리다이렉트된 표준 핸들과 함께 생성할 수 있도록 해준다. 이 기능은 부모 프로세스가 자식 프로세스의 입출력을 보내거나 가져오는 일을 가능하게 해준다.

NOTE: Some console based applications do not use the standard handles for their input/output (IO) operations. The Win32 API does not support redirection of these processes.

NOTE: 몇몇 콘솔 기반 애플리케이션은 입출력 처리에 있어 표준 핸들을 사용하지 않는다. Win32 API는 이러한 프로세스들의 리다이렉트를 지원하지 않는다.

MORE INFORMATION

The CreateProcess() API through the STARTUPINFO structure enables you to redirect the standard handles of a child console based process. If the dwFlags member is set to STARTF_USESTDHANDLES, then the following STARTUPINFO members specify the standard handles of the child console based process:

CreateProcess() API를 통해 넘겨지는 STARTUPINFO 구조체는 여러분이 자식 콘솔 기반 프로세스의 표준 핸들을 리다이렉트할 수 있도록 해준다. dwFlags의 숫자가 STARTF_USESTDHANDLES로 설정되면, 나머지 STARTUPINFO 멤버들은 자식 콘솔 기반 프로세스의 표준 핸들을 지정한다.

HANDLE hStdInput - Standard input handle of the child process.
HANDLE hStdOutput - Standard output handle of the child process.
HANDLE hStdError - Standard error handle of the child process.

You can set these handles to either a pipe handle, file handle, or any handle that can do synchronous reads and writes through the ReadFile() and WriteFile() API. The handles must be inheritable and the CreateProcess() API must specify that inheritable handles are to be inherited by the child process by specifying TRUE in the bInheritHandles parameter. If the parent process only wishes to redirect one or two standard handles, specifying GetStdHandle() for the specific handles causes the child to create the standard handle as it normally would without redirection. For example, if the parent process only needs to redirect the standard output and error of the child process, then the hStdInput member of the STARTUPINFO structure is filled as follows:

여러분은 이 핸들들을 파이프 핸들, 파일 핸들 또는 ReadFile()과 WriteFile() API를 통해 동기화된 읽기와 쓰기를 할 수 있는 어떠한 핸들로도 설정할 수 있다. 이 핸들들은 상속 가능해야 하며, CreateProcess API는 bInheritHandles 파라미터에 TRUE를 설정함으로써 이 상속 가능한 핸들들이 상속되도록 설정해야 한다. 부모 프로세스가 한 개나 두 개의 표준 핸들만을 리다이렉트하길 원한다면, GetStdHandle()을 그 핸들에 지정하여 자식 프로세스가 리다이렉트가 없는 것처럼 정상적으로 표준 핸들을 생성하도록 한다. 예를 들어 부모 프로세스가 자식 프로세스의 표준 출력과 에러 핸들만을 리다이렉트할 필요가 있다면, STARTUPINFO 구조체의 hStdInput 멤버는 다음과 같이 채워져야 한다:

hStdInput = GetStdHandle(STD\_INPUT\_HANDLE);

NOTE: Child processes that use such C run-time functions as printf() and fprintf() can behave poorly when redirected. The C run-time functions maintain separate IO buffers. When redirected, these buffers might not be flushed immediately after each IO call. As a result, the output to the redirection pipe of a printf() call or the input from a getch() call is not flushed immediately and delays, sometimes-infinite delays occur. This problem is avoided if the child process flushes the IO buffers after each call to a C run-time IO function. Only the child process can flush its C run-time IO buffers. A process can flush its C run-time IO buffers by calling the fflush() function.

NOTE: printf()와 fprintf()와 같은 C 런타임 함수들을 사용하는 자식 프로세스들은 리다이렉트되었을 때 이상하게 작동할 수 있다. C 런타임 함수들은 별도의 IO 버퍼들을 유지한다. 리다이렉트되었을 때, 이 버퍼들은 IO 호출이 끝날 때마다 즉시 버퍼를 비우지(flush) 않을 수도 있다. 결과적으로, printf() 호출의 리다이렉트 파이프로의 출력이나 getch() 호출로부터의 입력은 즉시 버퍼를 비우지 않고 지연되고, 얼마간에서-무한한 지연이 발생한다. 이 문제는 자식 프로세스가 C 런타임 IO 함수에 대한 호출이 끝날때마다 IO 버퍼를 비운다면 피할 수 있다. 오직 자식 프로세스만이 자신의 C 런타임 IO 버퍼를 비울 수 있다. 프로세스는 fflush() 함수를 호출함으로써 C 런타임 IO 버퍼를 비울 수 있다.

NOTE: Windows 95 and Windows 98 require an extra step when you redirect the standard handles of certain child processes. For additional information, please see the following article in the Microsoft Knowledge Base:

NOTE: 윈도우 95와 윈도우 98은 어떤 자식 프로세스의 표준 핸들을 리다이렉트할 때 추가적인 과정이 필요하다. 추가적인 정보에 대해서는 마이크로소프트 Knowledge Base에 있는 다음 글을 보기 바란다.

Q150956 INFO: Redirection Issues on Windows 95 MS-DOS Applications

The following sample redirects the standard input, output, and error of the child process specified in the CreateProcess call. This sample redirects the provided console process (Child.c).

다음 예제는 CreateProcess 호출에서 지정된 자식 프로세스의 표준 입력 출력, 에러를 리다이렉트한다. 이 예제는 제공된 콘솔 프로세스(Child.c)를 리다이렉트한다.

Sample Code

/*++
    Copyright (c) 1998  Microsoft Corporation
      Module Name:
         Redirect.c
      Description:
          This sample illustrates how to spawn a child console based
          application with redirected standard handles.
          The following import libraries are required:
          user32.lib
    Dave McPherson (davemm)   11-March-98
--*/

#include<windows.h>

void DisplayError(char \*pszAPI);
void ReadAndHandleOutput(HANDLE hPipeRead);
void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
                                  HANDLE hChildStdIn,
                                  HANDLE hChildStdErr);
DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam);
HANDLE hChildProcess = NULL;
HANDLE hStdIn = NULL; // Handle to parents std input.
BOOL bRunThread = TRUE;

void main ()
{
    HANDLE hOutputReadTmp,hOutputRead,hOutputWrite;
    HANDLE hInputWriteTmp,hInputRead,hInputWrite;
    HANDLE hErrorWrite;
    HANDLE hThread;
    DWORD ThreadId;
    SECURITY\_ATTRIBUTES sa;

    // Set up the security attributes struct.
    sa.nLength= sizeof(SECURITY\_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    // Create the child output pipe.
    if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0))
        DisplayError("CreatePipe");
    // Create a duplicate of the output write handle for the std error
    // write handle. This is necessary in case the child application
    // closes one of its std output handles.
    if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite,
                         GetCurrentProcess(),&hErrorWrite,0,
                         TRUE,DUPLICATE\_SAME\_ACCESS))
        DisplayError("DuplicateHandle");

    // Create the child input pipe.
    if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0))
        DisplayError("CreatePipe");

    // Create new output read handle and the input write handles. Set
    // the Properties to FALSE. Otherwise, the child inherits the
    // properties and, as a result, non-closeable handles to the pipes
    // are created.
    if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp,
                         GetCurrentProcess(),
                         &hOutputRead, // Address of new handle.
                         0,FALSE, // Make it uninheritable.
                         DUPLICATE\_SAME\_ACCESS))
        DisplayError("DupliateHandle");

    if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp,
                         GetCurrentProcess(),
                         &hInputWrite, // Address of new handle.
                         0,FALSE, // Make it uninheritable.
                         DUPLICATE\_SAME\_ACCESS))
        DisplayError("DupliateHandle");

    // Close inheritable copies of the handles you do not want to be
    // inherited.
    if (!CloseHandle(hOutputReadTmp)) DisplayError("CloseHandle");
    if (!CloseHandle(hInputWriteTmp)) DisplayError("CloseHandle");

    // Get std input handle so you can close it and force the ReadFile to
    // fail when you want the input thread to exit.
    if ( (hStdIn = GetStdHandle(STD\_INPUT\_HANDLE)) == INVALID\_HANDLE\_VALUE )
        DisplayError("GetStdHandle");

    PrepAndLaunchRedirectedChild(hOutputWrite,hInputRead,hErrorWrite);
    // Close pipe handles (do not continue to modify the parent).
    // You need to make sure that no handles to the write end of the
    // output pipe are maintained in this process or else the pipe will
    // not close when the child process exits and the ReadFile will hang.
    if (!CloseHandle(hOutputWrite)) DisplayError("CloseHandle");
    if (!CloseHandle(hInputRead )) DisplayError("CloseHandle");
    if (!CloseHandle(hErrorWrite)) DisplayError("CloseHandle");

    // Launch the thread that gets the input and sends it to the child.
    hThread = CreateThread(NULL,0,GetAndSendInputThread,
                           (LPVOID)hInputWrite,0,&ThreadId);
    if (hThread == NULL) DisplayError("CreateThread");

    // Read the child's output.
    ReadAndHandleOutput(hOutputRead);
    // Redirection is complete
   // Force the read on the input to return by closing the stdin handle.
   if (!CloseHandle(hStdIn)) DisplayError("CloseHandle");
   // Tell the thread to exit and wait for thread to die.
   bRunThread = FALSE;
   if (WaitForSingleObject(hThread,INFINITE) == WAIT\_FAILED)
      DisplayError("WaitForSingleObject");
   if (!CloseHandle(hOutputRead)) DisplayError("CloseHandle");
   if (!CloseHandle(hInputWrite)) DisplayError("CloseHandle");
}

///////////////////////////////////////////////////////////////////////
// PrepAndLaunchRedirectedChild
// Sets up STARTUPINFO structure, and launches redirected child.
///////////////////////////////////////////////////////////////////////
 void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut,
                                   HANDLE hChildStdIn,
                                   HANDLE hChildStdErr)
 {
    PROCESS\_INFORMATION pi;
    STARTUPINFO si;
    // Set up the start up info struct.
    ZeroMemory(&si,sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF\_USESTDHANDLES;
    si.hStdOutput = hChildStdOut;
    si.hStdInput  = hChildStdIn;
    si.hStdError  = hChildStdErr;
    // Use this if you want to hide the child:
    //     si.wShowWindow = SW\_HIDE;
    // Note that dwFlags must include STARTF\_USESHOWWINDOW if you want to
    // use the wShowWindow flags.
    // Launch the process that you want to redirect (in this case,
    // Child.exe). Make sure Child.exe is in the same directory as
    // redirect.c launch redirect from a command line to prevent location
    // confusion.
    if (!CreateProcess(NULL,"Child.EXE",NULL,NULL,TRUE,
                       CREATE\_NEW\_CONSOLE,NULL,NULL,&si,&pi))
       DisplayError("CreateProcess");
    // Set global child process handle to cause threads to exit.
    hChildProcess = pi.hProcess;
    // Close any unnecessary handles.
    if (!CloseHandle(pi.hThread)) DisplayError("CloseHandle");
 }

 ///////////////////////////////////////////////////////////////////////
 // ReadAndHandleOutput
 // Monitors handle for input. Exits when child exits or pipe breaks.
 ///////////////////////////////////////////////////////////////////////
 void ReadAndHandleOutput(HANDLE hPipeRead)
 {
    CHAR lpBuffer\[256\];
    DWORD nBytesRead;
    DWORD nCharsWritten;
    while(TRUE)
    {
       if (!ReadFile(hPipeRead,lpBuffer,sizeof(lpBuffer),
                                        &nBytesRead,NULL) || !nBytesRead)
       {
          if (GetLastError() == ERROR\_BROKEN\_PIPE)
             break; // pipe done - normal exit path.
          else
             DisplayError("ReadFile"); // Something bad happened.
       }
       // Display the character read on the screen.
       if (!WriteConsole(GetStdHandle(STD\_OUTPUT\_HANDLE),lpBuffer,
                         nBytesRead,&nCharsWritten,NULL))
          DisplayError("WriteConsole");
    }
 }

 ///////////////////////////////////////////////////////////////////////
 // GetAndSendInputThread
 // Thread procedure that monitors the console for input and sends input
 // to the child process through the input pipe.
 // This thread ends when the child application exits.
 ///////////////////////////////////////////////////////////////////////
 DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam)
 {
    CHAR read\_buff\[256\];
    DWORD nBytesRead,nBytesWrote;
    HANDLE hPipeWrite = (HANDLE)lpvThreadParam;
    // Get input from our console and send it to child through the pipe.
    while (bRunThread)
    {
       if(!ReadConsole(hStdIn,read\_buff,1,&nBytesRead,NULL))
          DisplayError("ReadConsole");
       read\_buff\[nBytesRead\] = '\\0'; // Follow input with a NULL.
       if (!WriteFile(hPipeWrite,read\_buff,nBytesRead,&nBytesWrote,NULL))
       {
          if (GetLastError() == ERROR\_NO\_DATA)
             break; // Pipe was closed (normal exit path).
          else
          DisplayError("WriteFile");
       }
    }
    return 1;
 }

 ///////////////////////////////////////////////////////////////////////
 // DisplayError
 // Displays the error number and corresponding message.
 ///////////////////////////////////////////////////////////////////////
 void DisplayError(char \*pszAPI)
 {
     LPVOID lpvMessageBuffer;
     CHAR szPrintBuffer\[512\];
     DWORD nCharsWritten;
     FormatMessage(
              FORMAT\_MESSAGE\_ALLOCATE\_BUFFER|FORMAT\_MESSAGE\_FROM\_SYSTEM,
              NULL, GetLastError(),
              MAKELANGID(LANG\_NEUTRAL, SUBLANG\_DEFAULT),
              (LPTSTR)&lpvMessageBuffer, 0, NULL);
     wsprintf(szPrintBuffer,
       "ERROR: API    = %s.\\n   error code = %d.\\n   message    = %s.\\n",
              pszAPI, GetLastError(), (char \*)lpvMessageBuffer);
     WriteConsole(GetStdHandle(STD\_OUTPUT\_HANDLE),szPrintBuffer,
                   lstrlen(szPrintBuffer),&nCharsWritten,NULL);
     LocalFree(lpvMessageBuffer);
     ExitProcess(GetLastError());
 }

 //////////////////////////////////////////////////////////////////////
 // child.c
 // Echoes all input to stdout. This will be redirected by the redirect
 // sample. Compile and build child.c as a Win32 Console application and
 // put it in the same directory as the redirect sample.
 //
 #include<windows.h>
 #include<stdio.h>
 #include<string.h>
 void main ()
 {
    FILE\*    fp;
    CHAR     szInput\[1024\];
    // Open the console. By doing this, you can send output directly to
    // the console that will not be redirected.
    fp = fopen("CON", "w");
    if (!fp) {
       printf("Error opening child console - perhaps there is none.\\n");
       fflush(NULL);
    }
    else
    {
    // Write a message direct to the console (will not be redirected).
       fprintf(fp,"This data is being printed directly to the\\n");
       fprintf(fp,"console and will not be redirected.\\n\\n");
       fprintf(fp,"Since the standard input and output have been\\n");
       fprintf(fp,"redirected data sent to and from those handles\\n");
       fprintf(fp,"will be redirected.\\n\\n");
       fprintf(fp,"To send data to the std input of this process.\\n");
       fprintf(fp,"Click on the console window of the parent process\\n");
       fprintf(fp,"(redirect), and enter data from it's console\\n\\n");
       fprintf(fp,"To exit this process send the string 'exit' to\\n");
       fprintf(fp,"it's standard input\\n");
       fflush(fp);
    }
    ZeroMemory(szInput,1024);
    while (TRUE)
    {
       gets(szInput);
       printf("Child echoing \[%s\]\\n",szInput);
       fflush(NULL);  // Must flush output buffers or else redirection
                      // will be problematic.
       if (!\_stricmp(szInput,"Exit") )
          break;
       ZeroMemory(szInput,strlen(szInput) );
    }
 }

REFERENCES

MSDN Library SDK documentation: CreateProcess(); STARTUPINFO structure
Inherit sample in the Win32 Platform SDK under:
\MSSDK\samples\winbase\ipc\inherit

Additional query words: Inheritance redirection redirected stdhandles

Keywords : kbAPI kbConsole kbIPC kbKernBase kbOSWinNT400 kbOSWin2000 kbSDKPlatform kbOSWin95 kbOSWin98 kbFAQ kbDSupport kbGrpDSKernBase
Issue type : kbhowto
Technology : kbAudDeveloper kbWin32sSearch kbWin32API

두번째 방법

원문 http://www.codeproject.com/threads/redir.asp

첨부 : **[redir_demo.zip](http://kkamagui.springnote.com/pages/410158/attachments/171448 "redir_demo.zip"), [redir_src.zip**](http://kkamagui.springnote.com/pages/410158/attachments/171449 "redir_src.zip")

Introduction

To redirect the input/output of a console application is interesting and useful. You can display the child's output in a window (just like Visual Studio's output window), or search some keywords in the output string to determine if the child process has completed its work successfully. An old, 'ugly' DOS program could become an useful component of your fancy Win32 GUI program.

My idea is to develop a simple, easy to use redirector class which can redirect an arbitrary console, and won't be affected by the behavior of the child process.

Background

The technique of redirecting the input/output of a console process is very sample: The CreateProcess() API through the STARTUPINFO structure enables us to redirect the standard handles of a child console based process. So we can set these handles to either a pipe handle, file handle, or any handle that we can read and write. The detail of this technique has been described clearly in MSDN: HOWTO: Spawn Console Processes with Redirected Standard Handles.

However, MSDN's sample code has two big problem. First, it assumes the child process will send output at first, then wait for input, then flush the output buffer and exit. If the child process doesn't behave like that, the parent process will be hung up. The reason of this is the ReadFile() function remains blocked untill the child process sends some output, or exits.

Second, It has problem to redirect a 16-bit console (including console based MS-DOS applications.) On Windows 9x, ReadFile remains blocked even after the child process has terminated; On Windows NT/XP, ReadFile always returns FALSE with error code set to ERROR_BROKEN_PIPE if the child process is a DOS application.

Solving the block problem of ReadFile

To prevent the parent process from being blocked by ReadFile, we can simply pass a file handle as stdout to the child process, then monitor this file. A more simple way is to call PeekNamedPipe() function before calling ReadFile(). The PeekNamedPipe function checks information about data in the pipe, then returns immediately. If there's no data available in the pipe, don't call ReadFile.

By calling PeekNamedPipe before ReadFile, we also solve the block problem of redirecting a 16-bit console on Windows 9x.

The class CRedirector creates pipes and launchs the child process at first. then creates a listener thread to monitor the output of the child process. This is the main loop of the listener thread:

for (;;)
    {
        // redirect stdout till there's no more data.
        nRet = pRedir->RedirectStdout();
        if (nRet <= 0)
            break;

        // check if the child process has terminated.
        DWORD dwRc = ::WaitForMultipleObjects(
            2, aHandles, FALSE, pRedir->m_dwWaitTime);
        if (WAIT_OBJECT_0 == dwRc)      // the child process ended
        {
            ...
            break;
        }
        if (WAIT_OBJECT_0+1 == dwRc)    // m_hEvtStop was signalled, exit
        {
            ...
            break;
        }
    }

This is the main loop of the RedirectStdout() function:

for (;;)
    {
        DWORD dwAvail = 0;
        if (!::PeekNamedPipe(m_hStdoutRead, NULL, 0, NULL,
            &dwAvail, NULL))    // error, the child process might ended
            break;

        if (!dwAvail)           // no data available, return
            return 1;

        char szOutput[256];
        DWORD dwRead = 0;
        if (!::ReadFile(m_hStdoutRead, szOutput, min(255, dwAvail),
            &dwRead, NULL) || !dwRead)  
                 // error, the child process might ended
            break;

        szOutput[dwRead] = 0;
        WriteStdOut(szOutput);          // display the output
    }

WriteStdOut is a virtual member function. It does nothing in CRedirector class. However it can be overrided to achieve our specific target, like I did in the demo project:

int nSize = m_pWnd->GetWindowTextLength();  
             // m_pWnd points to a multiline Edit control
    m_pWnd->SetSel(nSize, nSize);
    m_pWnd->ReplaceSel(pszOutput);      
           // add the message to the end of Edit control

To redirect DOS console based applications on NT/2000/XP

MSDN's solution is to launch an intermediate Win32 Console application as a stub process between the Win32 parent and the 16-bit console based child. In fact the DOS prompt program (on NT/XP it's cmd.exe, on 9x it's command.com) is a natural stub process we just need. We can test this in RedirDemo.exe:

  1. Input 'cmd.exe' in Command Editbox, then press Run button.
  2. Input the name of the 16-bit console based application (dosapp.exe for example) in the Input Editbox, then press Input button. Now we can see the output of the 16-bit consol.
  3. Input 'exit' in the Input Editbox, then press Input button to terminate cmd.exe

Apparently this is not a good solution because it's too complicated. A more effective way is to use a batch file as the stub. Edit stub.bat file like this:

%1 %2 %3 %4 %5 %6 %7 %8 %9

Then run a command like 'stub.bat dosapp.exe', then the 16-bit DOS console application runs OK.

About nickadams

  Nick Adams is one of my favorite figures in Hemingway's stories. I use it because Jeff Lee has been occupied on Codeproject.

Click here to view nickadams's online profile.

Other popular Threads, Processes & IPC articles:

02 간단한 Make 사용법

원문 : https://kkamagui.tistory.com/84

들어가기 전에...

0.시작하면서...

통합 개발툴(IDE)를 이용해서 개발하는 경우에 굳이 Makefile을 생성할 필요는 없지만 IDE를 사용하지 않고 개발할 경우, 많은 소스파일을 관리하기위해서 makefile이 필수이다. make는 실제로 굉장히 많은 기능을 가지고 있지만, 그중 몇가지 유용한 기능만 알아도 훨씬 코드 관리가 편리해 진다.

이제 그 몇가지에 대해서 알아보자. @0@)/~

1.makefile의 기본 형식

makefile은 기본적으로 아래와 같은 형식으로 이루어진다.

Target File : Source File(Dependency) 
    <tab> command

좌측의 Target File은 make command의 실행 결과로 생성할 파일이고 Source File은 make command에 사용될 파일이다. 아래의 command는 탭으로 한칸 띄우는데 이 탭이 command의 구분자의 역할을 한다. 만약 Abc.c 파일을 gcc -c로 컴파일 해서 Abc.o 파일을 생성한다면 아래와 같이 된다(주의-대소문자 구분함).

Abc.o : Abc.c 
    <tab> gcc -c Abc.o Abc.c

실제로 여러 파일을 생성한다고 가정하면 위와 같은 코드들이 아래와 같이 나열된다.

# 이건 주석입니다.
Abc.o : Abc.c 
    <tab>gcc -c Abc.o \\   <- 멀티라인 사용법
        Abc.c
Abb.o : Abb.c 
    <tab> gcc -c Abb.o Abb.c

A.exe : Abc.o Abb.o 
    <tab> gcc -o A.exe Abb.o Abc.o
...... 반복.....

주석은 #로 시작하고 멀티라인은 \를 마지막 라인에 추가하면 된다. 아주 간단하지 않는가? 위와 같이 파일을 생성하여 makefile을 만든 후, make.exe를 실행하면 자동으로 makefile을 읽어서 위의 조건에 따라 파일을 생성해 준다.

만약 어떤 Target File이 생성되기 위해 다른 파일이 Target File이 필요한 경우 makefile이 알아서 필요한 파일 순서대로 결과 파일을 만든 후 최종 결과물을 만들어 준다. 위의 makefile을 수행하면 A.exe와 각종 .o 파일들이 생긴다.

2.매크로(Macro)의 사용

만약 소스파일이 손으로 칠 수 없을 만큼 많다면? 또는 어떤 Target File을 생성하는데 규칙이 일정하다면?(.c 파일을 .o 파일로 바꾸는 것과 같은 경우) 과연 이런 경우에도 일일이 다 손으로 써줘야 할까?

매크로를 이용하면 이것을 편리하게 할 수 있다. GNU-Make에서 매크로는 아래와 같이 정의하고 사용한다.

OBJECT = A.o B.o C.o
CC = gcc
A.exe : $(OBJECT)
    <tab> $(CC) -o A.exe $(OBJECT)

OBJECT 매크로가 없다면 일일이 손으로 저 파일들을 다 쳐줘야 할 것을 OBJECT 매크로를 정의함으로써 편리하게 해결했다.

그럼 이제 좀더 이것을 확장해 보자. 일반적으로 .c 파일을 .o 파일로 만드는 규칙은 거의 동일하다. 컴파일 옵션에 -c 옵션을 줄 것이며 A.o A.c와 같은 같은 파일명을 사용하고 확장자만 다르다. 이것을 어떻게 간단하게 할 수 있을까? 답은 패턴 규칙을 이용하는 것이다. %를 사용하는 아래를 보자.

%.o : %.c 
    <tab> gcc -c $@ $<

위와 같이 하면, .c가 들어간 파일은 .o로 바꾸어 주는데 gcc -c 옵션을 적용하여 .o 파일을 생성한다. 즉 %는 확장자를 제외한 파일명을 의미한다. $@와 같은 생소한 문자를 볼 수 있는데, 이것은 make에서 미리 정의해둔 매크로로 아래와 같은 것들이 있다.

  • $@ : Target File. 파일명 및 확장자 포함. 왼쪽 전체 패턴을 치환
  • $< : Source File. 파일명 및 확장자 포함. 오른쪽 첫번째 패턴을 치환
  • $* : 파일명만 포함
  • $^ : Source File. 오른쪽 전체 리스트를 치환

그럼 패턴 규칙을 이용할때 파일명 그대로만 사용 가능할까? 답은 "그렇지 않다" 이다. 아래와 같은 사용도 가능하다.

%\_debug.o : %.c
    <tab> gcc -c $@ $<

위처럼하면 파일이름에 _debug가 붙은 파일을 생성할 수 있다. 저 규칙을 잘 이용하면 여러가지 종류의 파일을 생성할 수 있다.
자 그러면 매크로로 정의한 내용 중에 필요한 내용만 따로 치환할 수 는 없을까? 물론 가능하다. 아래는 .o를 .c로 치환하는 문장이다.

OBJECT = a.o b.o c.o d.o
SOURCE = $(OBJECT:.o=.c)

아주 간단하지 않은가? @0@)/~ 정말 놀랍다. @0@)/~~!!!!

그럼 만약 target에서 사용될 이름과 동일한 파일명이 존재한다면? 즉 make clean과 같이 clean을 사용해야 하는데 마침 이름이 clean인 항목이 있다면 어떻게 할까? make clean을 하면 해당 항목이 컴파일 될 것인데... 우리가 원하는 것은 그것이 아니다...

이런 경우 아래와 같이 사용하면 된다.

.PHONY : clean

.PHONY가 의미하는 것은 clean이 build의 대상이 아님을 말한다.

이번에는 make 실행 시 입력값에 의해 Active하게 플래그를 변경해서 실행해 보자. makefile안에 여러 모드로 컴파일하는 기능도 넣어보자. 아무옵션이 없이 그냥 make만 실행되었을 때, 디폴트로 가는 레이블은 all 이다. 이 기능을 이용해서 디폴트 Target 파일을 지정해보고 clean 기능을 넣어서 .o 파일을 모두 지우게 하자.

all : A.exe

.... 어쩌구 저쩌구 ...

A.exe : $(OBJ) 
    <tab> $(CC) $(FLAG) $(OBJ)

.... 어쩌구 저쩌구 ...

clean :
    <tab> del \*.o

위 처럼 Source File 즉 Dependency에 아무것도 넣지 않거나 만들 Target File 명을 넣으면 된다. A.exe를 만들고 싶으면 "make"라고 입력하던지 "make all"이라고 입력하면 되고 clean 기능을 동작 시키려면 "make clean"이라고 입력하면 된다.

조금 더 응용해서 make 시에 컴파일 플래그를 조절하도록 해보자

ifdef FULL
FLAG = -f -c
endif

위와 같이 한 뒤에 "make FULL=yes" 라고 실행하면 FULL 플래그를 설정하여 FLAG를 활성화 시킬 수 있다.

3.디렉토리의 변경

makefile을 생성하여 사용할때 여러개의 프로젝트를 동시에 빌드하여 하나의 output이 나와야 할 경우가 있다. 이런 경우 하위 폴더에 프로젝트들을 넣어두고 상위 폴더에 하위 폴더를 다 make 하는 형식을 사용하는데, 이때 각 프로젝트 폴더로 이동해서 해당 폴더의 makefile을 실행해야한다.

이때 사용하는 옵션이 -C 이고 make를 실행하기 전에 폴더를 변경하라는 옵션이다.

$> make -C "Boot" -f makefile

위의 명령은 Boot 폴더로 폴더를 이용해서 make를 수행하라는 명령이다.

4.출력 메시지

make 도중에 메시지를 화면에 출력하고 싶으면 @echo "메시지" 의 형태로 사용하면 된다.

@echo "빌드중입니다..."

5.마무리-makefile 적용

5.1 Build 파일을 직접 선택

자 이제 이것을 실제로 적용해 보자. 아래의 makefile은 make에 대해서 잘 몰랐을 때, 노가다로 작성한 파일이다. 상당히 불현하고 특히 자주 파일이 추가되거나 변경되는 Custom 폴더의 경우 파일이 추가될때 마다 일일이 명령을 추가해줘야하는 단점이 있었다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: Kkernel

# Compile 옵션  
GCC = gcc -ffreestanding -c  
NASM = nasm -f coff  
FWDIR = FW/  
CUSTOMDIR = Custom/

# Frame Work 파일  
# Compile할 파일 이름 다 적기  
A.o: $(FWDIR)Asm.asm  
    $(NASM) -o A.o $(FWDIR)Asm.asm  
Is.o: $(FWDIR)Isr.asm  
    $(NASM) -o Is.o $(FWDIR)Isr.asm  
K.o: $(FWDIR)Kernel.c  
    $(GCC) -o K.o $(FWDIR)Kernel.c  
D.o: $(FWDIR)Descriptor.c  
    $(GCC) -o D.o $(FWDIR)Descriptor.c  
Int.o: $(FWDIR)Interrupt.c  
    $(GCC) -o Int.o $(FWDIR)Interrupt.c  
Key.o: $(FWDIR)Keyboard.c  
    $(GCC) -o Key.o $(FWDIR)Keyboard.c  
Stdlib.o : $(FWDIR)stdlib.c  
    $(GCC) -o Stdlib.o $(FWDIR)Stdlib.c  
Task.o : $(FWDIR)Task.c  
    $(GCC) -o Task.o $(FWDIR)Task.c

# 응용 프로그램 파일  
FW.o : $(CUSTOMDIR)Framework.c  
    $(GCC) -o FW.o $(CUSTOMDIR)FrameWork.c  
KShell.o : $(CUSTOMDIR)KShell.c  
    $(GCC) -o KShell.o $(CUSTOMDIR)KShell.c  
Sched.o : $(CUSTOMDIR)Scheduler.c  
    $(GCC) -o Sched.o $(CUSTOMDIR)Scheduler.c

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다.  
OBJ = A.o K.o Is.o D.o Int.o Key.o Stdlib.o Task.o FW.o KShell.o Sched.o
Kkernel: $(OBJ)  
    ld $(OBJ) -o kkernel.bin --oformat binary -Ttext 0x100000
clean:  
    del \*.o

위의 makefile을 배운 것을 이용하여 간단하게 정리하면 아래와 같이 쓸 수 있다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: kernel

# Compile 옵션 및 폴더 설정  
GCC = gcc -ffreestanding -c  
NASM = nasm -f coff  
FWDIR = FW/  
CUSTOMDIR = Custom/

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다. 새로운 파일이 생기면 뒤에다 추가하자  
OBJ = Asm.o Kernel.o Isr.o Descriptor.o Interrupt.o Keyboard.o StdLib.o Task.o \\  
      FrameWork.o KShell.o Scheduler.o

#컴파일할 파일의 확장자에 따른 규칙
# FW 폴더 밑의 파일들 컴파일
%.o: $(FWDIR)%.asm  
    $(NASM) -o $@ $<  
%.o: $(FWDIR)%.c  
    $(GCC) -o $@ $<

# Custom 폴더 밑의 파일들 컴파일  
%.o : $(CUSTOMDIR)%.c  
    $(GCC) -o $@ $<

# 최종 링크  
kernel: $(OBJ)  
    ld $(OBJ) -o kkernel.bin --oformat binary -Ttext 0x100000
clean:  
    del \*.o

우와~ 정말 깔끔해졌다. 확장 또한 한부분만 추가하면 되니까 훨씬 간단해 졌다. makefile 사용해서 효율적인 프로젝트 관리를 해보자.

5.2 특정 폴더의 특정 확장자의 파일을 모두 빌드

이클립스 버전으로 옮겨가면서 makefile 또한 정리하였다. 자세한 내용은 21 OS 프레임워크 소스 릴리즈의 릴리즈 파일을 참고하도록하고 아래는 00Kernel 폴더의 makefile이다.

#   Kernel Make File  
#  
#   Written KKAMAGUI, [http://kkamagui.egloos.com](http://kkamagui.egloos.com/)
all: kernel

# Compile 옵션 및 폴더 설정  
GCC = djgcc -ffreestanding -c  
LD = djld  
NASM = nasm -f coff  
FWDIR = FW  
CUSTOMDIR = Custom  
SOURCEDIR = $(FWDIR) $(CUSTOMDIR)

#Object 파일 이름 다 적기  
#아래의 순서대로 링크된다. 새로운 파일이 생기면 뒤에 다 추가하자  
#커널에 꼭 필요한 Object 파일들. ASM.o 파일은 항상 제일 앞에 와야한다. 그 이유는  
#커널의 엔트리포인트가 있는 함수이기 때문이다.  
ESSENTIALOBJ = Asm.o Isr.o

# 디렉토리에 있는 C 파일들을 다 찾아서 넣도록 한다.  
CFILE = $(foreach dir,$(SOURCEDIR),$(notdir $(wildcard $(dir)/\*.c )))  
    CFILEOBJ = $(CFILE:.c=.o)  

#확장자 규칙을 이용해서 컴파일 한다.  
%.o: $(FWDIR)\\%.asm  
    $(NASM) -o $@ $<  
%.o: $(FWDIR)\\%.c  
    $(GCC) -o $@ $<  
%.o: $(CUSTOMDIR)\\%.asm  
    $(NASM) -o $@ $<  
%.o: $(CUSTOMDIR)\\%.c  
    $(GCC) -o $@ $<  

# 최종 링크  
kernel: $(ESSENTIALOBJ) $(CFILEOBJ)  
    @echo "==> Making Kernel..."  
    $(LD) $(ESSENTIALOBJ) $(CFILEOBJ) -o kkernel.bin --oformat binary -Ttext 0x100000  
    @echo "==> Complete"

clean:  
    del \*.o  
    del kkernel.bin

6.make 문법 quick reference

GNU make의 quick refernce는 http://www.viper.pe.kr/docs/make-ko/make-ko_15.html 에서 찾을 수 있다.

01 웹 프로그래밍

원문 :  http://kkamagui.springnote.com/pages/347953

 

들어가기 전에...

 

스프링노트로 쓴 글 이글루스에 보냈을 때 코드 상자 표시

 스프링노트로 글을 쓴다음 이 글을 이글루스로 보내면 그룹 상자나 코드 상자가 제대로 보이지 않는다. 이것을 해결하기 위해 소스를 열어보니 ol.code 라는 스타일과 li 라는 스타일이 보였다.

 

 이것을 이글루스 스킨에 css 영역에 아래와 같이 추가해 줬다.

div.BOX { border: dashed; border-width: thin; width: 100%; color: white; background: #404040; PADDING: 5PX;  line-height:2.0em}
ol.code { border: dashed; border-width: thin; width: 100%; color: white; background: #151515; PADDING: 5PX;  line-height:2.0em}
ol { border: dashed; border-width: thin; width: 100%; color: white; background: #404040; PADDING: 5PX; line-height:2.0em}
div { background: #202020 }

 

 이제 코드 상자도 잘 보이고 글 상자도 잘 보인다. 단 맨 처음이 항상 개행되는 문제만 제외하면.. ㅡ,.ㅡ;;;;

이 글은 스프링노트에서 작성되었습니다.

00 윈도우 프로그래밍 팁

원문 :  http://kkamagui.springnote.com/pages/347952

 

들어가기 전에...

 

각종 윈도우 팁들 - Debug Lab

 http://www.debuglab.com/knowledge/

 

WinDBG의 심볼 경로 설정

SRV*c:\websymbol*http://msdl.microsoft.com/download/symbols  -->

 

VC에서 메모리 관련 디버그 명령

INIT_CRTDEBUG(); <== 메모리 누수 모니터링 시작
BREAK_ALLOC(X); <== 메모리 X 블럭 할당시 Break

 

VC에서 Alt-F4 막기

Alt+F4 키와 같은 메세지를 처리해주기 위해서 CWinApp 함수의 PreTranslateMessage() 를 override 해서 아래의 코드를 넣어 주면 된다.

  1. if(pMsg->message == WM_SYSKEYDOWN && pMsg->wParam == VK_F4)
    {
        return TRUE;
    }

 

 

익스플로러가 죽어도 트레이아이콘에 계속 상주시키기

  1. // 메시지 등록 과정
    UINT g_uShellRestart;
    g_uShellRestart = RegisterWindowsMessage( "TaskbarCreated" );

    // Message Map 에서
    ON_REGISTERED_MESSAGE( g_uShellRestart, OnTrayShow )

    // 메시지를 처리합니다.
    LRESULT CMyDlg::OnTrayShow( WPARAM wParam, LPARAM lParam )

       // TrayIcon을 다시 보여줍니다. ShowTray는 Tray를 보여주는 함수입니다.
       m_Tray.ShowTray();
    }  -->

 

 

 Variable Argument(가변 인자)

  1. // crt_vsprintf.c
    // This program uses vsprintf to write to a buffer.
    // The size of the buffer is determined by _vscprintf.

    #include
    #include

    void test( char * format, ... )
    {
       va_list args;
       int len;
       char * buffer;

       va_start( args, format );
       len = _vscprintf( format, args ) // _vscprintf doesn't count
       + 1; // terminating '\0'
       buffer = malloc( len * sizeof(char) );
       vsprintf( buffer, format, args );
       printf( buffer );
       free( buffer );
    }

    int main( void )
    {
       test( "%d %c %d\n", 123, '<', 456 );
       test( "%s\n", "This is a string" );
    }

 

 

윈도우에서 Command Line Argument 얻기

 argc = __argc, argv = __argv 로 얻으면 된다.

 

 

윈도우 공유 폴더 로그인 시 이상한 계정으로 로그인 되는 경우

 관리도구->사용자계정->좌측 상단의 네트워크 연결관리 에서 서버와 계정을 추가한다.

 

 

툴팁 생성하기

 툴팁 생성 코드

  1.     m_clToolTip.Create( this, WS_VISIBLE | WS_BORDER );
        m_clToolTip.AddTool( this, "" );
        m_clToolTip.SetDelayTime( 100 );
        m_clToolTip.Activate( TRUE );

 

 툴팁 표시를 위한 메시지 릴레이 코드

  1. /**
        Enter와 Esc로 인한 종료를 막는다.
    */
    BOOL CSubPartition::PreTranslateMessage(MSG* pMsg)
    {
        if( pMsg->message == WM_KEYDOWN )
        {
            if( ( pMsg->wParam == VK_ESCAPE ) ||
                ( pMsg->wParam == VK_RETURN ) )
            {
                return TRUE;
            }
        }
  2.     // ToolTip 표시를 위해 메시지 릴레이 설정
        m_clToolTip.RelayEvent(pMsg);
        return CDialog::PreTranslateMessage(pMsg);
    }

 

 

Shell Execute로 프로그램 실행하기

 

  1. BOOL ExecuteProgram( String FileName, String Params, INT Flag )
    {
      SHELLEXECUTEINFO execinfo;
     
      // 실행을 위해 구조체 세트
      ZeroMemory( &execinfo, sizeof(execinfo) );
      execinfo.cbSize = sizeof(execinfo);
      execinfo.lpVerb = "open";
      execinfo.lpFile = FileName.c_str();
      execinfo.lpParameters = Params.c_str();
      execinfo.fMask = SEE_MASK_FLAG_NO_UI SEE_MASK_NOCLOSEPROCESS;
      execinfo.nShow = SW_SHOWDEFAULT;
     
      // 프로그램을 실행한다.
      int r = (int)ShellExecuteEx( &execinfo );
      if ( r == 0 ) return ( false );
     
      // 만약 Sync 플랙이 세트되었으면,
      // 실행이 종료될 때까지 기다린다.
      if ( Flag == 1 )
      {
        DWORD ec;
        do
        {
          GetExitCodeProcess( execinfo.hProcess, &ec );
          Application->ProcessMessages();
        }
        while ( ec == STILL_ACTIVE );
      }
     
      return ( true );
    }

 

 Open URL로 웹 연결하기

CInternetFile* pFile = NULL;
CInternetSession InetSession;
 
try
{
  pFile = (CInternetFile *)InetSession.OpenURL( "URL 주소 : http://???" )
}
catch(CInternetException *pEx)
{
  pFile = NULL;
  pEx = NULL;
  AfxMessageBox( "OpenURL Excption Error!" );
}
 
CString strData;
 
if( pFile )
{
  CString strTemp;
  pFile->SetReadBufferSize( 4096 );
 
  while( true )
  {
    if( pFile->ReadString( strTemp ) )
    {
      strTemp += "\r\n";
      strData += strTemp;
    }
    else
    {
      break;
    }
  }
}
else
{
  AfxMessageBox( "OpenURL pFile is NULL!!" );
}
 
MessageBox( strData );

 

윈도우에서 F1 키 막기 또는 기능 변경하기

 APP의 아래부분을 막거나 변경한다.

  1.  ON_COMMAND(ID_HELP, CApp::OnHelp)

 

 

DLL의 함수 Export시 정해진 이름으로 Export 하기

 DLL의 함수를 그냥 Export 하면 함수 이름 그대로 Export 되지 않고 기타 정보가 더 붙는다. extern을 사용하면 어느정도 해결되지만 확실한 해결책은 def 파일을 이용하는 것이다. 아래와 같이 쓰면된다.

  1. LIBRARY MyDll
    EXPORTS
       Function1=Function1
       Function2=Funciton2
  2.    Data1                 DATA <== 데이터를 외부로 Export 할때
  3.    Function3             @1   <== 서수를 같이 Export 할때
  4.    Function4             @2   NONAME  <== 서수로만 Export 할때, 함수 이름 포함 X
  5.    Function5                  PRIVATE <== lib 파일에 Function5에 대한 내용 제외, DLL에는 있음

 

파일(File) 및 라인(Line) 관련 매크로(Macro)

  • __FILE__ : 해당 파일 이름으로 대체
  • __LINE__ : 해당 라인 번호로 대체

 

  1. printf( "%d %d", __FILE__, __LINE__ );

 

printf 직접 구현하기

 C/C++의 Calling Convention을 보면 Stack에 파라메터를 넘긴다. 이것을 이용하여 Variable Argument List를 찾아서 넣는 방식이다.

  1. /**
     임의로 만든 printf 함수
      printf 함수의 간략버전
    */
    void kPrintf( char* pcFormat, ... )
    {
        DWORD* pdwArg;
        char vcBuffer[ 1024 ];
        int iBufferIndex;
        int iFormatIndex;
        char* pcString;
        int iLength;
  2.     // 2번째 Argument를 pdwArg가 가리키고 있다.
        pdwArg = ( DWORD* ) &pcFormat + 1;
        iBufferIndex = 0;
        iFormatIndex = 0;
  3.     // 문자열 끝까지 한다.
        while( pcFormat[ iFormatIndex ] != '\0' )
        {
            if( pcFormat[ iFormatIndex ] != '%' )
            {
                vcBuffer[ iBufferIndex ] = pcFormat[ iFormatIndex ];
                iFormatIndex++;
                iBufferIndex++;
                continue;
            }
  4.         iFormatIndex++;
            switch( pcFormat[ iFormatIndex ] )
            {
                // 16진수 출력
            case 'X':
            case 'x':
                // 10진수 출력
            case 'd':
                kDToA( vcBuffer + iBufferIndex, *pdwArg );
                iBufferIndex += 8;
                iFormatIndex++;
                pdwArg++;
                break;
  5.             // 문자열 출력
            case 's':
                pcString = ( char* )( *pdwArg );
                iLength = strlen( pcString );
  6.             kMemCpy( vcBuffer + iBufferIndex, pcString, iLength );
                iBufferIndex += iLength;
                iFormatIndex++;
                pdwArg++;
                break;
     
                // 문자 출력
            case 'c':
             vcBuffer[ iBufferIndex ] = *pdwArg & 0xFF;
             iFormatIndex++;
             iBufferIndex++;
             pdwArg++;
             break;
             
                // % 출력
            case '%':
                vcBuffer[ iBufferIndex ] = '%';
                iFormatIndex++;
                iBufferIndex++;
                break;
           
                // 그외 기타
            default:
                vcBuffer[ iBufferIndex ] = pcFormat[ iFormatIndex ];
                iFormatIndex++;
                iBufferIndex++;
                break;
            }
        }
        vcBuffer[ iBufferIndex ] = '\0'; 
       
        // 내부 출력함수 이용
        kPrintfInternal( vcBuffer );
    }

 

 

조사식 창(Watch)에 에러(Error) 내용 보기

 아래와 같이 입력하면 에러코드와 내용을 볼 수 있다.

  1. @err,hr 

 

VC 6.0에서 XP 스타일 적용하기 

원문 :  http://blog.naver.com/kisatsg?Redirect=Log&logNo=20004074897

① 다음 내용을 편집하신후에 프로젝트 아래의 res 디렉토리에 ApplicationManifestXMLFile 파일로 저장합니다.

 

./res/ApplicationManifestXMLFile

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
                 manifestVersion="1.0">
<assemblyIdentity
    version="1.0.0.0"
    processorArchitecture="X86"
    name="Microsoft.Windows.YourApplication"
    type="win32"
/>
<description>YourApplication</description>
<dependency>
    <dependentAssembly>
        <assemblyIdentity
            type="win32"
            name="Microsoft.Windows.Common-Controls"
            version="6.0.0.0"
            processorArchitecture="X86"
            publicKeyToken="6595b64144ccf1df"
            language="*"
        />
    </dependentAssembly>
</dependency>
</assembly>
② resource.h 에 아래 두줄 추가
#define IDR_MANIFEST  1
 #define RT_MANIFEST  24
③ .rc2 파일은 거의 손 안대시죠? 그 파일안에 다음 내용을 쳐 넣습니다.
// Add manually edited resources here...
IDR_MANIFEST RT_MANIFEST MOVEABLE PURE
             "res\\ApplicationManifestXMLFile"
④ Instance 시작 파일에 다음 내용을 쳐 넣습니다.
BOOL MoneyApp::InitInstance()
{
  InitCommonControls();    // initialize common control library
  CWinApp::InitInstance(); // call parent class method

#ifdef _AFXDLL
  Enable3dControls();      // Call this when using MFC in a
                           // shared DLL
#else
  Enable3dControlsStatic(); // Call this when linking to MFC
                            // statically
#endif

  // the rest of the code
}
이상입니다. 모 사실 ④번은 안해도 되는데, 원저자가 하라길래 했지요. ^^;

 

 

Tray Icon 관련 

1.Add Tray 

  1.  void CMainFrame::AddSysTray()
    {
     NOTIFYICONDATA data;
     data.cbSize = sizeof(NOTIFYICONDATA);
     data.hWnd = m_hWnd;
     data.uID = IDR_MAINFRAME;
     data.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
     data.uCallbackMessage = WM_SYSTRAYCLICKED;
     data.hIcon = AfxGetApp()->LoadIcon( IDR_MAINFRAME );
     strcpy(data.szTip , VERSION);
     Shell_NotifyIcon( NIM_ADD, &data );
    }

 

2.Delete Tray

  1. void CMainFrame::DeleteSysTray()
    {
     NOTIFYICONDATA data;
     data.cbSize = sizeof( NOTIFYICONDATA );
     data.hWnd = m_hWnd;
     data.uID = IDR_MAINFRAME;
     Shell_NotifyIcon( NIM_DELETE , &data );
    }

 

3.메시지 처리 

  1. LONG CMainFrame::OnSystrayClicked( WPARAM wParam , LPARAM lParam )
    {
     CYesNoDlg dlg;
  2.     switch( lParam )
     {
     case WM_LBUTTONDBLCLK:
      //::ShowWindow( m_pVisibleWnd->m_hWnd , SW_RESTORE );
      m_pVisibleWnd->ShowVisibleFrame();
      TRACE("에루 버튼\n");
      break;
     case WM_RBUTTONDBLCLK:
      TRACE("아루 버튼\n");
      if( dlg.DoModal() == IDOK )
      {
       PostMessage( WM_CLOSE , 0 , 0 );
      }
      break;
     }
     return TRUE;
    }

 

4.태스크바가 다시 생성되었을 때 재등록

  1. LRESULT CMainFrame::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
     // 만약 테스크바가 제시작 되었으면
     // 익스플로어가 한번 죽었던 것이므로..
     // 새로 Tray에 더한다.
     if( message == RegisterWindowMessage("TaskbarCreated") )
     {
      AddSysTray();
     }  
     return CFrameWnd::DefWindowProc(message, wParam, lParam);
    }

 

다이얼로그 기반 프로그램에서 처음 시작시 다이얼로그 안띄우기 

 WM_WINDOWPOSCHANGING 메시지를 이용한다. 이 메시지는 Dialog 메시지에는 없으므로 클래스 위져드에서 메시지 쪽에 메시지 필터를 "Window"로 바꿔줘야 나온다.

  1. void CHidDlgDlg::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)
    {
        CDialog::OnWindowPosChanging(lpwndpos);
      
        // TODO: Add your message handler code here
        if(m_bShowFlag)
            lpwndpos->flags |= SWP_SHOWWINDOW;
        else
            lpwndpos->flags &= ~SWP_SHOWWINDOW;
  2. }
  3. BOOL CHidDlgDlg::ShowWindowEx(int nCmdShow)
    {
        m_bShowFlag = (nCmdShow == SW_SHOW);
        return (GetSafeHwnd()) ? ShowWindow(nCmdShow) : TRUE;
    }
  4. 출처 : Tong - luster님의 Tip통

 

 

윈도우 핸들(Window Handle, hwnd)로 클래스 이름(Class Name) 얻기

  1. int GetClassName( HWND hWnd, LPTSTR lpClassName, int nMaxCount );

 이걸 찾을려고 온 MSDN을 다 뒤졌다는.. ㅡ_ㅡa...

 

이 글은 스프링노트에서 작성되었습니다.

04 진보된 DLL 디스어셈블러 만들기

원문 :  http://kkamagui.springnote.com/pages/389711

 

들어가기 전에...

 

 요전에 DLL 디스어셈블리어 프로그램을 하나 만들었다. 그런데 단순한 디스어셈블러였을 뿐, 실제 분석에 필요한 뭔가가 없었다. 보통 분석할 때(내가.. ㅡ_ㅡ;;; 다른 사람은 모르겠음...) 함수 단위로 call, jmp 하는 부분 위주로 따라가면서 분석하는데, 그러한 기능이 없었다.

 그래서 이러한 함수 분석 기능을 추가하여 JMP, JL, JE 등등과 같은 JMP와 call을 분석해서 따라갈 수 있는 것은 따라가 주도록 만들었다. 아직은 파이썬에 익숙하지 않아서 시간이 좀 걸렸지만... 하면서도 나름 재미있었다는.. ㅠ_ㅠ...

 아래는 코드이다.

  1. # -*- coding: cp949 -*-
    from distorm import Decode, Decode16Bits, Decode32Bits, Decode64Bits
    # c관련 함수를 사용하기위해 처리
    from ctypes import *
    import ctypes
  2. g_assemblyBuffer = [];
    g_gotoBufferArray = [];
  3. # Disassembly를 수행하는 함수
    def Disassembly( intAddress, curLevel, maxLevel ):
        buffer = ctypes.c_buffer( 4096 );
        libc.memcpy( buffer, intAddress, 4096 );
     
        l = Decode(intAddress, buffer, Decode32Bits)
        for i in l:
            # 아래와 같은 형식으로 출력된다.
            # 0x7c8309e1 8bff                 MOV EDI, EDI
            buffer = "0x%08x %-20s %s" % (i[0], i[3].upper(),  i[2])
            g_assemblyBuffer.append( buffer );
           
            # 만약 Jmp나 call 같은 명령이면 그 뒤에 오는 주소를 넣는다.
            if ( "J" in i[ 2 ] or "CALL" in i[ 2 ] ) and \
               ( "0x" in i[ 2 ] and "+" not in i[ 2 ] and "-" not in i[ 2 ] and \
                '*' not in i[ 2 ] and '/' not in i[ 2 ] ) and \
                curLevel < maxLevel:
                index = i[ 2 ].find( " " );
                stringAddr = i[ 2 ][ index + 1 : ];
                g_gotoBufferArray.append( [ curLevel + 1, stringAddr ] );
                #print "gotoBufferAdd", buffer;
               
            # 만약 ret거나 JMP 이면 그만 한다.
            if ( "RET" in i[ 2 ] ) or "NOP" in i[ 2 ]:
                g_assemblyBuffer.append( " " );
                return ;
  4. # Hex값을 Int로 바꾼다.
    def HexAddrToIntAddr( address ) :
        intAddress = 0;
        address = address[ 2 : ];
        for i in address :
            intAddress = intAddress << 4;
            if ord( i ) >= ord( '0' ) and ord( i ) <= ord( '9' ) :
                intAddress += int( i );
            else :
                intAddress += ( ord( i ) - ord( 'a' ) + 10 );
        return intAddress;
  5. # Indirect Addressing을 처리 함수
    def GetIndirectValue( address ):
        intAddress = 0;
        intAddress = HexAddrToIntAddr( address );
       
        #print "Indirect Address[%x]" % intAddress;
        buffer = ctypes.c_buffer( 4 );
       
        libc.memcpy( buffer, intAddress, 4 );
        #little Endian 이므로 역순으로 들어가있다. 이것을 바꿔준다.
        value = 0;
        mulValue = 0x00000001;
        for i in buffer :
            value += ( ord( i ) * mulValue );
            mulValue = mulValue << 8;
            #print "%x" %ord( i );
  6.     #print "%x" %value;
        return value;

  7. # 이미 Disassemby한 Address 인가 확인
    def IsAlreadyDisassembly( address ):
        for i in g_assemblyBuffer:
            if address in i[ : 10 ] :
                return 1;
        return 0;
  8. # c 런타임 라이브러리를 가지고 있음
    libc = cdll.msvcrt;
  9. dllName = raw_input( "DLL Name : " );
    functionName = raw_input( "Procedure Name : " );
    maxLevel = input( "Analysis Level( 0 ~ 20 ) : " );
  10. # 함수를 찾는다.
    dll = windll.LoadLibrary( dllName );
    functionAddress = dll.GetProcAddress( dll._handle, functionName );
  11. # 시작 Address를 넣는다.
    g_gotoBufferArray.append( [ 0, "0x%x" %functionAddress ] );
  12. for i in g_gotoBufferArray :
        [ level, stringAddress ] = i;
  13.     if '[' in stringAddress :
            intAddress = GetIndirectValue( stringAddress[ 1 : -1 ] );
            g_assemblyBuffer.append( "==============Level[%d] %s 0x%x============\n" \
                %( level, stringAddress, intAddress ) );
        else :
            intAddress = HexAddrToIntAddr( stringAddress );
            g_assemblyBuffer.append( "==============Level[%d] %s============\n" \
                %( level, stringAddress ) );
           
        # 점프하는 주소가 이미 Disassembly 되어있으면 안한다.
        if IsAlreadyDisassembly( "0x%x" %intAddress ) == 1 :
            g_assemblyBuffer.append( "0x%x Is Already Disassembly\n" %intAddress );
            continue;
       
        Disassembly( intAddress, level, maxLevel );
  14. # 출력하는 부분
    file = open( dllName + functionName + ".txt", "w" );
    print "Now Writing....."
    for i in g_assemblyBuffer:
        file.writelines( i + "\n" );
        #print i;
    file.close();
    print "Write Complete....."

 

 이것을 실행하면 DLL 이름과 함수 이름, 그리고 얼마나 깊이 call을 추적할 것인가가 나오는데, call 추적은 3~4 정도가 충분하다.

 아래는 kernel32.dll, OpenProcess, 3으로 함수를 분석한 결과이다. 아주 깔끔하게 잘 분석되었다 @0@)/~!!

==============Level[0] 0x7c8309e1============

0x7c8309e1 8BFF                 MOV EDI, EDI
0x7c8309e3 55                   PUSH EBP
0x7c8309e4 8BEC                 MOV EBP, ESP
0x7c8309e6 83EC 20              SUB ESP, 0x20
0x7c8309e9 8B45 10              MOV EAX, [EBP+0x10]
0x7c8309ec 8945 F8              MOV [EBP-0x8], EAX
0x7c8309ef 8B45 0C              MOV EAX, [EBP+0xc]
0x7c8309f2 56                   PUSH ESI
0x7c8309f3 33F6                 XOR ESI, ESI
0x7c8309f5 F7D8                 NEG EAX
0x7c8309f7 1BC0                 SBB EAX, EAX
0x7c8309f9 83E0 02              AND EAX, 0x2
0x7c8309fc 8945 EC              MOV [EBP-0x14], EAX
0x7c8309ff 8D45 F8              LEA EAX, [EBP-0x8]
0x7c830a02 50                   PUSH EAX
0x7c830a03 8D45 E0              LEA EAX, [EBP-0x20]
0x7c830a06 50                   PUSH EAX
0x7c830a07 FF75 08              PUSH DWORD [EBP+0x8]
0x7c830a0a 8D45 10              LEA EAX, [EBP+0x10]
0x7c830a0d 50                   PUSH EAX
0x7c830a0e 8975 FC              MOV [EBP-0x4], ESI
0x7c830a11 C745 E0 18000000     MOV DWORD [EBP-0x20], 0x18
0x7c830a18 8975 E4              MOV [EBP-0x1c], ESI
0x7c830a1b 8975 E8              MOV [EBP-0x18], ESI
0x7c830a1e 8975 F0              MOV [EBP-0x10], ESI
0x7c830a21 8975 F4              MOV [EBP-0xc], ESI
0x7c830a24 FF15 0C11807C        CALL [0x7c80110c]
0x7c830a2a 3BC6                 CMP EAX, ESI
0x7c830a2c 5E                   POP ESI
0x7c830a2d 0F8C B7710000        JL 0x7c837bea
0x7c830a33 8B45 10              MOV EAX, [EBP+0x10]
0x7c830a36 C9                   LEAVE
0x7c830a37 C2 0C00              RET 0xc
 
==============Level[1] [0x7c80110c] 0x7c93dd7b============

0x7c93dd7b B8 7A000000          MOV EAX, 0x7a
0x7c93dd80 BA 0003FE7F          MOV EDX, 0x7ffe0300
0x7c93dd85 FF12                 CALL [EDX]
0x7c93dd87 C2 1000              RET 0x10
 
==============Level[1] 0x7c837bea============

0x7c837bea 50                   PUSH EAX
0x7c837beb E8 7B17FDFF          CALL 0x7c80936b
0x7c837bf0 33C0                 XOR EAX, EAX
0x7c837bf2 E9 3F8EFFFF          JMP 0x7c830a36
0x7c837bf7 90                   NOP
 
==============Level[2] 0x7c80936b============

0x7c80936b 8BFF                 MOV EDI, EDI
0x7c80936d 55                   PUSH EBP
0x7c80936e 8BEC                 MOV EBP, ESP
0x7c809370 56                   PUSH ESI
0x7c809371 FF75 08              PUSH DWORD [EBP+0x8]
0x7c809374 FF15 6C10807C        CALL [0x7c80106c]
0x7c80937a 8BF0                 MOV ESI, EAX
0x7c80937c 56                   PUSH ESI
0x7c80937d E8 2EFFFFFF          CALL 0x7c8092b0
0x7c809382 8BC6                 MOV EAX, ESI
0x7c809384 5E                   POP ESI
0x7c809385 5D                   POP EBP
0x7c809386 C2 0400              RET 0x4
 
==============Level[2] 0x7c830a36============

0x7c830a36 Is Already Disassembly

==============Level[3] [0x7c80106c] 0x7c93fb3d============

0x7c93fb3d 6A 08                PUSH 0x8
0x7c93fb3f 68 78FB937C          PUSH 0x7c93fb78
0x7c93fb44 E8 79F2FFFF          CALL 0x7c93edc2
0x7c93fb49 64 A1 18000000       MOV EAX, FS:[0x18]
0x7c93fb4f 85C0                 TEST EAX, EAX
0x7c93fb51 74 11                JZ 0x7c93fb64
0x7c93fb53 8365 FC 00           AND DWORD [EBP-0x4], 0x0
0x7c93fb57 8B4D 08              MOV ECX, [EBP+0x8]
0x7c93fb5a 8988 F40B0000        MOV [EAX+0xbf4], ECX
0x7c93fb60 834D FC FF           OR DWORD [EBP-0x4], -0x1
0x7c93fb64 FF75 08              PUSH DWORD [EBP+0x8]
0x7c93fb67 E8 1D000000          CALL 0x7c93fb89
0x7c93fb6c E8 91F2FFFF          CALL 0x7c93ee02
0x7c93fb71 C2 0400              RET 0x4
 
==============Level[3] 0x7c8092b0============

0x7c8092b0 8BFF                 MOV EDI, EDI
0x7c8092b2 55                   PUSH EBP
0x7c8092b3 8BEC                 MOV EBP, ESP
0x7c8092b5 56                   PUSH ESI
0x7c8092b6 57                   PUSH EDI
0x7c8092b7 64 A1 18000000       MOV EAX, FS:[0x18]
0x7c8092bd 8B75 08              MOV ESI, [EBP+0x8]
0x7c8092c0 8BF8                 MOV EDI, EAX
0x7c8092c2 A1 C446887C          MOV EAX, [0x7c8846c4]
0x7c8092c7 85C0                 TEST EAX, EAX
0x7c8092c9 0F85 20090300        JNZ 0x7c839bef
0x7c8092cf 3977 34              CMP [EDI+0x34], ESI
0x7c8092d2 0F85 43040000        JNZ 0x7c80971b
0x7c8092d8 5F                   POP EDI
0x7c8092d9 5E                   POP ESI
0x7c8092da 5D                   POP EBP
0x7c8092db C2 0400              RET 0x4

 

 역시나 오늘도 파이썬 만쉐 @0@)/~!!

이 글은 스프링노트에서 작성되었습니다.

03 DLL 디스어셈블러 만들기

원문 : http://kkamagui.springnote.com/pages/388049

 

들어가기 전에...

 

 항상 분석을 하면 노가다가 많고 일일이 손으로 클릭을 해가면서 또는 눈으로 찾아가면서 분석을 해야 했다. 내가 원체 UI에 별로 관심이 없다보니 UI의 수준은 당연 극악의 콘솔형태... 물론 생긴건 GUI다.(그만큼 UI가 엉망이라는.... ㅡ_ㅡ;;;). 언젠가 한번 DLL 분석 툴을 파이썬으로 만들면 재미있겠다고 생각했는데... 파이썬용 디스어셈블러 자료는 찾아놓고 실제로 DLL을 파이썬으로 당겨야 하는데 엄두가 나지 않았다.

 오늘 잠시 짬을 내서 일하다 잠도 안자고 해봤는데, 의외로 C 함수를 쓰는게 간단하다.

 

DLL 및 C 타입의 변수 등등의 사용

 DLL을 로드해서 사용하는 코드를 C로 만들면 LoadLibrary() 함수를 호출하고 다시 GetProcAddress()를 호출하면 된다. 파이썬에서는 이것을 어떻게 할까? 블로그를 찾아보고 파이썬 마을을 둘러봤더니 ctypes라는 것이 보였다. Python 2.5 버전에서 표준 라이브러리에 포함되어있는 라이브러리라는데, 상당히 C 스타일 적이다.(아래는 Python 문서에 포함된 예제이다.)

  1. >>> from ctypes import *
    >>> print windll.kernel32
  2. <WinDLL 'kernel32', handle ... at ...>
    >>> print cdll.msvcrt
  3. <CDLL 'msvcrt', handle ... at ...>
    >>> libc = cdll.msvcrt

 windll에 보면 kernel32.dll, gdi32.dll 등등의 DLL의 정보를 포함하는 모듈이 들어있음을 알 수 있다. C 런타임 라이브러리도 사용할 수 있는데, cdll.msvcrt를 모듈을 이용하면 C 라이브러리를 사용할 수 있다.

  1. >>> from ctypes import *
    >>> libc.printf
    <_FuncPtr object at 0x...>
    >>> print windll.kernel32.GetModuleHandleA
  2. <_FuncPtr object at 0x...>

 재미있는 부분은 포인터도 사용할 수 있다는 것이다.(크아... 이제 완전 게임 끝났다.. @0@)/~ 이렇게 좋을 수가.. ㅜ_ㅜ)

  1. >>> from ctypes import *
    >>> i = c_int(42)
    >>> pi = pointer(i)
    >>> pi.contents
    c_long(42)

 더 자세한 사항은 파이썬 도움말을 참고하도록 하자.

 

디스어셈블러(Disassember) 모듈

 디스어셈블러 모듈은 파이썬 용으로 따로 개발된게 있다.(어찌나 다행인지.. ㅡ_ㅡ;;; 내가 가지고 있는걸로 파이썬에 붙일려고 했는데, 이렇게 되면 기절한다... ㅡ_ㅡ;;;)

 http://www.ragestorm.net/distorm/ 사이트에 가면 파이썬용 모듈을 받을 수 있고 간단히 pyd파일을 받아서 파이썬이 설치된 Lib 폴더에 넣기만 하면 편하게 사용할 수 있다. 아래는 위 홈페이지에서 설명해 놓은 사용 예제이다.

  1. Note: Save the file in %PYTHONDIR%\Lib\site-packages\
    from distorm import Decode, Decode16Bits, Decode32Bits, Decode64Bits
    l = Decode(0x100, open("file.com", "rb").read(), Decode16Bits)
    for i in l:
     print "0x%08x (%02x) %-20s %s" % (i[0],  i[1],  i[3],  i[2])

 

 

디스어셈블러 작성

 이제 디스어셈블러를 작성할 준비가 모두 끝났다. 이제 이 모듈들을 이용하여 kernel32.dll에 있는 OpenProcess 함수를 디스어셈블리해보자.

  1. # -*- coding: cp949 -*-
    from distorm import Decode, Decode16Bits, Decode32Bits, Decode64Bits
    # c관련 함수를 사용하기위해 처리
    from ctypes import *
    import ctypes
  2. # c 런타임 라이브러리를 가지고 있음
    libc = cdll.msvcrt;
    # kernel32.dll을 여는 여러 방법
    #kernel32 = windll.kernel32;
    kernel32 = windll.LoadLibrary( 'kernel32.dll' );
  3. # OpenProcess를 얻는다.
    openProcessAddress = kernel32.GetProcAddress( kernel32._handle, "OpenProcess" );
  4. # 버퍼를 할당해서 디스어셈블리를 하기위해 버퍼에 넣는다.
    buf = ctypes.c_buffer( 4096 );
    libc.memcpy( buf, openProcessAddress, 4096 );
    #libc.memcpy( buf, 0x7c809b96, 4096 );
  5. l = Decode(openProcessAddress, buf, Decode32Bits)
    for i in l:
        # 아래와 같은 형식으로 출력된다.
        # 0x7c8309e1 (02) 8bff                 MOV EDI, EDI
        print "0x%08X (%02X) %-20s %s" % (i[0],  i[1],  i[3].upper(),  i[2])

 

 소스 코드는 위와 같이 아주 간단하다. 출력 결과는 아래와 같이 깔끔하게 나온다.

0x7C8309E1 (02) 8BFF                 MOV EDI, EDI
0x7C8309E3 (01) 55                   PUSH EBP
0x7C8309E4 (02) 8BEC                 MOV EBP, ESP
0x7C8309E6 (03) 83EC 20              SUB ESP, 0x20
0x7C8309E9 (03) 8B45 10              MOV EAX, [EBP+0x10]
0x7C8309EC (03) 8945 F8              MOV [EBP-0x8], EAX
0x7C8309EF (03) 8B45 0C              MOV EAX, [EBP+0xc]
0x7C8309F2 (01) 56                   PUSH ESI

 

 정말 멋지다.. ㅜ_ㅜ... 이렇게 몇줄 작성하지 않았는데 이런 결과가 나오다니... ㅡ_ㅜ... 정말 환상적이지 않을 수 없다. ㅜ_ㅜ...

 파이썬 만쉐이.. ㅜ_ㅜ...

 

첨부

 

 

TODO

  • 코드 내부에 있는 Jump 및 Call 관련 루틴을 따라가서 분석하는 기능 추가하기

이 글은 스프링노트에서 작성되었습니다.

 01 이글루스에서 덧글 뽑기

원문 :  http://kkamagui.springnote.com/pages/377691

 

들어가기 전에...


 

 오늘로써 파이썬을 배운지 약 하루(24시간??)이 지났다. 파이썬이 아주 편리한 언어임을 뼈저리게 깨닫기에는 충분한 시간이었다. 그래서 내친김에 프로그램을 하나 만들었는데, 그것은 바로 @0@)/~!!! 이글루스에서 덧글 뽑기!!!

 

 간혹 덧글을 확인하러 이글루스에 들어가는데... 이것을 파이썬으로 대체해서 덧글만 뽑는 것이다. @0@)/~!!! 전에 비슷한 것을 VC++로 한적이 있는데... 상당히 긴 코드였다. 하지만.. 파이썬으로 했을때는... ㅠ_ㅠ... 아래 코드가 전부이다.

  1. # -*- coding: cp949 -*-
    import urllib
  2. # euc-kr Text를 utf-8의 형태로 바꾼다.
    def ConvUtf8( buffer ):
        uniText = unicode( buffer, "euc-kr" );
        return uniText.encode( "utf-8" );
  3. # utf-8 Text를 euc-kr의 형태로 바꾼다.
    def ConvEucKr( buffer ):
        uniText = unicode( buffer, "utf-8" );
        return uniText.encode( "euc-kr" );
  4. # 버퍼에서 각 부분을 자른다.
    def ExtractItem( buffer, startTag, endTag ):
        replyStart = ConvUtf8( startTag );
        replyEnd = ConvUtf8( endTag );
       
        #print "개별 추출 함";
  5.     try:
            startIndex = buffer.index( replyStart );
            endIndex = buffer[ startIndex : ].index( replyEnd );
        except:
            return [ buffer, "" ];
       
        if startIndex != -1 and endIndex != -1:
            endIndex = startIndex + endIndex;
            #print "찾음", startIndex, endIndex;
           
            reply = buffer[ startIndex : endIndex + len( replyEnd ) ];
            return [ buffer[ endIndex + len( replyEnd ) : ], reply ];
           
        else:
            #print "찾을 수 없습니다";
            return [ buffer, "" ];
  6.        
    # 메인 코드
    urlHandle = urllib.urlopen( "http://kkamagui.egloos.com" );
    buffer = urlHandle.read();
    # 덧글 블럭만 뽑는다.
    [ buffer, extractBuffer ]= ExtractItem( buffer,"<DIV CLASS=MNTTL>최근 등록된 덧글</DIV>",
                                "<CENTER>");
    #print extractBuffer;
  7. # 개별 덧글을 다 찾는다.
    i = 1;
    while 1:
        print "";
        print i;
  8.     reply = "";
        [ extractBuffer, reply ] = ExtractItem( extractBuffer, "<a href=", "</div>" );
        if( reply == "" ):
            break;
        #print reply;
       
        # 덧글은 아래와 같이 되어있다.
        #<a href="http://kkamagui.egloos.com/3287761#9763213">
        #네 감사합니다 ^^</a><br/><span class="SMALL">by kkamagui at 07/20</span>
        #<br/><div style="margin:12px;"></div>
        item = "";
        [ reply, item ] = ExtractItem( reply, "http", '">' );
        item = item[ : -2 ];
        item = ConvEucKr( item );
        print "PageNumber####", item;
  9.     [ reply, item ] = ExtractItem( reply, "", "<" );
        item = item[ : -1 ];
        item = ConvEucKr( item );
        print "Contents###", item;
  10.     [ reply, item ] = ExtractItem( reply, 'SMALL">', "</span" );
        item = item[ 7 : -6 ];
        item = ConvEucKr( item );
        print "Writer###", item;
        i = i + 1;
  11. i = input( "끝입니다. 아무키나 눌러주세요" );

 뭐 간단히 보면 HTML 특정 부분 파싱이 전부이다. 약간 주의할 점은 utf-8 형태로 HTML 코드가 전송되는데, 이것을 콘솔 화면에 적당히 출력하기위해서 euc-kr로 바꿔주는 부분이 조금 특이하다.

 

 이로써 덧글을 확인하러 블로그에 들어가지 않아도 되게 되었다. ㅠ_ㅠ

 

파이썬 만쉐이 ㅠ0ㅠ)/~!!!

 

이 글은 스프링노트에서 작성되었습니다.

01 파이썬(Python) 팁

원문 : http://kkamagui.springnote.com/pages/391699

 

들어가기 전에...

 

리스트(List) 팁

  • list.append( x ) : 리스트의 마지막에 x 추가
  • list.sort() : 리스트 정렬
  • list.reverse() : 리스트 뒤집기
  • list.index( x ) : x가 있는 인덱스를 표시
  • list.remove( x ) : 리스트에서 제일 처음 나온는 x를 제거
  • list.pop() : 리스트의 맨 마지막 요소를 반환하고 제거 
    • list.pop( i ) : 리스트의  i 번째를 반환하고 제거
  • list.count() :  리스트의 아이템 개수를 반환
    • list.count( x ) : 리스트에 존재하는 x의 개수를 반환
  • list.extend( x ) : 리스트의 끝에 x를 확장

 

C 타입(type)의 변수 할당 및 DLL 사용

 ctypes라는 기본 라이브러리를 사용하면 C Style의 변수와 포인터를 사용할 수 있다. 크아~ 멋지다 @0@)/~!!

  1. from ctypes import *
    import ctypes
  2. # c 런타임 라이브러리를 가지고 있음
    libc = cdll.msvcrt;
  3. buffer = ctypes.c_buffer( 4096 );
    libc.memcpy( buffer, intAddress, 4096 );
  4. # 아래와 같이 DLL을 찾고 DLL에서 함수를 얻어오는 것도 가능
  5. dll = windll.LoadLibrary( dllName );
    functionAddress = dll.GetProcAddress( dll._handle, functionName );

 

if __name__ == "__main__"의 의미

 __name__은 현재 모듈의 이름을 가지고 있는 전역 변수이다. "python a.py" 와 같이 실행되는 경우 __name__의 값은 __main__ 이 된다. 하지만 import a.py 와 같이 사용될 경우 __name__ 의 값은 a가 된다.

 따라서 쉘로 실행되는 경우 실행해야 할 루틴이 있으면 아래와 같이 사용하면 된다.

  1. if __name__ == "__main__" :
    1. print "이것은 쉘로 실행되었을때 호출되는 루틴입니다"; 

 

모듈(Module)을 사용하는 여러가지 방법

  • import a : a.py에 있는 모듈들을 사용한다. a.xxx() 와 같은 방식으로 접근 가능하다.
  • from a imprt XXX, YYY :  a.py에 있는 모듈들 중에 XXX, YYY 함수를 쓴다. XXX(), YYY() 로 접근 가능하다.
  • from a import * : a.py 에 있는 모든 모듈을 쓴다.

 

하드디스크에 있는 오브젝트(Object) 파일 지우기

 참고 : http://docs.python.org/lib/os-file-dir.html

  1. # -*- coding: cp949 -*-
    import os;
    ExtList = [ '.obj', '.idb', '.o', '.pdb', '.scc', '.ilk', '.pch' ];

    buf = '';
    ext = '';
    for p, ds, fs in os.walk('d:\\'):
        #for d in ds:
        #    print os.path.join(p, d);
           
        for f in fs:
            buf = os.path.join(p, f);
            for i in ExtList :
                if len( f ) > len( i ) :
                    ext = buf[-len( i ) : ];
                    if ext.upper() == i.upper() :
                        print buf;
                        os.remove( buf );

 

 

이 글은 스프링노트에서 작성되었습니다.

00 파이썬(Python) 프로그래밍 연습

원문 :  http://kkamagui.springnote.com/pages/391698

 

들어가기 전에...

 

첫번째 프로그램 : input, if, while

  1. # 이건 내 첫번째 파이썬 플그램이다 @0@)/~!!
    # ;는 안붙여도 상관없다. ()도 없어도 상관없다.
    # 문자열은 ''로 감싸던지 ""로 감싸자.
    i = input("loop Count");
  2. # 들여쓰기된 부분은 다 if나 else의 영향을 받는단다.
    if i >= 10 :
        print "Too big"
        exit();
    else :
        print "OK"
  3. while i < 10:
        i = i + 1;
        print i,
       
    password = raw_input("Password");
    while password != 'User':
        print 'Password is not match'
        password = raw_input("Password Retype");
       
    print 'End'

 

두번째 프로그램 : 함수

  1. # 두번째 프로그램
    # 함수 생성 예제, 우와 간단하다 @0@)/~
    def abs( num ):
        if( num < 0 ):
            num = -num;
        return num;
  2. a = 3;
    a = abs( a );
    print a;
  3. a = -1;
    a = abs( a );
    print a;

 

세번째 프로그램 : list, range

  1. #세번째 프로그램 리스트 프로그램
    nameList = ["KKAMAGUI", "NANMAGUI", "NUMAGUI" ];
  2. index = input( "Input Index" );
    print nameList[ index ];
  3. nameList.append( "GAMAGUI" );
    print nameList;
  4. nameList.sort();
    print nameList;
  5. if( "NAMAGUI" in nameList ):
        print "NAMAGUI is in list"
    elif( "NANMAGUI" in nameList ):
        print "NANMAGUI is in list"
    else:
        print "NAMAGUI is not in list"
       
    print "List Length", len(nameList);
    print "NANMAGUI Index is", nameList.index( "NANMAGUI" );
    del nameList[ 2 ];
    nameList.remove( "KKAMAGUI" );
  6. print nameList;
  7. print range( 1, 10 );

 

네번째 프로그램 : module

  1. #네번째 프로그램
    #달력 출력 예제
    import calendar;
    from calendar import prcal;
  2. print "Calendar Print###############";
    calendar.prcal(2007);
    prcal(2007);

  3. #OS 관련...
    import os;
    #import os.path;
  4. print "OS Print###############";
    a = "";
    a = os.path.abspath( a );
    print a;
    a = os.path.dirname( a );
    print a;
  5. a = "";
    os.chdir( "d:/" );
    a = os.path.abspath( a );
    print a;
  6. #시간 관련
    import time;
  7. print "Time Print###############";
    timeString = time.ctime( time.time() );
    print timeString;

 

다섯번째 프로그램 : for, buffer, split

  1. #다섯번째 프로그램
  2. a = "abcdefghijklmn";
    for ch in a:
        print ch;
        print a.index( ch );
       
    print a[ 2 : 20 ];
  3. # 끝에서 하나를 뺀다.
    a = a[ : -1 ];
    print a;
    [ b, c ] = a.split( "f" );
    print b, "+", c;
  4. # 위의 String의 출력결과
    #cdefghijklmn
    #abcdefghijklm
    #abcde + ghijklm

 

여섯번째 프로그램 : urllib로 http 코드 얻기

 내 블로그에 접근해서 최근 덧글부터 뒷부분만 추려서 화면에 출력하는 예제(pyscriptor라는 에디터를 썼다가 한글이 안나와서 삽질.. ㅠ_ㅠ)/~!! ) 가만히 생각해 보니 뒤에서 찾으면 금방 될꺼 같기도 한데.. ㅋㅋ

  1. # -*- coding: cp949 -*-
    #여섯번째 프로그램
    import urllib
  2. urlHandle = urllib.urlopen( "http://kkamagui.egloos.com" );
    buffer = urlHandle.read();
    #print buffer;
  3. text = "최근 등록된 덧글";
    uniText = unicode( text, "euc-kr" );
    text = uniText.encode( "utf-8" );
    #print text;
  4. if text in buffer:
        index = buffer.index( text ) + len( text );
        print "First Index", index;
       
        while text in buffer[ index : ]:
            newIndex = buffer[ index : ].index( text );
            if( newIndex == -1 ):
                break;
            index = newIndex + index + len( text );
            print "New Index", index;
               
        print buffer[ index : ];
  5. else:
        print "FAIL";

 

일곱번째 프로그램 : 파일 입출력

 seek와 readline(), readlines() 테스트

  1. # -*- coding: cp949 -*-
    #일곱번째 프로그램
    sourceFile = open( "c:/test.txt", "r" );
    targetFile = open( "c:/testOutput.txt", "w" );
  2. buffer = sourceFile.read();
    print "읽기의 결과#######";
    print buffer;
  3. #seek
    sourceFile.seek(0, 0 );
  4. # 각 라인별로 라인 번호를 출력해 준다.
    i = 0;
    for buffer in sourceFile.readlines():
        # printf의 식처럼 쓸수 있다. %와 ,를 찍는 위치를 잘 보자
        newBuffer = "%02d %s" %( i, buffer );
       
        # 이 부분은 buffer안의 단어별로 루프를 도는 부분이다.
        for word in buffer.split():
            print word;
           
        targetFile.write( newBuffer );
        i++;
           
    sourceFile.close();
    targetFile.close();
  5. for i in range( 0, 10 ):
        print i;

 

이 글은 스프링노트에서 작성되었습니다.

1.NTSTATUS를 String으로 바꾸기

원문 :  http://kkamagui.springnote.com/pages/370106

 

들어가기 전에...


 

SUMMARY

 Most Kernel Mode API functions return NTSTATUS values. To translate these status values to messages by using the FormatMessage API function, you must reference the NtDLL.dll module in the parameter list.

 

MORE INFORMATION

The following code sample demonstrates how to obtain the system message string.


  1. void DisplayError(DWORD NTStatusMessage)
    {
    LPVOID lpMessageBuffer;
    HMODULE Hand = LoadLibrary("NTDLL.DLL");

    FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM |
    FORMAT_MESSAGE_FROM_HMODULE,
    Hand,
    Err,
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    (LPTSTR) &lpMessageBuffer,
    0,
    NULL );

    // Now display the string.

    // Free the buffer allocated by the system.
    LocalFree( lpMessageBuffer );
    FreeLibrary(Hand);
    }

 

2.DDK에서 문자열 처리 함수 

  • 표준 함수(비권장) 

    • strcpy, wcscpy, strncpy, wcsncpy 
    • strcat, wcscat, strncat, wcsncat
    • sprintf, swprintf, _snprintf, _snwprintf 
    • vsprintf, vswprintf, vsnprintf, _vsnwprintf
    • strlen, wcslen
  • 안전한 유니코드 함수(UNICODE) 

    • RtlStringCbCopyW, RtlStringCchCopyW 
    • RtlStringCbCatW, RtlStringCchCatW 
    • RtlStringCbPrintfW, RtlStringCchPrintfW 
    • RtlStringCbVPrintfW, RtlStringCchVPrintW 
    • RtlStringCbLengthW, RtlStringCchLengthW 
  • 안전한 ANSI 함수 

    • RtlStringCbCopyA, RtlStringCchCopyA 
    • RtlStringCbCatA, RtlStringCchCatA 
    • RtlStringCbPrintfA, RtlStringCchPrintfA 
    • RtlStringCbVPrintfA, RtlStringCchVPrintfA 
    • RtlStringCbLengthA, RtlStringCchLengthA 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

01 Virtual Desktop  (8) 2007.11.15
00 KKAMALENDAR(KKAMAGUI Calendar)  (11) 2007.11.15
10 WinDbg 간단 사용법  (0) 2007.11.14
02 Device Driver Code  (0) 2007.11.14
04 PE 파일 분석-Relocation 분석  (4) 2007.11.14

10 WinDbg 간단 사용법

원문 : http://kkamagui.springnote.com/pages/388856

참고 : "http://0range.net/entry/Windbg을-마스터-하자"

 

들어가기 전에...

 

웹 심볼(Web Symbol) 얻기

 SRV*D:\Symbol\WebSymbol*http://msdl.microsoft.com/download/symbols 를  Symbol Path에 넣으면 웹 심볼을 사용할 수 있다.

 

로드된 모듈 리스트 보기

 lm 명령을 이용하면 된다.

  • lm k : Kernel Mode 모듈 표시
  • lm u : User Mode 모듈 표시
  • lm m : 패턴을 검사하여 해당하는 것만 보여줌 <lm m my*>

 

lkd> lm
start    end        module name
00c80000 00c90000   NateOnHook40u   (export symbols)       C:\Program Files\NATEON\BIN\NateOnHook40u.dll
00cb0000 00cb9000   MgHookDll C (export symbols)       C:\Program Files\LG Software\On Screen Display\MgHookDll.dll
01000000 0106a000   windbg     (pdb symbols)          D:\Symbol\WebSymbol\windbg.pdb\D6EF677AA54441279479F0307F05A8941\windbg.pdb
016a0000 01784000   ext        (export symbols)       C:\Program Files\Debugging Tools for Windows\winext\ext.dll
01790000 017c1000   kext       (pdb symbols)          D:\Symbol\WebSymbol\kext.pdb\6B643FC4E9F94FF4ABA4CEF1FD6F89D61\kext.pdb

 

모듈의 심볼(Symbol) 검사

 x 모듈!패턴 을 입력하면 된다.

lkd> x nt!Ke*
804f8c02 nt!KeQuerySystemTime = <no type information>
804f8c9e nt!KeEnableInterrupts = <no type information>
80500e38 nt!KeSwitchKernelStack = <no type information>
804fad32 nt!KeReadStateProcess = <no type information>
804f9188 nt!KeReleaseInterruptSpinLock = <no type information>

 

데이터 타입(Date Type) 표시

 dt 데이터 타입 을 입력하면 된다.

lkd> dt _EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : [3] Uint4B
   +0x09c QuotaPeak        : [3] Uint4B
   +0x0a8 CommitCharge     : Uint4B

 

 

메모리 덤프(Memory Dump)

 d* 명령들을 이용하면 된다.

  • db : Byte 형식 + Ascii 로 표시
  • dd : 데이터를 4Byte 형식으로 표시

 

lkd> db 8053db18
8053db18  8b ff 55 8b ec 8b 45 08-8b 4d 0c 8b 55 14 89 48  ..U...E..M..U..H
8053db28  0c 8b 4d 10 89 48 10 03-ca 89 48 14 8b 4d 18 83  ..M..H....H..M..
8053db38  c1 fe 89 48 18 8b 4d 1c-89 48 20 66 8b 4d 20 66  ...H..M..H f.M f

 

 

디스어셈블리(Disassembly)

 u 주소 를 이용하면 된다. 특정 함수를 디스어셈블리 하고 싶으면 uf 주소 를 하면 된다.

  • u 주소 : 주소에서 일부분만 디스어셈블리
  • u 주소1 주소2 : 주소1에서 주소 2까지 디스어셈블리

 

lkd> u 8053db18 or uf nt!NtOpenProcess
nt!KeInitializeProfile:
8053db18 8bff             mov     edi,edi
8053db1a 55               push    ebp
8053db1b 8bec             mov     ebp,esp
8053db1d 8b4508           mov     eax,[ebp+0x8]
8053db20 8b4d0c           mov     ecx,[ebp+0xc]

 

메모리 영역 속성 보기(VA Dump)

 !vadump 명령을 사용하면 된다. 만약 특정 메모리의 속성을 보고 싶다면 !vprot 주소 명령을 사용하면 된다.

0:000> !vadump
BaseAddress:       00000000
RegionSize:        00010000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS

BaseAddress:       00010000
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00020000  MEM_PRIVATE

 

0:000> !vprot 30c191c
BaseAddress: 030c1000
AllocationBase: 030c0000
AllocationProtect: 00000080 PAGE_EXECUTE_WRITECOPY
RegionSize: 00011000
State: 00001000 MEM_COMMIT
Protect: 00000010 PAGE_EXECUTE
Type: 01000000 MEM_IMAGE

 

프로세스 관련

 모든 프로세스를 보기위해서는 !process 0 0 를 입력하면 된다. 디버거를 특정 프로세스에 붙이고 싶으면 .process /i [pid] 를 입력하면 된다.

 

lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 8a3a3490  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00780000  ObjectTable: e1001c70  HandleCount: 521.
    Image: System

PROCESS 8a184158  SessionId: none  Cid: 03f0    Peb: 7ffdd000  ParentCid: 0004
    DirBase: 17a40020  ObjectTable: e163dd70  HandleCount:  20.
    Image: smss.exe

PROCESS 89df4da0  SessionId: 0  Cid: 0440    Peb: 7ffd5000  ParentCid: 03f0
    DirBase: 17a40040  ObjectTable: e1c6cb18  HandleCount: 626.
    Image: csrss.exe

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

00 KKAMALENDAR(KKAMAGUI Calendar)  (11) 2007.11.15
60 유용한 팁  (0) 2007.11.14
02 Device Driver Code  (0) 2007.11.14
04 PE 파일 분석-Relocation 분석  (4) 2007.11.14
03 PE 파일 분석-Export 분석  (0) 2007.11.14

02 Device Driver Code

원문 :  http://kkamagui.springnote.com/pages/570024

 

들어가기 전에...

 

 

1.CR0 Write Protect Managment Code

  1. //
    // 콘트롤 레지스터 관련 (IA-32 manual vol3, ch 2.5
    //    CR0 (Control Register Zero) 레지스터의 WP 비트(16)는 쓰기 속성제어에 사용됨
    // 
    #define   CR0_WP_MASK     0x0FFFEFFFF
  2.  
  3. /**
     Write Protect를 제거
    */
    VOID  ClearWriteProtect(VOID)

        __asm 
        {   
            push  eax;   
            mov   eax, cr0;   
            and   eax, CR0_WP_MASK; // WP 클리어   
            mov   cr0, eax;   
            pop   eax; 
        }
    }
  4. /**
     Write Protect를 설정
    */
    VOID  SetWriteProtect(VOID)

        __asm 
        {   
            push  eax;   
            mov   eax, cr0;   
            or    eax, not CR0_WP_MASK; // WP 비트 세팅   
            mov   cr0, eax;   
            pop   eax; 
        }
    }

 

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

60 유용한 팁  (0) 2007.11.14
10 WinDbg 간단 사용법  (0) 2007.11.14
04 PE 파일 분석-Relocation 분석  (4) 2007.11.14
03 PE 파일 분석-Export 분석  (0) 2007.11.14
02 PE 파일 분석-Import 분석  (0) 2007.11.14

04 PE 파일 분석-Relocation 분석

원문 :  http://kkamagui.springnote.com/pages/406762

 

들어가기 전에...

 

개요

 지난번에 PE 파일의 헤더구조 및 Import/Export 섹션에 대해서 알아보았다. 잘 기억이 안나면 01 PE 파일 분석-헤더분석 문서 및 02 PE 파일 분석-Import 분석, 그리고 03 PE 파일 분석-Export 분석 문서를 다시 보자.

 일단 들어가기 전에 재배치(Relocation)이 무엇인지 알아보자. 재배치는 코드에 특정 값을 더해줘서 다른 메모리 주소에서 실행 가능하게 해주는 것을 말한다. 말 그대로 코드를 다시 배치하는 과정인데, 왜 이런걸 해야 하는 걸까?

 EXE 파일의 경우 윈도우에서는 굳이 재배치를 할 필요가 없다. 왜냐하면 로더가 프로그램을 로딩할때 제일 먼저 EXE 파일을 위한 메모리를 할당해주기 때문이다. IMAGE_OPTIONAL_HEADER에 ImageBase라는 필드를 기억할지 모르겠다. 이것이 바로 PE 파일이 로딩될 Base 주소를 의미한다. EXE 파일의 경우 가장 먼저 메모리를 할당 받으므로 ImageBase(일반적으로 0x400000)에 로딩 가능하다.

 DLL의 경우는 어떨까? 실행파일이 사용하는 DLL이 어디 한두개일까? 여러개가 로딩이 되면 당연히 그중에 몇몇 DLL은 ImageBase에 로딩하지 못하는 경우도 발생한다. 이때 어쩔 수 없이 다른 메모리 주소에서 실행해야하는데 이 과정을 재배치 과정이라고 하고 재배치 섹션의 정보가 사용되는 것이다.

 이제 세부 구조에 대해서 알아보자

 

재배치 섹션의 시작

 재배치 정보는 다른 정보와 마찬가지로 IMAGE_OPTIONAL_HEADERData Directory에서 찾을 수 있고 0x05 인덱스(IMAGE_DIRECTORY_ENTRY_BASERELOC)에서 그 RVA를 구할 수 있다.

 재배치 섹션의 처음 시작은 IMAGE_BASE_RELOCATION 구조체로 시작하며 WinNT.h에 아래와 같이 정의되어있다.

  1. typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;
        DWORD   SizeOfBlock;
    //  WORD    TypeOffset[1];
    } IMAGE_BASE_RELOCATION;
    typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

 각 항목은 위와 같이 정의되어있고 아래와 같은 의미를 가진다.

  • VirtualAddress : 재배치가 수행될 메모리 상의 RVA.
  • SizeOfBlock : 재배치 영역의 크기. IMAGE_BASE_RELOCATION 자신 크기를 포함한 전체 크기

 Export 섹션보다 더 간단한 구조를 가진다. 이후에 보면 알겠지만 재배치 데이터 같은 경우 IMAGE_BASE_RELOCATION 구조체 다음에 n개의 WORD 형태로 반복해서 나타나고 그중 하위 0xFFF는 Offset으로 사용된다.

 따라서 재배치할 영역이 크고 넓은 경우 IMAGE_BASE_RELOCATION + n개의 WORD의 형태가 반복되어서 나타나게 된다. 마지막은 역시 데이터가 0인가를 이용하여 판단한다.

 

재배치 정보

 그럼 IMAGE_BASE_RELOCATION 이후에 존재하는 n개의 WORD는 어떤식으로 구성될까?

 상위 4Bit는 재배치 Type으로 사용되며 하위 12Bit는 Offset으로 사용된다. 따라서 코드를 작성한다면 아래와 같이 쓸 수 있을 것이다.

  1. WORD wData;
  2. printf( "Type %X\n", ( wData & 0xF000 ) >> 12 );
  3. printf( "Offset %X\n", wData & 0xFFF );

 Type과 Offset은 아래와 같은 의미를 가진다.

  • Type : 재배치 정보의 타입. 사실 거의 의미가 없고 0일 경우 패딩 데이터
  • Offset : 실제 코드가 재배치 될 영역. VirtualAddress와 더해져서 수정해야할 RVA 값이 됨

 위에서 보듯 실제 코드가 수정되어야 하는 위치는 IMAGE_BASE_RELOCATION의 VirtualAddress와 Offset을 더한 값이 된다. 정말 간단하다.

 여기서 알아두어야 할 점은 Offset이 최대 0xFFF라는 것이다. 즉 4Kbyte까지 커버가 가능하므로 4Kbyte 이상이 되면 다시 IMAGE_BASE_RELOCATION을 만들어서 접근해야 한다.

 이것을 그림으로 보면 아래와 같다.

재배치3.PNG

 

재배치 수행

 위에서 메모리에 어디를 재배치 해야하는가에 대한 정보를 알아보았다. 그럼 과연 그 위치에서 무엇을 해야 정상적으로 동작될까? 개요에서 재배치를 수행하는 이유가 모듈의 시작 위치가 이동되기 때문이라고 했다. 그럼 당연히 이동한 거리만큼 값을 더해줘야 정상적으로 수행이 될 것이라는 것을 알 수 있다.

 재배치를 수행하는 과정은 아주 간단하다. 아래의 순서대로 수행하면 된다.

  1. 재배치가 수행되야 할 곳의 DWORD 값을 읽는다.
  2. 읽은 DWORD 값에서 현재 IMAGE_OPTIONAL_HEADER의 ImageBase를 뺀다. 빼는 순간 코드는 0을 기본 Base 주소로 하는 코드로 변한다.
  3. 뺀 값에 실제로 모듈이 로딩된 메모리 주소를 더한다. 더하는 순간 코드는 실제 모듈이 로딩된 위치에서 정상적으로 수행가능한 코드로 변한다.

 단순한 뺄셈과 덧셈만으로 재배치가 가능하다. 자료 구조가 간단한 만큼 처리 또한 간단하다.

 

실제 구현

 구조가 아주 간단한 만큼 코드도 아주 간단하다. IMAGE_BASE_RELOCATION의 정보가 0일때까지 모든 재배치 정보를 표시한다.

 

 아래는 실행 결과이다.

 재배치1.PNG

재배치2.PNG

 

마치면서...

 간단히 재배치 섹션에 대해서 알아보았다. 아주 심플한 자료구조와 반복된 작업을 수행하면 코드를 어디서든 실행가능하게 할 수 있다는 놀라운 사실을 알게 됬으니, 이제 실행 코드를 임의의 영역에 메모리를 할당 시키고 실행 시키는 것도 가능 할 것이다(왜 자꾸 말만하면 어둠(??)의 세계 이야기가 나오는지 모르겠다... ㅡ_ㅡ;;;; 이러면 안되는데...).

 다음은 기회가 되면 실행파일을 조작해서 특정한 일을 수행하는 내용을 하려 하는데, 약간 민감한 부분이 잇어서 언제가 될지는 모르겠다.

 

첨부

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

10 WinDbg 간단 사용법  (0) 2007.11.14
02 Device Driver Code  (0) 2007.11.14
03 PE 파일 분석-Export 분석  (0) 2007.11.14
02 PE 파일 분석-Import 분석  (0) 2007.11.14
00 윈도우 DLL 분석  (1) 2007.11.14

03 PE 파일 분석-Export 분석

원문 :  http://kkamagui.springnote.com/pages/406170#

 

들어가기 전에...

 

개요

 지난번에 PE 파일의 헤더구조 및 Import 섹션에 대해서 알아보았다. 잘 기억이 안나면 01 PE 파일 분석-헤더분석 문서 및 02 PE 파일 분석-Import 분석 을 다시 보자. 이번에 분석할 부분은 PE 파일에 포함된 Export 영역이다.

 Export 영역은 내가 다른 프로그램을 위한 기능을 제공하기위해 노출한 함수 목록이 들어있다. Import 영역과 비슷한 방법으로 IMAGE_NT_HEADER의 Data Directory를 찾아서 0번째 인덱스(IMAGE_DIRECTORY_ENTRY_EXPORT)를 찾으면 Export 섹션을 구할 수 있다. 역시 RVA를 파일 내의 오프셋(Pointer Of Raw Data)로 바꾸는 작업이 필요하다.

 

Export 섹션의 시작

 Export 섹션의 첫번째는 IMAGE_EXPORT_DIRECTORY 구조체로 되어있으며 WinNT.h에 아래와 같이 정의되어있다.

  1. typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics;
        DWORD   TimeDateStamp;
        WORD    MajorVersion;
        WORD    MinorVersion;
        DWORD   Name;
        DWORD   Base;
        DWORD   NumberOfFunctions;
        DWORD   NumberOfNames;
        DWORD   AddressOfFunctions;     // RVA from base of image
        DWORD   AddressOfNames;         // RVA from base of image
        DWORD   AddressOfNameOrdinals;  // RVA from base of image
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

  각 항목의 역할은 아래와 같다.

  • Name : DLL의 이름을 나타내는 ASCII 문자열. RVA 값
  • Base : 아래에 오는 Address Of Name Ordinals의 시작 서수. AddressOfNameOridianals의 값은 Base의 값을 뺀 형태로 저장.
  • NumberOfFunctions : AddressOfFunctions가 가리키는 RVA 배열의 수
  • NumberOfNames : AddressOfNames가 가리키는 RVA 배열의 수
  • AddressOfFunctions : 함수의 실제 주소가 담긴 RVA 값의 배열. 배열의 인덱스는 아래 AddressOfNameOrdinals에서 구한 값이거나 서수에서 Base 값을 뺀 값
  • AddressOfNames : 함수의 실제 Ascii 문자열이 담긴 RVA 위치값의 배열
  • AddressOfNameOrdinals : 함수의 서수(Ordinal)값의 배열 위치. RVA 값이며 배열은 WORD크기. 실제 해당 함수의 서수는 AddressOfNameOrdinals에서 WORD의 값을 얻은 것에 Base의 값을 더해야 함

 Import 섹션보다 비교적 직관적이고 간단한 구조로 되어있음을 알 수 있다. 이것을 그림으로 보면 아래와 같다.

export2.PNG

 

Export된 함수 주소 찾기

 그럼 함수 이름과 서수를 가지고 어떻게 함수 주소를 찾을 수 있는지 알아보자. 함수의 실제 주소는 AddressOfFunctions 에 저장되어있다. 그럼 해당 함수가 AddressOfFunctions의 어디에 위치하는지 알아야 하는데 함수 이름을 이용해서 찾는 경우와 서수를 이용해서 찾는 경우가 다르다.

1. 함수 이름으로 함수 주소 찾기

 함수 이름은 AddressOfNames 필드를 이용해서 검색하면 함수 이름이 어느 인덱스에 위치하는지 알 수 있다. 이 인덱스로 서수가 들어있는 AddressOfNameOrdinals 에 접근하면 해당 함수 이름의 서수가 얼마인지 알 수 있다( 여기서 서수는 실제 서수값에서 Base를 뺀 상대 서수 값이다). 이 상대 서수값을 가지고 AddressOfFunctions 에 접근하면 함수가 존재하는 실 주소를 알 수 있다.

2. 서수로 함수 주소 찾기

 서수로 함수 이름을 찾는 방법은 아주 간단하다. AddressOfFunction의 함수 주소 인덱스는 서수에서 Base를 뺀 상대 서수값으로 되어있으므로 그냥 Base를 빼서 AddressOfFunction에 접근하면 된다.

3. 참고 : 모든 함수 주소 찾기

함수 이름과 함수 주소를 모두 찾을려면? NumberOfNames 필드 값을 이용해서 해당 Name의 스트링과 서수값을 구하고 그것으로 AddressOfFunctions에 접근하면 모두 구할 수 있다.

 

 아래는 위 3번 방식을 이용해서 간단히 Export된 함수를 Enumeration하는 소스코드이다.

  1. /**
        Export 섹션을 표시한다.
    */
    void DumpExportSection( CPEAnalyzer* pclAnalyzer )
    {
        PIMAGE_EXPORT_DIRECTORY pstImageExportDirectory;
        PIMAGE_NT_HEADERS pstImageNtHeader;
        DWORD dwFileMappedAddress;
        DWORD dwRVA;
        DWORD dwFileOffset;
        DWORD dwNumberOfNameOrdinal;
        DWORD dwOrdinalBase;
        DWORD* pdwAddressRVATable;
        DWORD* pdwNameRVATable;
        WORD* pwOrdinalTable;
        DWORD i;
  2.     pstImageNtHeader = pclAnalyzer->GetImageNtHeaders();
  3.     // Import Section에 대해서 실제 파일 내의 Offset을 얻는다.
        dwRVA = pstImageNtHeader->OptionalHeader.
            DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ].VirtualAddress;
        if( dwRVA == 0 )
        {
            printf( "\n=== No Export Section ===\n" );
            return ;
        }
  4.     dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA( dwRVA );
        dwFileMappedAddress = ( DWORD ) pclAnalyzer->GetMappingAddress();
        pstImageExportDirectory = ( PIMAGE_EXPORT_DIRECTORY )(
            dwFileMappedAddress + dwFileOffset );
  5.     printf( "\n==== IMAGE_EXPORT_DIRECTORY ====\n" );
  6.     // 실제 Name String을 출력한다.
        dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA( pstImageExportDirectory->Name );
        printf( "Characteristics       0x%X\n", pstImageExportDirectory->Characteristics );
        printf( "TimeDateStamp         0x%X\n", pstImageExportDirectory->TimeDateStamp );
        printf( "MajorVersion          0x%X\n", pstImageExportDirectory->MajorVersion );
        printf( "MinorVersion          0x%X\n", pstImageExportDirectory->MinorVersion );
        printf( "Name                  0x%X [%s]\n", pstImageExportDirectory->Name,
            dwFileMappedAddress + dwFileOffset );
        printf( "Base                  0x%X\n", pstImageExportDirectory->Base );
        printf( "NumberOfFunctions     0x%X\n", pstImageExportDirectory->NumberOfFunctions );
        printf( "NumberOfNames         0x%X\n", pstImageExportDirectory->NumberOfNames );
        printf( "AddressOfNameOrdinals 0x%X\n", pstImageExportDirectory->AddressOfNameOrdinals );
       
        // 모두 다 출력할 준비를 한다.
        dwNumberOfNameOrdinal = pstImageExportDirectory->NumberOfNames;
        dwOrdinalBase = pstImageExportDirectory->Base;
  7.     dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA(
            pstImageExportDirectory->AddressOfFunctions );
        pdwAddressRVATable = ( DWORD* ) ( dwFileMappedAddress + dwFileOffset );
  8.     dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA(
            pstImageExportDirectory->AddressOfNameOrdinals );
        pwOrdinalTable = ( WORD* ) ( dwFileMappedAddress + dwFileOffset );
  9.     dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA(
            pstImageExportDirectory->AddressOfNames );
        pdwNameRVATable = ( DWORD* ) ( dwFileMappedAddress + dwFileOffset );
  10.     // Ordinals를 돌면서 실제 함수 이름과 주소를 찍는다.
        for( i = 0 ; i < dwNumberOfNameOrdinal ; i++ )
        {
            printf( "--- Export Function[%d] ---\n", i );
            // 함수 이름
            if( pdwNameRVATable[ i ] != 0 )
            {
                dwFileOffset = pclAnalyzer->GetPointerOfRawDataFromRVA( pdwNameRVATable[ i ] );
  11.             printf( "Name                  0x%X [%s]\n", pdwNameRVATable[ i ],
                    ( dwFileMappedAddress + dwFileOffset ) );
            }
            else
            {
                printf( "Name                  0x%X []\n", pdwNameRVATable[ i ] );
            }
  12.         // 함수의 서수
            printf( "Ordinal               0x%X [0x%X]\n", pwOrdinalTable[ i ],
                pwOrdinalTable[ i ] + dwOrdinalBase );
  13.         // 함수의 주소
            printf( "Function Address      0x%X\n", pdwAddressRVATable[
                pwOrdinalTable[ i ] ] );
        }   
    }

 

실제 구현

 이제 Export 섹션을 출력해 보자. Import 섹션보다 간단하므로 출력하는데 큰 어려움이 없을 것이다.

 이 코드를 가지고 DLL을 출력해 보면 실제 함수가 있는 Address의 주소가 다른 것들이 보일지도 모른다. 이것은 해당 역할을 하는 함수가 다른 DLL이나 EXE의 함수로 포워드(Forward) 된 것으로 함수의 주소가 text 섹션이 아닌 특정 다른 섹션을 가리킨다. 이 값은 Forward된 dll.Function 형태의 스트링 주소를 가리키고 실제 표시를 해보면 그렇다는 것을 알 수 있을 것이다. 이러한 경우가 발생하면 위의 Function Address를 출력하는 부분을 조금 수정하여 스트링을 찍도록 하면 된다.

 

 아래는 실행한 결과이다.

export1.PNG

 

 

 

 

 

 

 

 

마치면서...

 이제 Import/Export에 대해서 모두 알아보았다. 지금까지 문서를 통해 DLL이나 EXE가 어떻게 다른 DLL이나 EXE의 함수를 호출하고 어떻게 제공하는지 대략적인 감을 잡을 것이라 생각한다. 이를 잘 생각하면 어떻게 내가 원하는 함수만 노출하고 다른 함수들은 노출을 줄일 수 있는가에 대한 부분도 어느정도 감이 올 것이다.

 다음은 DLL에서는 빠지지 않는 재배치(Relocation) 섹션에 대해서 알아보자.

 

첨부

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

02 Device Driver Code  (0) 2007.11.14
04 PE 파일 분석-Relocation 분석  (4) 2007.11.14
02 PE 파일 분석-Import 분석  (0) 2007.11.14
00 윈도우 DLL 분석  (1) 2007.11.14
01 FAT 파일 시스템(File System)  (4) 2007.11.14

02 PE 파일 분석-Import 분석

원문 : http://kkamagui.springnote.com/pages/404563

 

들어가기 전에...

 

개요

 지난번에 PE 파일의 헤더구조에 대해서 알아보았다. 잘 기억이 안나면 01 PE 파일 분석-헤더분석 문서를 다시 보자. 이번에 분석할 부분은 PE 파일에 포함된 Import 영역이다.

 Import 영역은 PE 파일이 실행될 때 외부로부터 당겨와서 사용하는 함수들의 목록이 포함되어있으며, Export 영역은 다른 모듈이 사용할 수 있게 노출해 놓은 함수들이 들어있다.

 일단 Import 영역부터 살펴보자.

 

Import 영역 분석

 Import 영역은 PE 파일내의 특정 섹션에 자리잡고 있으며, IMAGE_OPTIONAL_HEADER의 DataDirectory에서 위치를 찾을 수 있다. DataDirectory내의 인덱스는 0x01이며 IMAGE_DIRECTORY_ENTRY_IMPORT 매크로로 WinNT.h에 정의되어있다. Data Directory가 포함하는 정보는 해당 위치의 RVA 값과 크기 정보 이다.

 분석하는 데이터는 실제 파일의 형태로 되어있으므로, 여기서 부터는 섹션의 RVA와 PointerOfRawData의 값을 이용해서 찾아야 한다. RVA 값을 이용해서 어떻게 실제 섹션 데이터가 있는 파일 내의 위치를 찾을까? 답은 섹션 정보에있는 RVA값과 Virtual Size 그리고 PointerOfRawData의 값을 이용하는 것이다.

 그럼 함수를 하나 만들자. 함수의 원형은 아래와 같이 RVA를 넣어주면 실제 파일에서 존재하는 위치를 계산해 주는 것이다.

  1. DWORD GetPointerOfRawDataFromRVA( DWORD dwRVA );

 

 저 함수를 이용해서 실제 섹션내에 존재하는 Import 섹션의 위치를 찾아보자. 어떻게 해야 할까? Import 섹션은 IMAGE_OPTIONAL_HEADER의 Data Directory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]에서 찾을 수 있다고 했다. Virtual Address를 위 함수에 넣으면 실제 파일내의 Offset을 구할 수 있다.

 Import 섹션은 IMAGE_IMPORT_DESCRIPTOR 구조체로 시작한다. 해당 구조체의 배열 형태로 구성되며 마지막은 NULL 구조체로 되어있어 끝을 표시한다. 아래는 WinNT.h에 정의된 내용이다.

  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {
        union {
            DWORD   Characteristics;            // 0 for terminating null import descriptor
            DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
        };
        DWORD   TimeDateStamp;                  // 0 if not bound,
                                                // -1 if bound, and real date\time stamp
                                                //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                                // O.W. date/time stamp of DLL bound to (Old BIND)
  2.     DWORD   ForwarderChain;                 // -1 if no forwarders
        DWORD   Name;
        DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
    } IMAGE_IMPORT_DESCRIPTOR;
    typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

  중요 항목에 대한 설명은 아래와 같다

  • OriginalFirstThunkIMAGE_THUNK_DATA 구조체 RVA 주소를 가리킴. 이때 IMAGE_THUNK_DATA는 Import하는 함수 이름이나 서수(Ordinal)를 포함하는 구조체 IMAGE_IMPORT_BY_NAME을 가리킴
  • Name : Import한 DLL의 이름을 담고 있는 ASCII 문자열의 RVA 주소
  • FirstThunk : OriginalFirstThunk와 같이 IMAGE_THUNK_DATA 구조체의 RVA 주소를 가리키지만 PE 파일이 메모리에 맵핑되고 나면 Import 한 DLL 내의 함수 주소가지고 있는 IMAGE_THUNK_DATA를 가리킴

 

 위의 항목을 보면 Import 섹션에 외부로 부터 당겨쓰는 DLL이나 함수 정보를 모두 포함하고 있음을 알 수 있다. 그럼 OriginalFirstThunk 및 FirstThunk가 가리키는 구조체인 IMAGE_THUNK_DATA의 구조는 어떻게 되어있을까?

 WinNT.h에서 IMAGE_THUNK_DATA에 대한 구조체를 보면 아래와 같이 단순한 공용체임을 알 수 있다.

  1. typedef struct _IMAGE_THUNK_DATA64 {
        union {
            ULONGLONG ForwarderString;  // PBYTE
            ULONGLONG Function;         // PDWORD
            ULONGLONG Ordinal;
            ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME
        } u1;
    } IMAGE_THUNK_DATA64;
    typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
  2. typedef struct _IMAGE_THUNK_DATA32 {
        union {
            DWORD ForwarderString;      // PBYTE
            DWORD Function;             // PDWORD
            DWORD Ordinal;
            DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME
        } u1;
    } IMAGE_THUNK_DATA32;
    typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

 

  공용체이기 때문에 상황에 따라 쓰임새가 다르다는 것을 알 수 있는데, 각 상황에 따라 아래와 같이 해석된다.

  • ForwarderString : 실제 Import 한 함수가 Forwarding된 함수일 경우. Forwarding에 대해서는 Export에서 설명
  • Function : FirstThunk의 경우 메모리에 로딩이 되면 실제 함수 주소를 가리키는 IMAGE_THUNK_DATA의 RVA값을 표시한다고 했는데, 이때 실제 Function의 주소가 포함된 부분
  • Ordinal : Import를 함수 명이 아니라 서수(Ordinal)로 한 경우. 0x80000000 값으로 마스크하면 최 상위 비트를 구할 수 있는데 이것이 1로 셋팅된 경우 서수로 판단
  • AddressOfData : 실제 Import 된 함수의 이름이 포함된 IMAGE_IMPORT_BY_NAME 구조체에 대한 RVA 주소 포함

 위의 공용체를 보면 크게 함수 이름과 서수를 통한 Import가 가능하다는 것을 알 수 있다. 이 말은 곧 export 시에 서수와 함수 이름 모두를 외부로 노출한다는 말인데, 후에 export 섹션을 분석하면서 차차 알아보자.

 이제 살펴볼 마지막은 실제 함수 이름을 포함하는 IMAGE_IMPORT_BY_NAME 구조체이다. 이 구조체는 WinNT.h에 아래와 같이 표시되어있다.

  1. typedef struct _IMAGE_IMPORT_BY_NAME {
        WORD    Hint;
        BYTE    Name[1];
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 각 항목은 아래와 같다.

  • Hint : Import시 서수를 사용하지 않으면 임의로 설정되는 0 base의 숫자, Import 한 함수가 추가될때마다 1씩 증가. 별로 중요한 값은 아닌듯...
  • Name : NULL로 끝나는 Ascii 문자열. 실제 함수 이름을 포함

 

 굉장히 복잡한 구조체들이 얼기설기 엮여있는데, 이것을 메모리 로딩 전의 PE 파일과 메모리 로딩 후의 PE 파일을 비교해서 그림으로 보면 좀 간단하다.

Import3.PNG

<메모리에 로딩 전 PE 파일 구조>

 

 

Import4.PNG

<메모리 로딩 후 PE 파일의 구조>

  Import 섹션을 찾는 순서는 아래와 같다(위 그림을 참조하면서 같이보자).

  1. Data Directory 또는 섹션 헤더에서 Import 섹션의 시작을 찾는다.
  2. 시작부분은 IMAGE_IMPORT_DESCRIPTOR의 구조체로 시작하고 n개가 존재한다. 끝은 구조체의 모든 데이터가 0이면 끝이다.
  3. Name의 RVA를 따라가서 DLL 이름을 뽑는다.
  4. Original First Thunk 또는 First Thunk의 RVA를 따라가서 IMAGE_THUNK_DATA의 구조체 배열에 접근한다. IMAGE_IMPORT_DESCRIPTOR와 마찬가지로 n개가 존재하며 마지막은 구조체의 모든 데이터가 0인지로 판단한다.
  5. IMAGE_THUNK_DATA의 필드 값에 따라 파일명으로 함수를 Import 했는지 서수(Ordinal)로 Import 했는지 판단하여 해당 정보를 뽑는다.

 

실제 구현

 자 이로써 Import 섹션을 분석할 수 있는 모든 조건이 갖추어졌다. 실제 위에서 나온 구조체를 바탕으로 Import 섹션을 한번 분석해 보자.

 주의 할 것은 Borland 사의 링커의 경우 Original First Thunk를 아예 사용하지 않는 다는 것이다. 다행이도 First Thunk 항목은 다 연결되어있으니 First Thunk를 이용해서 덤프하는 것으로 하자. 사실 정확하게 구현을 하려면 Forwarder String까지 해야 하나 예가 드물어서 서수 아니면 함수명이라 가정하고 프로그램을 작성하였다. 혹 문제가 발생하면 ForwarderString 쪽을 처리하면 될 것 같다.

  • PEAnalyzer.h : PE 파일을 분석하는 클래스의 헤더 파일
  • PEAnalyzer.cpp : PE 파일을 분석하는 클래스의 소스 파일
  • main.cpp : 실제 사용하는 예제

 

 아래는 실행 결과이다.

Import1.PNG

<서수(Ordinal)로 Import 한 경우>

Import2.PNG

<이름으로 Import 한 경우>

 

마치면서

 지금까지 PE 파일에서 Import 한 함수들을 분석하는 방법을 알아보았다. Import 섹션은 보안 분야에서 상당히 중요하게 다뤄지고 있는 부분이다. 고전적인 루트킷 같은 경우, 실행 중인 프로세스에 DLL을 삽입하여 Import 섹션의 함수를 후킹해 정보를 빼내는 기능을 하였고, 실행 중인 프로세스에 Import 섹션의 함수를 모니터링 함으로써 후킹을 감시하는 기능도 할 수 있다.

 물론 명시적 로딩(Explicit Loading) 즉 LoadLibrary() 같은 함수를 이용하여 DLL을 로딩하고 함수를 사용하는 경우는 Import 섹션에 데이터가 그리 많이 존재하지 않겠지만 명시적 로딩을 사용하더라도 반드시 2개의 함수 즉 LoadLibrary(), GetProcAddress() 와 같은 함수는 필요하기 때문에 Import 섹션을 분석하는 것은 의미가 있다.

 다음은 Export 섹션에 대해서 알아보도록 하자.

 

첨부

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

'Windows System Application' 카테고리의 다른 글

04 PE 파일 분석-Relocation 분석  (4) 2007.11.14
03 PE 파일 분석-Export 분석  (0) 2007.11.14
00 윈도우 DLL 분석  (1) 2007.11.14
01 FAT 파일 시스템(File System)  (4) 2007.11.14
00 Window I/O 관련  (2) 2007.11.14

00 윈도우 DLL 분석

원문 :  http://kkamagui.springnote.com/pages/388634

 

들어가기 전에...

 

개요

 보안 프로그램들이 실행될 때, 실행 중인 프로세스에 DLL을 밀어 넣는 것은 알만한 사람들은 다 아는 비밀(??)이다. DLL을 밀어넣어서 각종 보안에 필요한 API를 후킹한다던지 하는 작업을 하게되는데... 순간 의문점이 생겼다. DLL이 삽입(Injection)되어서 Kernel32.dll에 포함된 코드들을 변경할 경우, 코드의 영향력은 어디까지 일까? 해당 프로세스에만 영향을 미치는 걸까? 아니면 Kernel32.dll을 사용하는 프로그램 전체에 영향을 미치는 것일까?

 지금까지 여기저기 작업하면서 어렴풋이 해당 프로세스에만 영향을 미칠 것이라고 생각하고 있었는데, 오늘 제대로 한번 분석해 보았다. 일단 대상은 LoadLibrary로 정하였고 내가 Python으로 만든 디스어셈블러를 이용하여 분석하였다.

 

Kernel32.dll 정보 분석

 일단 Kernel32.dll에 있는 LoadLibrary() 함수를 찾아서 그 함수가 존재하는 영역의 메모리 정보를 얻어야 했다. 그래서 아래와 같은 테스트 프로그램을 작성하였다. 아래 코드는 VirtualQuery()와 VirtualProtect() 함수를 이용해서 간단히 메모리 영역에 대한 정보를 보고 변경하는 코드이다.

  1. #include "stdafx.h"
    #include <windows.h>
  2. /**
        Protect String을 표시한다.
    */
    void PrintProtectString( DWORD dwProtect )
    {
        if( dwProtect & PAGE_READONLY )
        {
            printf( "PAGE_READONLY " );
        }
       
        if( dwProtect & PAGE_READWRITE )
        {
            printf( "PAGE_READWRITE " );
        }
       
        if( dwProtect & PAGE_WRITECOPY )
        {
            printf( "PAGE_WRITECOPY " );
        }
       
        if( dwProtect & PAGE_EXECUTE )
        {
            printf( "PAGE_EXECUTE " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READ )
        {
            printf( "PAGE_EXECUTE_READ " );
        }
       
        if( dwProtect & PAGE_EXECUTE_READWRITE )
        {
            printf( "PAGE_EXECUTE_READWRITE " );
        }
  3.     if( dwProtect & PAGE_EXECUTE_WRITECOPY )
        {
            printf( "PAGE_EXECUTE_WRITECOPY " );
        }
  4.     if( dwProtect & PAGE_GUARD )
        {
            printf( "PAGE_GUARD " );
        }
  5.     if( dwProtect & PAGE_NOACCESS )
        {
            printf( "PAGE_NOACCESS " );
        }
  6.     if( dwProtect & PAGE_NOCACHE )
        {
            printf( "PAGE_NOCACHE " );
        }
    }
  7. /**
        State를 출력한다.
    */
    void PrintState( DWORD dwState )
    {
        if( dwState & MEM_COMMIT )
        {
            printf( "MEM_COMMIT " );
        }
  8.     if( dwState & MEM_FREE )
        {
            printf( "MEM_FREE " );
        }
  9.     if( dwState & MEM_RESERVE )
        {
            printf( "MEM_RESERVE " );
        }
    }

  10. /**
        Type을 표시한다.
    */
    void PrintType( DWORD dwType )
    {
        if( dwType & MEM_IMAGE )
        {
            printf( "MEM_IMAGE " );
        }
  11.     if( dwType & MEM_MAPPED )
        {
            printf( "MEM_MAPPED " );
        }
  12.     if( dwType & MEM_PRIVATE )
        {
            printf( "MEM_PRIVATE " );
        }
    }
  13. /**
        Memory Information을 출력한다.
    */
    void DumpMemoryInformation( MEMORY_BASIC_INFORMATION* pstMbi )
    {
        printf( "Type : " );
        PrintType( pstMbi->Type );
        printf( "\n" );
  14.     printf( "State : " );
        PrintState( pstMbi->State );
        printf( "\n" );
  15.     printf( "Base Address : 0x%X\n", pstMbi->BaseAddress );
  16.     printf( "Allocation Base : 0x%X\n", pstMbi->AllocationBase );
  17.     printf( "Region Size : %d\n", pstMbi->RegionSize );
  18.     printf( "Allocation Protect : " );
        PrintProtectString( pstMbi->AllocationProtect );
        printf( "\n" );
  19.     printf( "Protect : " );
        PrintProtectString( pstMbi->Protect );
        printf( "\n\n" );
    }
  20. int main(int argc, char* argv[])
    {
        HMODULE hModule;
        BYTE* pbOpenProcess;
        MEMORY_BASIC_INFORMATION stMbi;
        DWORD dwWriteEnable;
        DWORD dwOldProtect;
  21.     hModule = LoadLibrary( "Kernel32.dll" );
        pbOpenProcess = ( BYTE* ) GetProcAddress( hModule, "LoadLibraryA" );
  22.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  23.     // 일단 막무가네 쓰기 테스트
        // 여기서 이렇게 하면 에러난다.
        //pbOpenProcess[ 0 ] = 0xFF;
  24.     // 정보 출력
        printf( "Befere Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  25.     // Write Enable을 하고나면
        dwWriteEnable = stMbi.Protect;
        dwWriteEnable &= ~( PAGE_READONLY | PAGE_EXECUTE_READ );
        dwWriteEnable |= ( PAGE_READWRITE );
        if( VirtualProtect( pbOpenProcess, stMbi.RegionSize, dwWriteEnable,
            &dwOldProtect ) == FALSE )
        {
            printf( "Write Protect Error\n" );
            return 0;
        }
  26.     // 막무가네 쓰기
        // 이렇게 하면 성공한다.
        pbOpenProcess[ 0 ] = 0xFF;
  27.     // OpenProcess가 있는 영역의 속성을 본다.
        if( VirtualQuery( pbOpenProcess, &stMbi, sizeof( stMbi ) ) == 0 )
        {
            printf( "Virtual Query Error\n" );
            return 0;
        }
  28.     // 정보 출력
        printf( "After Protect Change=================\n" );
        DumpMemoryInformation( &stMbi );
  29.     return 0;
    }

 

 위의 코드를 Console로 만들어서 실행하면 아래와 같이 LoadLibrary() 함수 영역에 대한 정보를 뽑을 수 있다.

MemoryInfo.PNG

 일단 메모리의 STATE가 MEM_COMMIT인 것을 보면 물리 메모리가 맵핑된 상태의 가상메모리 주소임을 알 수 있고, Allocation Protect가 PAGE_EXECUTE_WIRTECOPY 인 것을 보아 쓰기가 되면 메모리가 새로 복사되어 맵핑될 것이라는 것을 알 수 있다.

 실제 위의 코드에서 보면 Before Protect Change와 After Protect Chage 사이에서 메모리에 보호 설정을 변경하여 제일 첫바이트를 0xFF로 변경한 것을 볼 수 있는데, 이때 쓰기와 동시에 Page 한개의 크기(4096Byte)만큼 새로 할당되어 맵핑된 것으로 추측된다. 이것은 위에 Region Size가 4096으로 변경된 것을 보고 추측한 것이다.

 

변경 영향력 검사

 위의 포로그램을 실행한 상태에서 디스어셈블리어를 실행시켜 LoadLibrary()를 디스어셈블리시켜 보았다. 만약 저 변경이 글로벌한 것이라면 디스어셈블리의 결과 첫바이트가 0xFF로 변경되어 나와야 한다. 일단 아래 결과를 보자.

0x7c801d77 (02) 8BFF                 MOV EDI, EDI
0x7c801d79 (01) 55                   PUSH EBP
0x7c801d7a (02) 8BEC                 MOV EBP, ESP

 위 결과에서 보는 것과 같이 0x8B로 변하지 않았다는 것을 알 수 있다. 이것은 변화가 해당 프로세스에 국한 된다는 것을 말한다.

 

결론

 위의 결과로 보아 VirtualProtect를 이용함 메모리 수정은 해당 프로세스에 종속되므로 결국 모든 프로세스에 DLL을 삽입해야 할 것 같다. 어렴풋이 알고 있는 것을 제대로 알게 되어 다행이다. 역시 직접 해보는 것이 최고 @0@)/~!!

 

ps)

만약 kernel32.dll에 실제 물리주소를 얻어서 직접 수정을 가한다면 모든 프로세스에 DLL을 삽입하지 않아도 될 것 같은데... 상당히 위험한 방법일 것 같기도 하고 ㅡ_ㅡ;;;;(많이 위험할래나...)

 

이 글은 스프링노트에서 작성되었습니다.

FAT 파일 시스템(File System)

원문 :  http://kkamagui.springnote.com/pages/345737


들어가기 전에...

1.FAT 파일 시스템 개요

 FAT 파일 시스템은 전통적으로 DOS 시절부터 사용되어 왔다. FAT 파일 시스템이 다른 파일 시스템과 구별되는 점은 파일 내용을 클러스터 단위로 구성하고 이것을 링크드 리스트(Linked List)의 형태로 보관하는 것이다.

 말로 하면 어려우므로, 일단 아래 그림을 보도록 하자.

FAT1.PNG

<FAT의 간략한 구조>



 위 그림과 같이 파일은 고정된 크기의 클러스터(Cluster) 단위로 나누어져 있으며, 그 클러스터들은 클러스터 풀(Cluster Pool)에 존재하는 클러스터를 연결한 클러스터 채인(Cluster Chain)으로 되어있다. 실제 FAT 파일 시스템에서 클러스터 풀은 FAT(File Allocation Table)이라는 용어를 사용한다.

 클러스터(Cluster)란 별개 아니고 여러개의 섹터를 모아서 하나의 블럭 단위로 보는 것이다. 윈도우 포맷 프로그램을 이용해서 포맷을 하면 일반적으로 클러스터의 크기를 4Kbyte로 잡는데, 이 말은 한 섹터(Sector)의 크기가 512byte 이므로 8개의 섹터를 하나의 블럭으로 설정한다고 보면 된다.

 그럼 왜 클러스터라는 말을 쓰는 것일까? 그것은 섹터를 블럭으로 관리하면 얻는 이득이 있기때문이다. 아까 잠시 클러스터 풀에 대한 이야기를 했는데, 고용량일수록 클러스터 풀(FAT 영역)이 커지게 된다. 이것이 커질수록 파일을 읽고 쓸때 관리해야 하는 양이 늘어나게되고, 또한 디스크의 비어있는 공간이 줄어들게 된다. 이것을 블럭으로 관리하게 되면 이런 문제가 해결되며, 또한 블럭 단위로 읽기 쓰기를 수행함으로 얻는 효율로 인해 성능이 좋아지게 되는 것이다.

 특히 요즘처럼 파일의 크기가 큰 상황에서는 클러스터의 크기가 큰 것이 성능 향상에 도움이 된다. 하지만 클러스터의 크기가 무조건 크다고 좋은 것은 아니다. 아까 이야기 했듯이 클러스터의 단위로 작업을 하기 때문에, 작은 파일 같은 경우는 클러스터 하나를 할당받아서 대부분의 영역을 낭비하는 경우도 있으니 적당히 조절해야 한다.


 자 그럼 FAT 파일 시스템의 큰 특징을 알아봤으니 세부적인 내용을 알아보자

 FAT 파일 시스템은 총 3가지의 타입이 있다.

  • FAT12 : 클러스터의 개수가 1 ~ 0xFFF 까지를 가지는 타입. 플로피 디스크에서 주로 사용됨
  • FAT16 : 클러스터의 개수가 0xFFF ~  0xFFFF 까지를 가지는 타입. 소용량에 주로 사용됨
  • FAT32 : 클러스터의 개수가 0xFFFF ~ 0xFFFFFFFF 까지를 가지는 타입. 대용량에 주로 사용됨

 세가지 타입 모두 내부 포맷은 비슷하며, 차이점은 클러스터 개수라고 볼 수 있다.(물론 그것만 차이나는 것은 아니다. ㅡ,.ㅡ;;; 오해하지 말자.). 클러스터의 개수에 따라 FAT 타입을 분류한다고 했는데, 이 클러스터 개수는 어떻게 구하는 것일까? 실제 데이터 영역을 구성하는 크기를 가지고 계산하는 것일까?


 실제 타입 구분에 사용되는 클러스터의 크기는 디스크 전체 크기를 범위로 한다. 즉 아래의 공식으로 클러스터의 크기를 구하고 그것으로 타입을 유추하는 것이다.

클러스터의 개수 = 파티션 또는 디스크의 크기 / 클러스터의 크기

 이렇게 구한 클러스터의 개수는 실제로 사용가능한 데이터 영역의 클러스터의 개수와 비교하면 당연히 큰값을 가진다. 왜냐하면 디스크 또는 파티션 영역에 데이터만 들어가는 것이 아니고 FAT 파일 시스템을 관리하기위한 파일 시스템 메타 데이터(Meta Data)를 포함하기 때문이다.

 이제 파티션과 메타 데이터에 대해서 알아보자.


2.파티션(Partition)

 파티션에 대해서 익히 알고있고 들어봤을 것이다. 디스크 전체를 하나로 사용하는 것이 아니라 그 안에 C: D:와 같이 영역을 분할하여 사용하는 것이 파티션이다. 파티션을 나누면 디스크를 효율적으로 관리할 수 있는 장점 때문에 약간의 디스크 공간을 낭비하더라도 파티션을 해서 많이 사용한다(아래와 같이 나눌 수도 있다).

Partition.PNG


 그럼 어떻게해서 파티션이 나누어지는 것일까? 파티션을 나누게 되면 그 파티션에 대한 정보가 MBR(Master Boot Record)란 영역에 삽입되게 된다. MBR이란 디스크의 첫번째 섹터로 부트 코드와 여러가지를 정보를 포함하고 있다. MBR의 존재는 맨 마지막 510째부터 0x55 0xAA가 있는지 확인하여 이것이 있으면 MBR이 존재한다고 보면 된다(사실 다 있다. ㅡ_ㅡ;;; OS를 깔아 쓰면 저것 말고도 여러 정보가 MBR에 들어가 있다.)

 MBR의 세부구조를 한번 알아보자.

MBR.PNG

 헥사 에디터 프로그램으로 4개의 파티션으로 나누어진 USB 메모리의 MBR을  캡쳐한 화면이다. 좌측 상단에 0xEB 0x3C 0x90의 3Byte가 있는데, 이부분은 부트 코드의 시작으로 jmp 하라는 어셈블리어 명령이다. 굳이 필요한 것은 아니나 윈도우에서 MBR 인식을 위해서는 이 부분이 꼭 필요하다( 이부분이 0x00으로 비어져있는 경우에 윈도우가 MBR로 인식하지 못하는 것을 발견했다).

 우측 하단에 0x55 0xAA의 매직넘버가 보이고 그위에 붉은 색 줄이 쳐져있는 16byte의 라인이 보인다. 이부분이 파티션 데이터의 시작으로 그 앞부분은 모두 Boot Code의 용도로 사용되거나 아니면 사용되지 않는다. 파티션 정보는 16Byte씩 4개로 총 64Byte로 되어있으며 각 항목은 아래와 같은 구조체로 되어있다.

  1. /**
        Partition에 대한 정보를 포함하는 구조체
    */
    typedef struct partitionStruct
    {
        BYTE bBootIndicator;
        BYTE bStartingHeader;
        WORD wStartingSectorAndCylinder;
        BYTE bSystemID;
        BYTE bEndingHeader;
        WORD wEndingSectorAndCylinder;
        DWORD dwRelativeSector;
        DWORD dwTotalSector;
    } PARTITION, * PPARTITION;

  여기서 중요하게 봐야할 부분은 위에서 파란색으로 표시된 부분인데, 그 외 부분들은 옛날 도스 시절에나 사용된 필드기 때문에 무시해도 된다. 각 필드는 아래와 같은 의미를 가진다.

  • Boot Indicator : 부팅 가능한 파티션이면 0x80을 가짐. 그렇지 않으면 0x00
  • System ID : 파일 시스템의 ID. 일반적으로 윈도우에서 사용하는 값은 0x0B. 그 외의 값도 많으나 잘 쓰이지 않음
  • Relative Sector : 파티션이 시작되는 섹터.
  • Total Sector : 총 파티션의 크기

  위 정도값만 제대로 설정하면 윈도우에서 정상적으로 파티션 된 것으로 인식한다. MBR에 파티션 필드는 총 4개가 있으므로 파티션은 최대 4개까지 만들 수 있다. 그럼 도스 시절에는 파티션이 4개 이상이 가능했는데, 이것은 어떻게 한 것일까? 옛날 도스에서는 확장 파티션(Extension Partition)이라는 기능을 사용했다. 즉 MBR 파티션에는 확장 파티션 영역을 표시해 놓고, 확장 파티션 영역의 첫번째 섹터로 이동하면 그 첫번째 섹터에 다시 파티션 정보 + 확장 파티션 정보를 기입하는 식으로 체인으로 연결했던 것이다(이렇게 하면 디스크 용량이 허락하는 한 거의 무한대의 파티션을 생성할 수 있다. ㅡ,.ㅡ;;;;). 도스 이후에는 별로 사용하지 않으므로 일단 넘어가자. 궁금한 사람은 http://www.nondot.org/sabre/os/articles에 가서 Partition 쪽을 보면 된다.



3.파일 시스템 메타 데이터(File System Meta Data)

 FAT_메타_데이터_구조.PNG

 FAT 파일 시스템을 구성하는 메타 데이터는 위와 같은 순서로 구성된다. 각 항목에 대한 설명은 아래와 같다.

  • PBR : 파티션 부트 레코드(Partition Boot Record)의 약자로서 파티션에 대한 전반적인 정보를 저장하고 있음. 핵심적인 부분
  • Reserved : 특수한 용도(Root Directory의 시작위치를 조절하거나 특수한 데이터를 저장할 용도)로 사용되는 공간. 존재하지 않을 수도 있음
  • FAT1/2 : 클러스터 풀로써 클러스터 체인을 구성하는 부분. 뒤에오는 Data Cluster의 개수에 따라 가변적인 크기를 가짐. FAT1은 FAT2은 동일한 데이터를 가지며 FAT는 1만 존재할 수도 있음
  • Root Directory : 파일시스템의 최상위 디렉토리. FAT12/16의 경우 섹터단위로 설정할 수 있고, FAT32의 경우 클러스터 체인으로 구성함.
  • FSInfo : FAT32에만 존제하는 영역으로 FAT32에 속하는 기타 정보를 포함하는 영역
  • Backup Area : FAT32에만 존재하는 영역으로 PBR부터 FSInfo까지를 백업하는 영역. 완전히 복사하여 파일 시스템에 문제가 생기면 Backup 영역에서 데이터를 다시 복사하여 복원하는 역할

3.1 PBR(Partition Boot Record), BPB(BIOS Parmeter Block)

 PBR은 여러 이름으로 불리는데 다른 이름으로는 BPB라고도 한다. 옛날 도스 시절에 BIOS 콜을 통해 파티션에 대한 정보를 얻어오고 처리하고 했는데, 그 기본 정보가 되는 블럭이라서 저런 이름이 붙은 것 같다. 일단 여기서는 PBR이라고 하겠다.

 PBR과 MBR의 차이는 무엇일까? MBR은 하나만 존재하며 섹터 0에만 있고, PBR은 각 파티션의 시작에 존재하며 FAT에 대한 정보를 포함하고 있다는 것이 다르다. PBR에는 어떤 정보가 포함되어있을까? FAT Type에 따라 다르므로 각각 살펴보도록 하자.


 FAT16/32의 PBR에 포함된 데이터는 아래와 같다.

  1. /**
        FAT16에 대한 구조체
    */
    typedef struct fat16Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  2.     // 여기 위까지는 FAT16/32 공통
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 448 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT16, * PFAT16;
  3. /**
        FAT32에 대한 구조체
    */
    typedef struct fat32Struct
    {
        BYTE vbJumpCode[ 3 ];
        char vcCreatingSystemIdentifier[ 8 ];
        WORD wSectorSize;
        BYTE bSectorsPerCluster;
        WORD wReservedSectorCount;
        BYTE bNumberOfFATs;
        WORD wNumberOfRootDirectoryEntries;
        WORD wTotalSectors;
        BYTE bMediumIdentifer;
        WORD wSectorsPerFAT;
        WORD wSectorsPerTrack;
        WORD wNumberOfSides;
        DWORD dwNumberOfHiddenSectors;
        DWORD dwTotalSectors;
  4.     // 여기 위까지는 FAT16/32 공통
        DWORD dwSectorsPerFAT32;
        WORD wExtensionFlag;
        WORD wFSVersion;
        DWORD dwRootCluster;
        WORD wFSInfo;
        WORD wBackupBootSector;
        BYTE vbReserved[ 12 ];
        BYTE bPhysicalDiskNumber;
        BYTE bReserved;
        BYTE bExtendedBootRecordSignature;
        char vcVolumeIDNumber[ 4 ];
        char vcVolumeLabel[ 11 ];
        char vcFileSystemType[ 8 ];
        BYTE vbReservedForSystemUse[ 420 ];
        BYTE vbSignatureWord[ 2 ];
    } FAT32, * PFAT32;

 상당히 많은 데이터를 포함하고 있는데, 일단 FAT16/32에 공통인 부분부터 살펴보자.

  • BYTE vbJumpCode[ 3 ] : Jump Code. 0xEB 0x?? 0x90 또는 0xE9 0x?? 0x??으로 구성되어야 함(이 부분을 제대로 넣지 않을 경우 윈도우에서 파티션 인식이 제대로 안됨)
  • char vcCreatingSystemIdentifier[ 8 ] : Format을 수행한 OS의 이름. 대충 집어넣으면 됨. "MSWIN4.1"와 같은 값.
  • WORD wSectorSize : 섹터 하나의 크기. 보통 512byte
  • BYTE bSectorsPerCluster : 클러스터를 구성하는 섹터의 수. 1Byte이므로 최대 255개까지 클러스터로 설정가능하나 1, 2, 4, 8, 16, 32, 64, 128이 일반적으로 유효한 값.
  • WORD wReservedSectorCount : 파티션 시작 영역부터 FAT 1영역이 시작되기 전에 존재하는 섹터의 수(위 그림 참조). FAT12/16에서는 1, FAT32에서는 32로 권장. 하지만 0xFFFF까지 값을 가질 수 있음. 이 값을 이용하면 Root Cluster의 시작을 적당한 위치로 설정할 수 있음
  • BYTE bNumberOfFATs : File Allocation Table(FAT)의 개수. 1개 또는 2개가 설정 가능하나 일반적으로 2의 값을 가짐.
  • WORD wNumberOfRootDirectoryEntries : Root Directory의 Entry의 개수. FAT12/16에서 사용하며 FAT16에서는 512 권장. FAT32에서는 0.
  • WORD wTotalSectors : 파티션의 크기가 0xFFFF 섹터 이하이면 이 필드 사용. FAT32에서는 0.
  • BYTE bMediumIdentifer : Media의 Type. 일반적으로 0xF8 사용.
  • WORD wSectorsPerFAT : FAT12/16에서 사용되는 값. FAT32에서는 0.
  • WORD wSectorsPerTrack : Track에 포함된 섹터의 수.
  • WORD wNumberOfSides : Size의 개수. 일반적으로 Cylinder의 수.
  • DWORD dwNumberOfHiddenSectors : 파티션 시작 이전에 존재하는 섹터의 수.
  • DWORD dwTotalSectors : 파티션의 크기가 0xFFFF 초과일때 사용되는 필드. FAT32에서는 반드시 이 필드 사용.
  •  BYTE bExtendedBootRecordSignature :  0x29 값으로 설정.
  • char vcVolumeIDNumber[ 4 ] : Volume ID. 일반적으로 일자/시간을 섞어서 만듬. 대충 만들어도 됨.
  • char vcVolumeLabel[ 11 ] : Volume Label. 볼륨 이름.
  • char vcFileSystemType[ 8 ] : "FAT12", "FAT16", "FAT", "FAT32" 같은 문자열.
  • BYTE vbSignatureWord[ 2 ] : 0x55 0xAA

 공통인 부분들에 대해 살펴봤으니, 이제 FAT32에 특화된 부분을 보자

  • DWORD dwSectorsPerFAT32 : FAT32용 FAT 영역 크기를 설정하는 부분
  • WORD wExtensionFlag : FAT1/2 영역이 존재할때, 하나만 사용할 것인지 아니면 둘다 동기화 해서 사용할 것인지 설정하는 플래그. 일반적으로 0으로 설정하여 둘다 동기화 하여 사용하는 것으로 설정.
  • WORD wFSVersion : File System Version. High Byte는 Major, Low Byte는 Minor를 나타냄. 일반적으로 0x00 0x00으로 설정.
  • DWORD dwRootCluster : Root Cluster의 번호. 일반적으로 2로 설정(클러스터 0과 1번은 Signature 같은 용도로 사용).
  • WORD wFSInfo : FSInfo가 위치하는 Sector Offset. 일반적으로 PBR 바로 뒤에 위치하므로 1의 값을 가짐.
  • WORD wBackupBootSector : Backup 영역이 존재하는 Sector Offset. 일반적으로 6의 값을 가짐.
  • BYTE vbReserved[ 12 ] : 예약된 영역. 무조건 0으로 설정.
  • BYTE bPhysicalDiskNumber : Drive Number. 일반적으로 0x80을 가짐.
  • BYTE bReserved : 예약된 영역. 무조건 0으로 설정.

  위의 필드 값을 FAT Type에 따라 PBR에 설정해 주면 반은 끝난 것이다. 약간 주의할 점은 Reserved Sector에 값을 설정했다면 파티션의 시작부터 Reserved Sector에 설정된 값만큼을 0으로 초기화해야한다(적극 권장). 위의 설명에도 나와있듯이 Reserved Sector의 값은 파티션의 시작부터 FAT1/2 이전까지의 섹터 수이므로, 절대 0이 될 수 없다. 왜? PBR이 있기 때문에 @0@)/~!!! 아무리 작아도 1 이상의 값을 가진다.

 이제 다음 메타 데이터를 살펴보자.


3.2File Allocation Table(FAT) 1/2 영역

 FAT 1/2 영역은 클러스터의 개수에 따라서 영역의 크기가 달라진다고 이야기 했다. 여기에서 말하는 클러스터의 개수는 실제 사용가능한 클러스터의 개수를 이야기하는데, 이 크기 이상만 된다면 얼마든지 좀 크게 잡아도 상관없다.

 FAT12의 경우 FAT를 구성하는 클러스터 링크의 비트수가 12bit가 이고, FAT16의 경우  16bit, FAT32의 경우 32bit이다. 따라서 한 섹터를 기준으로 FAT Type에 따라서 포함할 수 있는 클러스터 링크의 개수는 아래와 같이 된다.

  • FAT12 : 512Byte * 8 / 12 = 341.3333 개
  • FAT16 : 512Byte * 8 / 16 = 256 개
  • FAT32 : 512Byte * 8 / 32 = 128 개

 위에서 보는 것과 같이 FAT32로 갈수록 한 섹터에 담을 수 있는 클러스터 링크의 개수가 작아진다. 이것은 동일한 클러스터의 크기를 사용한다면 대용량일수록 FAT의 크기가 커진다는 것을 의미하고 FAT가 커지면 커질수록 실제로 사용가능한 클러스터의 크기가 줄어들게된다. 클러스터의 크기를 적당히 조절하여 FAT 크기를 너무 크지않게 하는 것이 좋다.

 FAT의 0번째 클러스터 링크와 1번째 클러스터 링크는 Signature의 용도 비슷하게 사용되는데, FAT Type에 따라 아래와 같이 설정해 준다.

  • FAT12 : 0xFF8, 0xFFF
  • FAT16 : 0xFFF8, 0xFFFF
  • FAT32 : 0xFFFFFFF8, 0xFFFFFFFF

 자 그럼 이제 FAT의 실제 크기를 한번 계산해 보자. 가장 간단하게 계산할 수 있는 방법은 파티션 전체의 크기를 클러스터의 크기로 나누어 구하는 방법이다. 실제로 이렇게 구하면 PBR + Rerserved Area + FAT1/2 영역의 크기도 사용가능한 것으로 인식하여 계산하기 때문에 낭비가 좀 있지만 계산은 편리하다. 특히 Root Directory의 시작 위치를 맞추거나 할때는 이렇게 계산하여 FAT의 크기를 구하고 여기에 Reserved Area의 값을 조절하여 맞출 수 있어 편리하다.

 또다른 방법은 전체 크기에서 PBR과 Reserved 영역을 제외하고 구하는 방법이다. 낭비가 좀 줄어들긴 한데, 식이 복잡해 진다. 아래는 그 계산 식이다(511은 올림을 위해 넣었다)

FAT의 크기(섹터) = [ ( 전체 파티션 크기(섹터) - PBR(1섹터) - Reserved Sector(?섹터)  )  / 클러스터의 크기(섹터) * 클러스터 링크의 크기 + 511 ] / 512

 이렇게 계산된 크기를 PBR에 wSectorsPerFAT나 dwSectorsPerFAT32 영역에 넣어주면 된다. 만약 wNumberOfFAT의 값을 2로 설정했으면 FAT1/2 영역을 연속해서 만들어줘야 하며 위의 FAT의 크기 * 2 만큼의 영역을 할당해 줘야 한다.



3.3 Root Directory Sector or Cluster 영역

  FAT16/32의 경우 위의 순서대로 PBR, FAT1/2 를 만들고 Root Directory Sector만 생성해 주면 정상적으로 윈도우에서 인식이 가능하다. 만약 Volume Label을 "NO NAME"으로 입력한 경우 Root Directory는 0으로 초기화 해주는 것으로 충분하다. 하지만 Volume Label을 사용한다면 Directory Entry Structure를 생성해서 Volume Label을 삽입해야 한다.


 Directory Entry Structure는 아래와 같이 구성되어있다.

  1. typedef struct directoryStruct
    {
        char vcNameAndExtension[ 11 ];
        BYTE bAttribute;
        BYTE bReservedForNT;
        BYTE bCreatedTimeTenth;
        WORD wCreatedTime;
        WORD wCreatedDate;
        WORD wLastAccessDate;
        WORD wStartingClusterNumberHigh;
        WORD wTimeRecorded;
        WORD wDateRecorded;
        WORD wStartingClusterNumberLow;
        DWORD dwFileLength;
    } DIRECTORY, * PDIRECTORY;

 각 항목은 아래와 같은 의미를 가진다.

  • char vcNameAndExtension[ 11 ] : 파일 이름과 확장자 또는 Volume Label 저장. 0번째 값이 0x00 or 0xE5이면 Free Entry.
  • BYTE bAttribute : Entry의 속성을 표시. 여러가지가 있음(타입은 아래 참조)
  • BYTE bReservedForNT : NT를 위해 사용되는 필드. 0의 값으로 설정.
  • BYTE bCreatedTimeTenth : 생성된 초. 1/10초 단위까지 저장.
  • WORD wCreatedTime : 생성된 시간(포맷은 아래 참조)
  • WORD wCreatedDate : 생성된 날짜(포맷은 아래 참조)
  • WORD wLastAccessDate : 마지막으로 접근한 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberHigh : 클러스터의 상위 16bit 번호
  • WORD wTimeRecorded : 마지막으로 쓴 시간(포맷은 아래 참조)
  • WORD wDateRecorded : 마지막으로 쓴 날짜(포맷은 아래 참조)
  • WORD wStartingClusterNumberLow : 클러스터의 하위 16bit 번호
  • DWORD dwFileLength : 파일의 크기

 조금 복잡한데, 일단 Attribute 부터 보면 아래와 같이 나와있다(첨부 항목에 White Paper 참조).

DIR_ATTRIBUTE.PNG

 일단 포맷 후에 Volume Label을 생성해야 하므로 ATTR_VOLUME_ID를 생성하면 된다. 이때 주의할 것은 Volume Label을 설정하는 Directory Entry의 경우 Name과 Attribute를 제외한 나머지는 모두 0으로 설정해야한다.

 위를 보면 낯 익은 값들도 있을 것이다. 만약 윈도우가 FAT Filesystem으로 포맷되어있다면 위의 항목들이 설정된 Directory Entry 구조체가 여기저기 널려있을 것이다. Hex Editor와 같은 프로그램을 이용해서 하드디스크를 열어서 확인해 보자 @0@)/~~!!!!. 각 항목에 대한 자세한 설명은 White Paper를 참조하자.


 Time과 Date 부분은 공통적인 포맷을 사용하는데, White Paper에 아래와 같이 나와있다(간단한 비트 연산으로 만들 수 있다).

DIR_DATETIME.PNG

 파일 시스템 분석을 하는 경우라면 ClusterNumber와 Name, 그리고 Attribute를 유심히 봐두는 것이 도움이 될 것이다. 다른 파일 시스템이 그러하듯이 Root Directory로부터 Sub Directory가 생성되고 트리 형태로 관리되기 때문에 Root Directory를 찾은 다음 Entry 구조만 안다면 전체를 Travel 하는 것은 어렵지 않으니까...


3.4 FSInfo

  FSInfo 영역은 FAT32에만 존재하는 영역이다. 역시 한 섹터 크기정도로 되어있고 별다른 정보를 포함하지 않는다. 단지 FAT32에 참고할만한 정보만 가지고 있다.

  1. typedef struct fsInfoStruct
    {
        BYTE vbLeadSignature[ 4 ];
        BYTE vbReserved1[ 480 ];
        BYTE vbStructureSignature[ 4 ];
        DWORD dwFreeClusterCount;
        DWORD dwNextFreeCluster;
        BYTE vbReserved2[ 12 ];
        BYTE vbTrailSignature[ 4 ];
    } FSINFO, * PFSINFO;

  각 항목은 아래와 같은 의미를 가진다.

  • BYTE vbLeadSignature[ 4 ] : 0x52 0x52 0x61 0x41 설정
  • BYTE vbReserved1[ 480 ] : 예약된 영역. 0으로 설정.
  • BYTE vbStructureSignature[ 4 ] : 0x72 0x72 0x41 0x61 설정
  • DWORD dwFreeClusterCount : Free한 클러스터의 개수 저장. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • DWORD dwNextFreeCluster : Free한 클러스터의 첫번째 번호. 알 수 없을 경우 0xFFFFFFFF. 권장 값이므로 100% 신용하면 안됨.
  • BYTE vbReserved2[ 12 ] : 예약된 영역. 0으로 설정.
  • BYTE vbTrailSignature[ 4 ] : 0x00 0x00 0x55 0xAA 설정

 위에서 보는 것과 같이 FAT32의 부가적인 정보가 포함된 영역이고 특히 클러스터 관련 필드는 권장값이므로 절대 100% 믿으면 안된다.


4.마치며...

 여기까지 간단하게 FAT Filesystem에 대해서 알아보았다. 원래 훨씬 일찍 마무리 되었어야 하는데... 과제하느라 정신이 조금 없어서 찔끔 찔끔 정리하다보니 이제야 마무리를... ㅜ_ㅜ... 다소 부족한 감이 있지만 Formatter를 개발하면서 알아낸 정보를 기반으로 작성하였다.

 다음에는 위 정보를 기반으로 실제 FAT Filesystem을 분석해 보도록 하자.


5.첨부




이 글은 스프링노트에서 작성되었습니다.

00 Window I/O 관련

원문 : http://kkamagui.springnote.com/pages/340860

 

들어가기 전에...

 

1.윈도우에서 Physical Drive 직접 읽고 쓰기

  • 드라이브 열기
  1. HANDLE OpenDrive( int iPhysicalDriveNumber )
    {
        HANDLE hDevice;
        char vcDriveName[ 30 ];
  2.  

  3.  

  4.     // HDD를 실수로 지우는걸 방지하기 위함

        if( iPhysicalDriveNumber == 0 )
        {
            return INVALID_HANDLE_VALUE;
        }

  5.     // Physical Drive를 연다.
        sprintf( vcDriveName, "\\\\.\\PhysicalDrive%d", iPhysicalDriveNumber );
        hDevice = CreateFile( vcDriveName, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        
        return hDevice;
    }

 

  • 드라이브 읽기
  1. BOOL ReadSector( HANDLE hDevice, DWORD dwSectorOffset, BYTE* pbBuffer,
                     int iSectorCount )
    {
        DWORD dwLow;
        DWORD dwHigh;
        DWORD dwRet;
        DWORD dwRead;
        DWORD dwErrorCode;
  2.     // 움직일 위치는 byte 단위로 되어야 한다.
        // 결국 dwSectorOffset에 512를 곱해줘야 한다.
        dwLow = ( dwSectorOffset << 9 );
        dwHigh = ( dwSectorOffset >> 23 );
  3.     // File Pointer를 이동한다.
        dwRet = SetFilePointer( hDevice, dwLow, &dwHigh, FILE_BEGIN );
        if( dwRet == INVALID_SET_FILE_POINTER )
        {
            return FALSE;
        }
  4.     // Sector를 읽는다.
        if( ReadFile( hDevice, pbBuffer, iSectorCount * SECTORSIZE, &dwRead,
            NULL ) == FALSE )
        {
            dwErrorCode = GetLastError();
            return FALSE;
        }
  5.     return TRUE;
    }

 

  • 드라이브 쓰기
  1. BOOL WriteSector( HANDLE hDevice, DWORD dwSectorOffset, BYTE* pbBuffer,
                     int iSectorCount )
    {
        DWORD dwLow;
        DWORD dwHigh;
        DWORD dwRet;
        DWORD dwWrite;
        DWORD dwErrorCode;
  2.     // 움직일 위치는 byte 단위로 되어야 한다.
        // 결국 dwSectorOffset에 512를 곱해줘야 한다.
        dwLow = ( dwSectorOffset << 9 );
        dwHigh = ( dwSectorOffset >> 23 );
  3.     // File Pointer를 이동한다.
        dwRet = SetFilePointer( hDevice, dwLow, &dwHigh, FILE_BEGIN );
        if( dwRet == INVALID_SET_FILE_POINTER )
        {
            return FALSE;
        }
  4.     // Sector를 쓴다.
        if( WriteFile( hDevice, pbBuffer, iSectorCount * SECTORSIZE, &dwWrite,
            NULL ) == FALSE )
        {
            dwErrorCode = GetLastError();
            return FALSE;
        }
  5.     gs_dwTotalWriteSectorCount += iSectorCount;
        return TRUE;
    }

 

  • 드라이브 닫기
  1. void CloseDrive( HANDLE hDevice )
    {
        CloseHandle( hDevice );
    }

 

2.Drive의 Geometry 읽기

Geometry정보에는 CHS 값이 들어있기 때문에 유용하게 쓸 수 있다.

  1. BOOL GetDriveGeometry( int iPhysicalDriveNumber, GEOMETRY* pstGeometry )
    {
        HANDLE hDevice;
        BOOL bRet;
        DWORD dwOutBytes;
        char vcDriveName[ 30 ];
        DISK_GEOMETRY stWindowGeometry;
  2.     // Physical Drive Number를 저장한다.
        sprintf( vcDriveName, "\\\\.\\PhysicalDrive%d", iPhysicalDriveNumber );
  3.     hDevice = CreateFile( vcDriveName, 0, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
  4.     if( hDevice == INVALID_HANDLE_VALUE )
        {
            return FALSE;
        }
  5.     bRet = DeviceIoControl( hDevice, IOCTL_DISK_GET_DRIVE_GEOMETRY,
            NULL, 0, &stWindowGeometry, sizeof( DISK_GEOMETRY ), &dwOutBytes,
            NULL );
  6.     CloseHandle( hDevice );
  7.     // 지금은 윈도우 Geometry와 같은 Geometry를 사용한다.
        memcpy( pstGeometry, &stWindowGeometry, sizeof( DISK_GEOMETRY ) );
  8.     return bRet; 
    }

 

3.Drive Descriptor 얻기

Descriptor에 보면 해당 드라이브의 제품명과 리비전 번호 같은 걸 알 수 있다.

  1. BOOL GetDeviceDescriptor( char* pcDevice, PSTORAGE_DEVICE_DESCRIPTOR pstDesc )
    {
        HANDLE hDevice;
        STORAGE_PROPERTY_QUERY  stQuery;
        DWORD dwOut;
        BOOL bRet;
  2.     memset( pstDesc, 0, sizeof(STORAGE_DEVICE_DESCRIPTOR) );
  3.     // Device를 Open한다.
        hDevice = CreateFile( pcDevice, GENERIC_READ, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if( hDevice == INVALID_HANDLE_VALUE )
        {
            return FALSE;
        }
  4.     pstDesc->Size = sizeof( STORAGE_DEVICE_DESCRIPTOR );
  5.     // Device Io Control을 호출한다.
        stQuery.PropertyId = StorageDeviceProperty;
        stQuery.QueryType = PropertyStandardQuery;
  6.     bRet = DeviceIoControl( hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
                &stQuery, sizeof( STORAGE_PROPERTY_QUERY ),
                pstDesc, pstDesc->Size, &dwOut, NULL);                 
        if( bRet == FALSE )
        {
            return FALSE;
        }
  7.     CloseHandle( hDevice );
        return TRUE;
    }

 

4.Drive 문자로 Physical Index 얻기

드라이브 문자로 Physical Index를 얻는 방법은 VOLUME 정보를 이용하면 된다.

  1. int GetPhysicalDriveNumber( char cDriveName )
    {
        HANDLE hDevice;
        DWORD dwOut;
        BOOL bRet;
        char vcDriveName[ 40 ];
        VOLUME_DISK_EXTENTS* pstVolumeData;
        int iDiskNumber;
  2.     // 메모리를 할당한다.
        pstVolumeData = ( VOLUME_DISK_EXTENTS* ) malloc( VOLUMEDISKSIZE );
        if( pstVolumeData == NULL )
        {
            return -1;
        }
  3.     // 해당 Drive의 정보를 얻는다.
        sprintf( vcDriveName, "\\\\?\\%c:", cDriveName );
        // Device를 Open한다.
        hDevice = CreateFile( vcDriveName, GENERIC_READ, FILE_SHARE_READ |
            FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL );
        if( hDevice == INVALID_HANDLE_VALUE )
        {
            return -1;
        }
  4.     // Device Io Control을 호출한다.
        bRet = DeviceIoControl( hDevice, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
                NULL, 0, pstVolumeData, VOLUMEDISKSIZE, &dwOut, NULL );
        if( bRet == FALSE )
        {
            free( pstVolumeData );
            return -1;
        }
        CloseHandle( hDevice );
  5.     // Disk 정보가 1보다 작으면 실패
        if( pstVolumeData->NumberOfDiskExtents < 1 )
        {
            free( pstVolumeData );
            return -1;
        }
  6.     iDiskNumber = pstVolumeData->Extents[ 0 ].DiskNumber;
        free( pstVolumeData );
  7.     return iDiskNumber;
    }


5.Logical Drive와 Physical Drive 간의 매치방법

1. 일단 Logical Drive를 검색한다.
-> GetDriveType() 함수를 이용

2. 해당 Drvie를 열어서 Volume 정보를 얻음
-> "\\?\c" 와 같은 형태로 CreateFile() 호출
-> IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS를 날려서 Physical Drive 정보를 얻음
-> 리더기에 데이터가 연결되어있지 않으면 DeviceIoControl에서 문제 발생

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts