Home | Info | Research | Blog | Repos | Messages | Contact Me

 




윈도우에서 Subversion과 Apache, OpenSSL를 연동하여 http와 https 프로토콜을 사용하는 서버를 구성하는 방법을 설명합니다.

이번 문서는 모든 개발 환경을 윈도우에서 구축하고자 하는 분들을 위해 작성하였습니다.

http://www.pyrasis.com/main/SubversionServerForWindows





Do As Infinity Final Concert 영상을 봤습니다. 2005년 9월 29일 공식 해체 후 2005년 11월 25일 마지막 콘서트였습니다.

군대 가기전에 목표가 하나 있었는데, 11월 전역이라 전역 후 DAI 2006~2007 카운트다운 콘서트를 보는것이었습니다. 해체를 해버렸으니 보고싶어도 못보게 되어서 아직도 마음에 남아있습니다.

해체 소식도 까마득히 모르다가 일병때 외박 나왔을때 PC방에서 네이버 뉴스를 보고 알았습니다. 소식을 접하고나서 얼마나 슬프던지...

2004년 제주도에서 DAI를 직접 본걸로 만족해야겠습니다.

일본 가서 콘서트를 볼려면 이제 다른 가수를 좋아해야 하는데 아직도 다른 가수로 마음이 가질 않습니다. 일본음악은 워낙 뽑기라서 제 마음에 드는 가수를 찾는 것도 쉬운일이 아니더군요.

이제 파이널 콘서트에 대해 이야기 해보겠습니다. 말 그대로 파이널 콘서트라서 DAI 대뷔 초의 곡들부터 마지막 앨범인 Need Your Love의 곡들까지 31곡을 불렀습니다.

부도칸의 전 좌석이 꽉 차있었고, 부도칸의 가장 중간에 무대가 있었습니다. 무대도 지금껏 했던 공연중에서도 무대장치도 화려하고 규모도 컷습니다. DAI의 인기를 실감할 수 있었습니다.

여러 명곡들을 불렀지만 그 중에서 Field of dreams를 부를때가 가장 가슴이 찡했습니다. 시부야에서 노상 라이브 할때의 기억을 떠올리며 울먹이는 토미코양...

그리고 이어지는 TAO(道). 이제 정말 곡 제목 그대로 각자의 길로 가는군요.

마지막까지 안나올 줄 알았던 나가오 다이도 나왔습니다. 오와타리 료와 함께 통기타로 Wings를 연주했습니다. 물론 보컬은 토미코양.

아직도 믿어지지가 않지만 정말 끝은 끝인가 봅니다. 토미코양이 하던 말을 끝까지 잇지 못하고 고맙다는 인사를 몇번이나 하더군요.

언제나 시작이 있으면 끝도 있는 법이죠. 비록 아쉽게 해체하긴 했지만 아름답게 활동을 마무리지어서 팬으로써 기쁩니다. 지금까지 정말 수고 많으셨습니다.



이번에는 어셈블리 명령어에 대해서 간단히 알아보도록 하겠습니다.

사실 리버스를 하기 위해서는 어셈블리 명령어만 알면 되는 것이 아니라 인텔 CPU의 동작 방식과 리버스하려는 시스템의 운영체제의 내부 구조와 메모리 관리 방식을 모두 알아야 합니다.


어셈블리는 어셈블리어라고도 부르는데 이 어셈블리어는 명령어들의 조합니다. 인텔 CPU 안에는 이 명령어들이 회로로 구현되어 있어서 우리가 작성한 어셈블리 코드를 실행할 수 있습니다. CPU는 2진수로 모든것을 처리하는데 어셈블리 명령어들도 당연히 2진수로 되어 있습니다. 하지만 2진수로 된 것은 우리가 눈으로 읽기 어려우니까 디스어셈블러나 디버거 같은 프로그램에서 mov, add, push 같이 사람이 읽기 좋은 형태로 변환하여 보여줍니다.

