2018. 12. 15. 13:59 악성코드 분석
x64dbg 분석 팁
0. 개요
1. 팁
2. 명령어
3. 구조체 관련
4. 플러그인
0. 개요
x64dbg는 기본적으로 OllyDbg와 매우 비슷하기 때문에 굳이 여기에서까지 상세한 정리를 할 필요는 없을 것 같다. 다음 링크를 보고 필요한 부분은 찾은 후에 x64dbg에서 직접 찾으면 될 것 같다. [ http://sanseolab.tistory.com/8 ]
그렇기 때문에 여기서는 OllyDbg에서는 없는 부분이던가 아니면 기능이 추가된 부분을 위주로 설명한다. 아직까지는 많이 파악하지 못했기 때문에 이 포스트에서 전체를 담을 것이며 이후에 계속 추가되어 가다가 양이 많아지면 주제에 따라 나눌 생각이다.
1. 팁
- 먼저 수 많은 창들이 이미 켜져 있는 것 같지만 몇 개는 덜 보이는 것들이 있다. 대표적으로 Label (레이블) 창이 그렇다. OllyDbg에는 없는 기능인데 평소 굉장이 필요하다고 생각했던 것이었다. 그리고 이것에 더해 "함수" 창도 유용하게 사용할 수 있다. 다른 기능들도 많으며 명령어를 사용하는 기능의 경우에는 아래에서 설명하겠다.
- Label과 관련해서는 OllyDbg 문서에서 찾아보면 되고 여기에서는 x64dbg의 추가적인 부분을 설명한다. OllyDbg에서는 Label을 통해 함수 첫 부분이나 메모리의 주소에 Label을 달아서 그 부분을 참조할 때 즉 함수 호출이나 메모리 참조 시에 주소 대신 이름으로 보여줄 수 있게 해주었다.
x64dbg에서는 이것에 더해서 함수인 경우에 추가적인 기능을 제공해 준다. 함수 주소의 첫 부분을 마우스 오른쪽 버튼으로 클릭한 경우 함수에 등록되어 "함수" 창에서 볼 수 있다. 디폴트로 이름은 sub_address 형태이지만 이후 Label을 붙여주면 함수 이름도 변경할 수 있다. 기본적인 Label 기능에 함수만 따로 정리해서 볼 수 있는 기능이 추가된 것 뿐이지만 막상 디스어셈블러의 결과를 임포트하거나 직접 분석하면서 이름 붙인 경우에 직접 하나씩 찾는것 보다는 훨씬 편한 기능이다.
- "핸들" 창과 관련해서도 OllyDbg보다 훨씬 많은 정보를 주기 때문에 분석 중에 유용하게 사용할 수 있다.
2. 명령어
잘 쓰지는 않겠지만 대부분의 명령어들이 간단해서 쉽게 사용할 수 있을 것이다. 문제는 예제로서 문서화가 잘 되어있지 않고 설명도 부족한 것이 많다.
명령어의 경우 간단한 결과는 명령어 창의 아래에서 바로 확인 가능하지만 제대로 된 결과를 원한다면 "로그" 창을 확인해야 한다.
- exhandlers : 이 명령어는 SEH 뿐만 아니라 VEH, Unhandled 핸들러까지 모두 보여준다. 물론 대부분 SEH의 결과를 원할 것이기 때문에 스택 창과 "SEH" 창만 확인해도 별 문제 없겠지만 분석하다 Unhandled Exception 핸들러 함수도 종종 중요하게 사용되기 때문에 분석할 때마다 여기에 BP를 걸어놓지 않아도 이 명령으로 쉽게 찾을 수 있다는 것이 장점이다.
문제는 일반적인 경우 SEH 핸들러만 결과가 나오고 나머지는 symbol과 관련된 문제 때문에 정보가 나오지 않는다. 이 경우에는 심볼을 다운로드 받아야 한다.
- symdownload / downloadsym : x64dbg는 직접 설정할 필요 없이 디폴트로 마이크로소프트의 심볼 서버가 등록되어 있다. 문제는 이것을 명령어를 통해 직접 다운로드 받아야 한다는 것이다. 간단하게 이 명령어를 사용하면 심볼을 다운로드 받아 분석에 도움도 되며 위의 exhandlers 명령어를 제대로 사용할 수 있다. 참고로 모든 심볼을 다운로드 받는 것이 아닌 현재 바이너리에서 사용되는 dll들의 심볼만 다운로드 받는다. 심볼을 이용하는 방식은 OllyDbg도 가지고 있는 기능이다.
- hidedebugger : 안티디버깅과 관련해서는 대부분 플러그인을 쓰겠지만 x64dbg 내부적으로도 간단한 기능이 존재한다. 이 명령어가 정확히 어떤 기능을 하는지 찾아보니 x64dbg의 소스에는 없고 x64dbg가 사용하는 디버깅 엔진인 TitanEngine의 소스 코드에서 확인할 수 있었다. x64dbg에서는 내부적으로 PathAPILevel을 0으로 설정해서 호출하기 때문에 실질적으로 PEB 관련 기능만 사용한다. 즉 PEB.BeingDebugged를 false로, PEB.NtGlobalFlag를 NULL로 설정해 주는 기능이다.
2.1 언패킹
다음 링크에 간단한 명령어를 이용한 언패킹 스크립트들이 있다. 패커들의 경우 특별히 안티디버깅 기법들이 없기 때문에 몇 줄의 명령만으로 OEP까지 자동으로 갈 수 있다. upx, mpress, ASPack 등 다수의 패커들 뿐만 아니라 지원되는 경우 x64 버전까지 OEP로 갈 수 있게 해주는 스크립트들이다. [ https://github.com/SanseoLab/ejUnpacker ]
3. 구조체 관련
OllyDbg 문서에서 설명하였듯이 구조체 기능이 있어서 가끔은 유용하게 사용할 수 있다. OllyDbg는 기본적으로 자주 사용되는 구조체 DB를 지원해 주며 문서화되지 않은 방식으로 사용자가 직접 추가하는 방식도 존재한다.
x64dbg에서는 기본으로 제공해주는 구조체 DB는 존재하지 않는다. 그래서 직접 만들어야 한다. 개인적으로 분석을 공부하면서 눈에 띄는 구조체들을 찾아서 직접 추가한 후 깃허브에 올리도록 하겠다.
사실 구조체는 객체 지향 언어로 만든 프로그램을 분석할 때 유용하게 사용할 수 있다. C++나 델파이에서 객체는 결국 구조체 형태로 사용되므로 멤버 인스턴스들의 구조를 쉽게 파악할 수 있다. 문제는 객체라는 것이 할당과 해제가 반복되는데 그때마다 적용하는 것 까지는 좋지만 결국 찾아봐야 한다는 점이다.
그래서 개인적으로 플러그인을 만들 생각이다. 첫 번째 기능은 PEB나 TEB 같이 프로세스 시작 시 항상 같은 것들의 경우 초기화 시에 구조체를 파악하고 이후 자동으로 Label을 달아주는 것이다. 많은 패킹된 악성코드들이 Ldr 구조체 등을 이용해 Kernel32.dll의 Export Table로 LoadLibrary()나 GetProcAddress() 함수 호출 대신 직접 함수 주소를 구한다. 이 경우 해당 구조체에도 Label을 달아주면 진행하면서 주소 대신 구조체 멤버의 이름을 확인할 수 있으므로 대충 보더라도 이 부분인 것을 확인할 수 있을 것이다.
다음으로 객체의 경우 분석이 선행되어야 하겠지만 멤버를 파악한 이후에는 다시 분석할 때 할당 이후 직접 이 플러그인을 사용하여 자동으로 구조체의 주소와 멤버에 Label을 달아줌으로써 이후 여기에 접근할 때 주소 대신 이름으로 접근할 수 있게 하는 것이다. 물론 객체가 해제될 때 자동으로 Label을 제거해주는 기능이 있다면 더 괜찮아 보인다.
객체나 변하지 않는 구조체들 외에도 유용하게 사용할 수 있는데 악성코드 PE 파일 자체를 패킹하여 메모리 내에 저장되어 있을 때 덤프를 뜨면서 그 구조를 파악하는 용도로도 쉽게 사용할 수 있다. 물론 OllyDbg의 경우 PE 관련 구조체는 이미 존재한다.
첫 번째 기능은 어느 정도 분석해 가면서 빨리 패킹을 풀어 나갈때, 그리고 두 번째 기능은 C++는 그렇다고 치더라도 델파이라는 언어를 분석해 나갈 때 유용하게 사용할 수 있을 것으로 보인다. 물론 선행 작업은 기본적인 DB를 만들어 놓는 것이다. 아래는 만들고 있는 DB이며 꾸준히 업데이트 시켜 나갈 생각이다.
[ https://github.com/montouesto/Structure-DB ]
DB는 없지만 필요한 것을 적용하는 방식에 대해서 설명하기로 한다. 명령어, 헤더 파일, JSON 파일 이렇게 3가지 종류가 있는데 JSON 방식은 제외하고 설명한다. JSON은 이곳을 참고하자. [ https://gist.github.com/mrexodia/e949ab26d5986a5fc1fa4944ac68147a ]
3.1 명령어를 이용하는 방식
명령어로 구조체나 Union을 선언해서 사용하는 경우는 휘발성이므로 스크립트를 따로 만들지 않는 이상 직접 파일을 만들어 두는 것이 좋을 것 같다.
먼저 다음 명령으로 "struct_1"이라는 구조체를 생성한다.
: AddStruct struct_1
이후 "int" 타입의 멤버 "mem_1", "mem_2"을 추가한다.
: AddMember struct_one, int, member_1
: AddMember struct_one, int, member_2
참고로 타입은 다음 명령어를 통해 로그 창에서 확인 가능하다.
: EnumType
이제 화면의 아랫 부분을 보면 "구조체" 탭을 볼 수 있다. 이곳을 선택하면 빈 화면을 볼 수 있다. 마우스 오른쪽 버튼을 클릭한 후 "방문 유형"을 선택한다. 팝업이 뜨면 방금 생성했던 구조체 struct_1을 입력한다. 이후 "방문할 주소"를 입력하라는 팝업이 뜬다. 그냥 취소할 경우에는 구조체의 형태를 확인할 수 있으며 특정 주소를 입력할 경우 그 주소를 기반으로 구조체 형태로 보여준다.
예를들어 보겠다. 현재 나에게는 다음과 같은 정보가 있다.
--------------------------------------------------------------------------
0x002 BYTE BeingDebugged;
0x008 void* ImageBaseAddress;
0x00C _PEB_LDR_DATA* Ldr;
0x018 void* ProcessHeap
0x064 DWORD NumberOfProcessors;
0x068 DWORD NtGlobalFlag;
--------------------------------------------------------------------------
또한 프로그램을 디버거로 오픈한 후 EP에서 EBX 레지스터를 확인해 보면 PEB 구조체인 것을 알 수 있다. 지금까지 배운 것들을 이용해 PEB 구조체를 생성해 보자.
앞에서 설명을 덜했는데 Addmember의 4번째 인자와 5번째 인자는 옵션이다. 4번째 인자는 배열일 경우 배열의 크기인데 배열의 크기가 2개이면 2를 넣고 하면 되지만 현재는 5번째 인자를 사용하기 위해 어쩔 수 없이 4번째 인자도 채워야 하는 상황이기에 이 값을 0으로 채운다. 5번째 인자는 시작 주소로부터의 오프셋이다.
나의 경우 PEB의 모든 멤버를 파악하지 못했기 때문에 파악한 멤버만 적기 위해 이 값을 활용하기로 한다.
--------------------------------------------------------------------------
: AddStruct myPEB
: AddMember myPEB, byte, BeingDebugged, 0, 2
: AddMember myPEB, void*, ImageBaseAddress, 0, 8
: AddMember myPEB, int, Ldr, 0, c
: AddMember myPEB, void*, ProcessHeap, 0, 18
: AddMember myPEB, int, NumberOfProcessors, 0, 64
: AddMember myPEB, int, NtGlobalFlag, 0, 68
--------------------------------------------------------------------------
이제 적용시켜 보면 직접 명시한 부분들이 깔끔하게 잘 나오는 것을 확인할 수 있다. 물론 앞에서도 말했듯이 휘발성이기 때문에 다음의 방식을 사용하는 것을 추천한다.
3.2 헤더 파일을 이용한 방식
앞에서 했던 PEB 구조체를 헤더 파일로 만들어 보자. 크기가 워낙 크므로 멤버는 4개만 추가하기로 하며 빈 영역은 따로 이름을 붙여 주자.
--------------------------------------------------------------------------
struct myPEB {
int16 null1;
byte BeingDebugged;
int8 null2;
int null3;
void* ImageBaseAddress;
int Ldr;
int null4;
int null5;
void* ProcessHeap;
};
--------------------------------------------------------------------------
참고로 주석 이런거 다 없애고 위처럼 깔끔하게 (특히 타입 자료형에 주의해서) 만들어야 한다. 이후 "구조체" 창에서 마우스 오른쪽 버튼을 클릭한 후 "헤더 구문분석"을 선택하고 만든 헤더 파일을 선택한다. 이후 앞에서와 같이 방문 유형을 선택해서 적용시키면 된다. 타입 자료형에 주의하지 않거나 주석 같은 문자가 들어가면 제대로 파싱하지 못하기 때문에 주의해서 만들어야 한다. 개인적으로는 그냥 오리지널 헤더 파일을 넣으면 자동으로 구조체 부분만 파싱해서 인식했으면 좋겠다는 생각이 든다.
3.3 단점
예를들어 TEB를 다음과 같이 선언했다고 하자.
--------------------------------------------------------------------------
struct TEB {
...
PEB* PEB;
...
};
--------------------------------------------------------------------------
이 경우 가장 좋은 방식은 PEB 구조체도 바로 보여주는 것이다. 하지만 아직까지는 이것을 보여주는 기능이 없다. 그렇기 때문에 다음과 같이 VisitType 명령어로 직접 하나씩 조사해야 한다.
--------------------------------------------------------------------------
: VisitType TEB, 003f8000
: VisitType PEB, 003f5000
: VisitType PEB_LDR_DATA, 77b17be0
: VisitType Init_Module, 027c3900
: VisitType Load_Module, 027c39f8
: VisitType Memory_Module, 027c3a00
--------------------------------------------------------------------------
만약 "PEB* PEB;" 대신 "PEB PEB;"와 같은 방식으로 선언한다면 멤버로서 구조체 자체가 들어있는 것으로 인식하기 때문에 맞는 결과를 볼 수 없다. 이렇게 포인터가 붙은 경우에는 값에 따라서 제대로 된 결과를 한 눈에 볼 수 있는 기능이 빨리 추가되었으면 좋겠다.
4. 플러그인
플러그인 쪽 내용은 사실 잘 아는 사람들이 많아서 여기에서 도움이 될만한 내용이 많이 있을것 같지는 않지만 분석에 유용했던 것들 위주로 간단하게 정리하기로 하겠다.
4.1 Scylla
디폴트로 존재하는 플러그인으로서 덤프를 뜨는데 사용할 수 있다. 참고로 덤프 뿐만 아니라 Import Table도 복구해줄 수 있다. 사용법은 다음 링크를 참고하자. [ http://sanseolab.tistory.com/73 ]
4.2 SwissArmyKnife
많지는 않지만 뭔가 다양한 기능들이 있다. 유용하게 사용했던 기능은 map 파일을 임포트하는 기능이었다. 버그도 있어서 다음 링크에 [ http://sanseolab.tistory.com/53 ] 따로 정리하기도 했다. map 파일 외에도 sig 파일도 있고 하지만 그다지 유용해 보이지는 않는다. 그리고 Crypt 관련해서 AESFinder 등도 보이지만 이것도 그다지 유용해 보이지는 않는다.
4.3 x64dbgpy
x64dbg에서 파이썬 스크립트를 사용할 수 있게 해주는 플러그인다. 단점이라면 Immunity Debugger처럼 명령줄 라인으로 명령어를 받지는 못하는 것으로 보이며 단지 파이썬 스크립트만 실행 가능한 것으로 보인다. 또 다른 단점은 32비트 버전인 x32dbg에서 사용하기 위해서는 32비트 파이썬을 설치해야 하며 64비트 버전인 x64dbg에서는 64비트의 파이썬을 설치해야 하는 것으로 보인다.
4.4 OllyDumpEx
OllyDbg만의 플러그인으로 생각했었는데 x64dbg도 지원하며 잘 되서 좋다.
4.5 xAnalyzer
[ https://github.com/ThunderCls/xAnalyzer ]
OllyDbg처럼 API 호출 이전에 파라미터를 달아준다. 버전이 잘 안맞아서인지 사용법을 몰라서인지는 모르겠지만 개인적으로 통하는 방식은 마우스 오른쪽 클릭 후 "xAnalyzer"를 선택하고 "Analyze Selection", "Analyze Function", "Analyze Executable" 중 하나를 선택하는 것이었다. "Analyze Executable"을 선택하면 조금 시간이 걸리지만 전체를 파악해주어서 좋지만 단점이 있다. 파라미터가 하드코딩되어 있는 값인 경우에는 잘 통하지만 레지스터인 경우에는 현재 레지스터를 기반으로 파라미터를 분석해 준다. 아마 이것이 가장 큰 단점이 아닌가 싶다. 또 "CALL ESI" 같은 형태의 호출은 바로 파악하지 못한다. (이건 실시간으로 분석해주는 기능이 없기 때문으로 보인다) 그래도 가끔은 파라미터의 Constant까지 파악해서 보여주는 경우도 있으므로 나쁘지 않다.
4.6 ScyllaHide
[ https://github.com/x64dbg/ScyllaHide ]
Debugger Hiding, Timing Hooks, DRx Hiding 등의 여러 기능을 지원하는 것으로 보인다. 아직 제대로 사용해보지는 않았지만 정말 많은 기능을 지원한다. 후킹을 이용한 방식이라서 설정하기도 까다로워 보이며 업데이트가 끊긴 것으로 보인다. 사실 분석하는 입장에서 방해만 되는 것 같아서 사용할 생각은 없다.
'악성코드 분석' 카테고리의 다른 글
악성코드 지속 메커니즘 (0) | 2018.12.16 |
---|---|
TotalCommand 자동화 (0) | 2018.12.16 |
디버거로 덤프 뜨기 (1) | 2018.12.15 |
[Tool] ejExtractor (0) | 2018.12.10 |
윈도우의 서비스 (0) | 2018.11.03 |