2009.03.01 10:45

OllyDbg/Hiew 분기 어셈코드 넣는 방법

바이너리로 되어있는 exe파일에 section을 추가하고, Import table을 추가한뒤에 새로 imported된 API를 호출해 기존프로그램에 기능을 추가하는.. 일종의 ReverseMe 프로그램을 만지작 거리다 왔다.

간단히 두개의 API를 import테이블에 추가해서 호출하면 되는데 공간이 없는 바람에 section을 새로 만들고.. 언제나 그렇듯이 뭔가가 하나가 플러스되면 항상 삽질도 반비례하게 플러스되고, import table에 함수를 넣는과정에서도 역시 삽질.. 그리고 마지막으로 기능추가 어셈코드를 추가할때도 잠깐 삽질..

그 과정을 거쳐 몇시간만에 끝을냈다. 씨~ 이건 완전 노가다야!! 역시 소스코드가 없는 프로그램을 조작한다는건 정말 만만치 않은 작업이라는걸 새삼 깨닫게 된다.

물론 오늘 이 작업을 통해 느낀것이 있다. 주로 툴의 사용법을 익힌것이 그것인데 지금부터 몇가지를 노트삼아 적어놓는다.


일단 Import table에 대한 이해를 먼저 하고 넘어가자.

모든 imported 함수는 IMAGE_IMPORT_DESCRIPTOR 안에 임포트 정보를 가지고 있다. 잠깐 PE import관련 구조체를 살펴보자.

IMAGE_IMPORT_DESCRIPTOR
DWORD OriginalFirstThunk
DOWRD TimeDateStamp
DWORD ForwardChain
DWORD Name
DWORD FirstThunk


위 구조체는 구조체 하나당 dll하나씩을 떠맡는다. 만약 import하고 있는 dll이 3개라면 저러한 구조체 3개가 pe파일상에 배열처럼 나란히 배치되게 되며, 저 구조체의 끝을 알리기 위해 위의 구조체 크기만큼인 14byte가 0x00으로 채워지게 된다.

TimeDateStamp, ForwardChain은 별로 중요하지 않다. 신경꺼도 무관하고 Name은 어떤 dll을 임포트하고 있는지 dll이름의 RVA정보를 가지고 있다. 다시한번 말하지만 dll이름을 가리키는것이 아니라 dll이름 문자열이 저장되어있는 곳의 RVA를 가지고 있다.

드디어 중요한 값으로 넘어가 보자.
저기서 OriginalFirstThunk는 어떠한 값을 가지고 있느냐?
바로 IMAGE_IMPORT_BY_NAME 구조체의 RVA를 가지고 있다. IMAGE_IMPORT_BY_NAME 은 임포트하고있는 함수의 Ordinal 정보와 그 함수의 이름을 가지고 있는 구조체이다. OriginalFirstThunk가 IMAGE_IMPORT_BY_NAME을 가리키고 있는것이 아니라 그 구조체의 RVA를 가지고 있다는 점을 명심하도록 하자.

그리고 그 RVA는 역시 배열로 나란히 되어있다. 만약 dll하나를 임포트하고 있다면 IMAGE_IMPORT_DESCRIPTOR 구조체는 딱 하나가 있고 그 구조체 크기(0x14)만큼 0x00으로 채워져 그 배열의 요소가 딱 하나뿐(dll을 하나만 임포트)이라는 것을 나타내고..

그리고 이 dll안에서 함수를 3개 호출해서 쓴다면 OriginalFirstThunk는 pe파일상에 3개로 배열처럼 나란히 놓여지게 되며 그 끝을 알리기 위해 위 자료형의 크기(0x04)만큼 0x00으로 채워져 있게 된다. 다시한번 말하지만 OriginalFirstThunk의 각 배열요소는 IMAGE_IMPORT_BY_NAME구조체의 RVA를 가리키고 있다.

아참. FirstThunk는 OriginalFirstThunk 와 같은 값을 가리켜도 된다. 어차피 이 값은 PE로더가 pe파일을 메모리에 올려놓은다음 동적으로 채워주는 값이기 때문에 0x00으로 채워져 있어도 무방하다. 다만 중요한 것은 OriginalFirstThunk와 FirstThunk가 각각 어떤 용도로 쓰이는지를 아는 것이다.