어셈블리 명령어를 영어로는 instruction이라고 합니다. 그리고 명령어 다음에 오는 레지스터 이름이나 값들은 operand라고 합니다. mov eax, 0x100에서 eax와 0x100이 오퍼랜드 입니다.


리버스할때 자주 등장하는 명령어들 부터 알아보도록 하겠습니다.

mov : move, 이름 그대로 데이터를 이동하는 명령어입니다. mov a, b라고 한다면 b의 값을 a에 대입합니다. a <- b 형태죠. 메모리와 레지스터(CPU 안의 기억공간, 모든 연산은 이 레지스터에 저장한 뒤 이루어집니다.) 사이의 데이터 이동, 레지스터와 레지스터 사이의 데이터 이동이나 값을 메모리나 레지스터에 대입할 때 사용합니다. 단 메모리와 메모리 사이의 데이터 이동은 할 수 없고 세그먼트 레지스터와 세그먼트 레지스터 사이의 데이터 이동에도 사용할 수 없습니다. 메모리와 메모리 사이의 데이터 이동은 movs (string move) 명령어를 사용합니다.

cmp : compare, 비교 명령어입니다. cmp eax, 1 형식으로 사용합니다. cmp 명령은 혼자 사용되지 않고 언제나 조건 점프 명령어나 조건 이동(mov) 명령어와 함께 사용됩니다. 조건 점프 명령어는 밑에서 설명하겠습니다.

jmp : jump, 점프 명령어입니다. 프로그램의 흐름을 바꿀때 사용합니다. 이 점프계열 명령어들을 이용해서 C언어의 if, for, while, goto 등을 구현합니다. jmp 00403000 이라면 00403000의 주소로 가서 그곳의 명령어를 실행합니다. 이 점프 명령어는 다음에 설명할 call 명령어와는 달리 되돌아 오지 않고 뛰어넘어간 주소의 다음 명령어를 계속 실행해 나갑니다. jmp 00403000 형태 처럼 주소를 직접 지정하는 방법과 jmp eax 처럼 레지스터에 저장된 주소로 점프하는 방법이 있습니다.

조건 점프 명령어 : 조건 점프 명령어는 위의 cmp 명령어의 결과에 따라 점프하는 명령어입니다.

cmp  eax, 1
je   00403000
mov  ebx, 1

이라면 eax에 저장된 값과 1이 같다면 00403000의 주소로 점프 합니다. 같지 않다면 je 다음에 오는 명령어를 실행합니다.

Unsigned 계열 (부호가 없는 값)
je : jump equal - 비교 결과가 같을 때 점프
jne : jump not equal - 비교 결과가 다를 때 점프
jz : jump zero - 결과가 0일 때 점프, je와 같음. (cmp 명령에서 결과가 같으면 0을 출력합니다.)
jnz : jump not zero - 결과가 0이 아닐 때 점프

ja : jump above - cmp a, b에서 a가 클 때 점프
jae : jump above or equal - 크거나 같을 때 점프
jna : jump not above - 크지 않을 때 점프
jnae : jump not above or equal - 크지 않거나 같지 않을 때 점프

jb : jump below - cmp a, b에서 a가 작을 때 점프
jbe : jump below or equal - 작거나 같을 때 점프
jnb : jump not below - 작지 않을 때 점프
jnbe : jump not below or equal - 작지 않거나 같지 않을 때 점프

jc : jump carry - 캐리 플래그가 1일 때 점프
jnc : jump not carry - 캐리 플래그가 0일 때 점프

jnp/jpo : jump not parity / parity odd - 패리티 플래그가 0일 때 / 홀수일 때 점프
jp/jpe : jump parity / parity even - 패리티 플래그가 1일 때 / 짝수일 때 점프

jecxz : jump ecx zero - ecx 레지스터가 0일때 점프

Signed 계열 (부호가 있는 값)
jg : jump greater - cmp a, b에서 a가 클 때 점프
jge : jump greater or equal - 크거나 같을 때 점프
jng : jump not greater - 크지 않을 때 점프
jnge : jump not greater or equal - 크지 않거나 같지 않을 때 점프

jl : jump less - cmp a, b에서 a가 작을 때 점프
jle : jump less or equal - 작거나 같을 때 점프
jnl : jump not less - 작지 않을 때 점프
jnle : jump not less or equal - 작지 않거나 같지 않을 때 점프

jo/jno : jump overflow / not overflow - 오버플로 플래그가 1일 때 / 0일 때 점프
js/jns : jump sign / not sign - 사인(부호) 플래그가 1일 때(음수) / 0일 때(양수) 점프

조건 점프 명령을 조합하여
if ( a > b )   if ( a >= b )   if ( a < b )   if ( a == b )
for, while ... 등의 조건문을 구현합니다.

push : 메모리상에 설정된 스택이라는 공간에 데이터를 저장합니다. 스택의 자료구조는 접시를 쌓는 것과 같다고 생각하면 됩니다. 접시는 순서대로 쌓고, 절대 중간에 있는 접시를 빼낼 수 없습니다. 맨위에 쌓인 접시부터 차례 차례 빼내는 것입니다. push 명령어는 스택의 가장 윗부분에 자료를 추가합니다. 추가하면서 스택 포인터도 가장 나중에 추가된 쪽을 가리키게 합니다. 일반적으로 접시를 쌓는 동작은 아래에서 위로 스택이 커지지만 x86 아키텍쳐에서는 스택이 위에서 아래로 커집니다.

스택이             실행 전        push 20000000       실행 후
커지는 방향        n      00000000                    n      00000000
↓                 n - 4  00000001 ← esp             n - 4  00000001
                                                      n - 8  20000000 ← esp

pop : 스택에서 자료를 빼냅니다. pop eax 이면 스택에서 가장 나중에 추가된 데이터를 eax에 저장합니다. 그리고 스택 포인터를 그 바로 전에 추가된 데이터 쪽을 가리키게 합니다.

스택이             실행 전          pop eax           실행 후
커지는 방향        n      00000000                    n      00000000
↓                 n - 4  00000001                    n - 4  00000001 ← esp
                   n - 8  20000000 ← esp

call : 함수 호출 명령어입니다. 점프 명령어는 한번에 점프한 곳에서 돌아오지 않지만, 이 call 명령어는 점프했던 곳에서 명령어들을 실행한 뒤 ret 명령어를 사용하여 call 명령어를 사용한 곳으로 되돌아옵니다. call 명령어가 실행되면 복귀주소를 스택에 저장(push)합니다 그래서 ret(return) 명령어가 실행되면 다시 되돌아 올 수 있습니다. 우리가 함수로 구현한 부분을 호출할 때 사용합니다.

ret : 스택에 저장된 복귀 주소로 돌아갑니다. ret 10과 같이 오퍼랜드가 같이 있는 경우가 있는데 이것은 스택 포인터를 지정된 오퍼랜드 만큼 위로 올리는 것입니다. push 등으로 스택을 사용한 뒤 초기화 할 때 사용합니다.

sum(1, 2)를 어셈블리 명령어로 표현하면 대략 다음과 같습니다.

push 2         ; 두번째 인자를 스택에 저장합니다.
push 1         ; 첫번째 인자를 스택에 저장합니다.
call 00403010  ; 함수 sum의 시작 주소로 이동합니다.

00403010:
...            ; 1과 2를 더함
ret            ; sum 함수를 호출한 곳으로 되돌아갑니다.




앞에 올렸던 리버스 엔지니어링이란? 1부에서 이어지는 글입니다.