OriginalFirstThunk는 일반 역어셈툴, 디버거 툴등에서 PE파일만을 가지고 import 정보를 얻기 위해 존재하는 멤버이며, FirstThunk는 pe가 pe loader에 의해 메모리에 올라가 실행될때, 해당 import 함수.. 즉 dll안의 특정 함수가 어느주소에 있는지 동적으로 값을 채워넣어 호출을 용이하게 하기 위함이다. 왜 굳이 동적으로 넣어야하느냐? 너무 당연한 말이지만.. dll가 메모리에 맵되는 주소는 항상 달라지기 때문이다. 말 그대로 Dinamic Link Library(동적 연결 라이브러리) 이기 때문에!

여기까지 이해가 되었다면 오케이! 이젠 OllyDbg/Hiew 툴에서 어떻게 분기코드, 호출코드를 넣는지 팁을 적는다.


OllyDbg에서 import 하고있는 함수 호출하기.

call dword ptr [임포트함수의 Virtual Address]

너무 간단하게 설명하면 나중에 다시봐도 모르는 경우가 생기니 조금 설명을 하자면..
좀전에 모든 Import함수들은 OriginalFirstThunk 배열에 RVA로 값이 들어가 있다고 했는데 바로 이 RVA값을 [ ] 안에 Virtual address로 변환해서 넣어주면 된다.

가령 OriginalFirstThunk 안의 값(즉, IMAGE_IMPORT_BY_NAME 구조체의 RVA)이 0x30D8 이고 PE파일의 ImageBase가 0x400000 이라고 한다면

call dword ptr [4030D8]

이렇게 호출해주면 될것이다.

OllyDbg에서 분기명령을 사용하기

분기명령엔 jmp, je, jne 등등이 있다. 하지만 이 분기명령에도 기계어 코드에 따라 두가지로 나뉜다고 볼 수 있다. 하나는 점프할 곳이 현재 점프를 하려는 곳의 주소와 근접한 주소일때 사용하는 short 점프와, 점프할 곳이 현재 점프를 하려는 곳의 주소와 근접하지 않고 떨어져있는 주소일때 사용하는 long 점프가 있다.

라는 정보를 알고있긴 있었는데 그 차이점을 제대로 몰랐던게 사실이다. 이번에 확실히 짚고 넘어가도록 한다.

jmp명령의 오퍼랜드 값은

오퍼랜드 = 점프할 주소 - (현재 명령줄의 주소 + 명령줄의 기계어 코드 크기)
이런공식에 의해서 만들어 진다. short 와 long 점프로 나누는 기준은 저 오퍼랜드의 값에 달려있다. 저 값이 0x80이하면 short 점프가 가능하고 0x80을 초과하면 long 점프를 해야만 한다.

OllyDbg에서 short jmp를 하는방법은

je short 이동할주소
jmp short 이동할주소


OllyDbg에서 long jmp를 하는 방법은

je 이동할주소
jmp 이동할주소


이런식으로 적어주면 된다. 반드시 short 점프를 할 경우엔 명령줄에 short를 명시해 주어야만 한다.



Hiew에서 임포트 함수 call하기

call d,[임포트함수의 Virtual Address]

위에서 d,는 []값이 Double word라는 것을 명시해주는 것이다. 테스트는 안해봤지만 안의 값이 word라면 w, 를 넣어야 할듯하다.



Hiew에서 분기명령 사용하기

역시 Hiew에서도 short점프 long점프를 다음과 같은 방법으로 선택해서 사용가능하다

short 점프 : 오퍼랜드에 점프할 주소의 RVA를 넣어준다.
long 점프 : 오프랜드에 점프할 주소르 Virtual Address를 넣어준다.


RVA계산하기 정말 짜증나는게 사실이다 ㅡ_ㅡ; 그것만 빼면 아주 편하다.


출처 : http://nevernding.egloos.com

Trackback 0 Comment 0