윈도우용 바이너리를 리버스 한다고 하면 윈도우 API를 알아야 하고 기타 리눅스나 BSD라면 해당 OS의 API를 알아야 합니다. 그리고 당연히 인텔 어셈블리를 알고 있어야 하고, 인텔 CPU가 돌아가는 방식을 알아야 합니다. 프로그램은 단순히 로직만으로 이루어져 있지 않고 시스템의 API를 호출하여 여러가지 동작을 하기 때문에 각 API의 사용방법과 동작결과를 알고 있어야 어셈블리 코드에서 C소스 코드로 옮기고 다시 소스를 재구현할 수 있기 때문입니다. 어플리케이션을 리버스하는 경우 윈도우 커널에 대한 지식이 없어도 리버스가 가능하지만 커널 레벨로 동작하는 드라이버나 기타 서비스는 윈도우 커널에 대한 지식이 필요합니다.

어셈블리에서 C소스 코드로 옮기는 작업은 한마디로 단순 반복작업입니다. 디스어셈블된 코드에서 call은 함수이고 각종 점프 명령어들은 if, for, while, switch, goto 등의 C언어 제어문입니다. mov 명령어는 변수에 값을 대입한다거나 하는 것이고 push, pop 명령은 함수를 호출하면서 인자값을 넘겨줄 때 사용합니다. 이 어셈블리 명령의 조합을 읽어 C코드로 구현을 하면 됩니다.


각종 도구들

리버스 엔지니어링에서는 디스어셈블러라는 도구가 매우 필수적입니다.
자주 쓰이는 것들로는 리버스계에서 아주 유명한 소프트아이스(SoftICE, 이하 소아)라는 프로그램이 있습니다. 소아의 특징으로는 윈도우가 돌고 있는 상태에서 Ctrl+D를 누르면 윈도우가 멈추고 소아 창이 떠서 현재 실행되고 있는 어셈블리 코드를 보여줍니다. 정말 강력하고 편리한 기능입니다. 이 기능이 소아를 쓰는 이유이기도 합니다. 물론 그냥 바이너리를 열어서 디스어셈블도 가능하며 요즘은 비주얼 소프트아이스라고 나와서 원격 디버깅도 가능합니다. 단 비쌉니다.

WinDBG(Windows Debugger)는 MS에서 배포하는 무료 디버거인데 윈도우 내부를 분석하는데 매우 유용한 도구입니다. 심볼 서버에서 심볼 파일을 받아와서 윈도우 내부 DLL들의 함수이름과 구조체 등을 볼 수 있습니다. 물론 이것도 그냥 바이너리를 열어 디스어셈블이 가능합니다.

OllyDBG, 아주 편리한 도구입니다. 리버스하기에는 딱 알맞은 도구가 아닌가 싶습니다. 디스어셈블 뿐만 아니라 이 디버거가 어셈블리 코드를 분석하여 사용자에게 많은 정보(함수 이름, 인자값 이름, 서브루틴끼리 묶어주는 기능, 점프명령의 도착점 표시 기능 등)를 제공해 줍니다.

W32dasm는 OllyDBG처럼 인터렉티브 하지는 않지만 상당히 쓸만한 도구입니다. 기타 IDA나 PE Browse등의 프로그램이 있는데 사용자 취향에 따라 골라 쓰면 되겠습니다.

거의 모든 디버거에서 레지스터, 스택, 메모리 상태 등을 표시해 주고 있으므로 코드 분석에 많은 도움이 됩니다.


마지막으로 주의할 점은 같은 바이너리를 디스어셈블한다고 하더라도 디스어셈블러마다 분석해 내는 코드가 조금씩 다른 경우가 있습니다. 그래서 한가지 디버거만 쓰다 보면 엉뚱한 코드를 보기 쉽습니다. 여러가지 디버거를 돌려 가면서 코드를 분석하는 것도 좋은 방법입니다.




리버스 엔지니어링이란?

리버스(reverse)라는 말은 반대, 역(逆)의 뜻을 가지고 있는데, 리버스 엔지니어링을 역공학이라고 쓰기도 합니다. 리버스 엔지니어링은 목표가 되는 프로그램이나 프로토콜을 분석하여 똑같은 동작을 만들어 내는 것을 말합니다.


리버스 엔지니어링의 종류

통상적으로 컴파일된 바이너리(EXE, DLL, SYS 등)를 디스어셈블러라는 도구를 이용하여 어셈블리 코드를 출력한 후 그것을 C언어 소스형태로 다시 옮겨 적고 적당한 수정을 통해 리버스하고 있는 파일과 동일한 동작을 하는 프로그램을 만드는 것이 있습니다.

모든 어셈블리 코드를 소스 형태로 옮기지 않고 그냥 동작 방식만을 알아낸다거나 일정 부분만 수정하는 것들도 리버스 엔지니어링이라고 할 수 있습니다. 예를 들면 바이러스를 분석하는 일은 모든 코드를 알아낼 필요가 없기 때문에 동작 방식만 알아내면 됩니다. 그리고 크랙처럼 일정 부분만 수정하여 사용제한을 푸는 것 등도 이에 해당됩니다.

실행파일을 디스어셈블 하지 않고도 그 실행파일이 만들어내는 데이터 파일이나 패킷등을 분석하여 똑같이 재구현하는 것도 리버스 엔지니어링입니다. 예를 들면 오래전 PC 게임에서 많이 하던일인데, HEX 에디터 등으로 세이브 파일을 분석하여 에디트를 만들거나 게임자체를 조작하는 것이 있고, 당나귀와 호환되는 이뮬 같은 프로그램은 당나귀 프로토콜의 패킷을 분석하여 동일한 동작을 하도록 만들어낸 것입니다.


리버스 엔지니어링에서 가장 많이 사용되는 방식은 첫번째로 이야기 했던 바이너리를 디스어셈블 하여 코드를 얻어내는 것입니다. 이것을 하기 위해서는 먼저 인텔 어셈블리를 배워야 하고, 물론 C언어도 알아야 됩니다.

그런데 여기서 컴파일된 바이너리가 VC나 gcc등으로 컴파일한 것이 대부분이지만 비주얼 베이직으로 컴파일 한 것도 있고 델파이(파스칼)로 컴파일 한 것도 있을 것입니다. 이 바이너리들은 모두 CPU에서 직접 실행되는 것들이기 때문에 디스어셈블 해보면 모두 똑같은 방식으로 되어 있습니다. 그래서 VC, VB, 델파이(파스칼)등으로 컴파일 된 것도 디스어셈블 한 뒤 C 소스 코드로 옮길 수 있습니다. 물론 리버스 하는 사람이 VB나 파스칼로 옮겨 적을 수도 있을 것입니다. 하지만 C언어로 하는 것이 가장 간편합니다.

대부분 리버스해서 얻어내는 것들은 프로그램의 로직(알고리즘)이기 때문에 어느 언어로 표현하든 결과는 똑같기 때문입니다.


자바나 닷넷으로 컴파일 된 바이너리는 CPU에서 직접 실행되지 않고 자바 가상머신이나 닷넷 프레임워크를 통해서 실행됩니다. 그래서 자바로 컴파일된 바이너리를 열어보면 자바 바이트코드 문법으로 되어 있고 닷넷으로 컴파일 된 것은 MSIL이라는 문법으로 되어 있습니다. 이런 것을은 인텔 어셈블리와 문법이나 명령어가 다르므로 따로 자바 바이트코드나 MSIL을 공부해서 리버스 하면 되겠습니다.






PYRASIS.COM 블로그를 모니위키 내장 블로그에서 테터툴즈 기반으로 이전했습니다.

모니위키 내장 블로그로 썻던 글들은 변함없이 모니위키를 통해서 볼 수 있으며, 앞으로 새로운 글은 테터툴즈로 쓸 예정입니다.

RSS 주소는 예전 모니위키의 RSS 주소를 그대로 사용하실 수 있습니다. 새로 등록하고자 하시는 분들은 http://feeds.feedburner.com/pyrasis을 사용하시면 됩니다.

또한 기존 모니위키를 통해 제공되는 문서들의 인코딩이 euc-kr이었는데 이번기회에 UTF-8로 바꾸게 되었고, 모니위키도 최신 버전으로 업데이트 하였습니다.