2018. 7. 19. 23:36 악성코드 분석

GCC 사용법

1. 개요

2. 실행 파일

.... 2.1 간략한 부분

.... 2.2 실제 예제

3. 라이브러리

.... 3.1 정적 라이브러리

.... 3.2 동적 라이브러리





1. 개요

  gcc의 옵션은 매우 다양하지만 여기서는 악성코드 분석에 도움이 될 정도의 내용만 적기로 한다. 즉 자세한 내용은 생략하며 필요한 부분의 경우에는 실제 예제와 함께 설명하도록 한다.


  전체적인 구성은 옵션을 따로 정리한다던가 하지 않고 간단한 것부터 시작해서 하나 하나 추가해 나갈 것이다. 옵션만 따로 정리하는 방식 자체도 중요하겠지만 개발 위주가 아니므로 간략한 개발 및 필요한 부분에 대한 빠른 복붙을 목적으로 하겠다.


  하지만 언제 필요할지 모르므로 간략한 배경지식은 언급하기로 한다. GCC의 경우 간단한 컴파일러로 생각할 수 있지만 사실 순서대로 다음의 명령을 호출하는 역할을 담당하는 프로그램이라고 생각하면 된다. 가장 먼저 전처리기인 cpp0가 사용되며 이를 통해 전처리 단계가 진행된다. 이후 실제 컴파일러인 cc1이 컴파일 과정을 담당한다. 그리고 어셈블러인 as가 어셈블 단계를 진행하고 이에 따라 목적 파일이 생성된다. "gcc -c" 옵션을 사용하는 경우 어셈블 단계까지만 진행되고 이후 .o 파일이 생성된다. 마지막으로 링크 단계인데 이것은 링커인 ld가 맡는다.





2. 실행 파일

2.1 간략한 부분


$ gcc hello.c

  c 소스 코드인 hello.c를 컴파일하여 실행 파일인 "a.out"이 생성된다. 생성되는 파일 이름을 지정해주지 않았기 때문에 디폴트로 "a.out"이라는 실행 파일이 생성되는 것이다.


$ gcc -o hello hello.c

  구체적으로 생성되는 파일명 즉 "hello"를 지정해 준다.


$ gcc -c main.c func1.c func2.c

$ gcc -o hello main.o func1.o func2.o

  이것은 위에서 설명했듯이 -c 옵션을 이용해 목적 파일인 .o 파일들을 생성한다. 이후 목적 파일들을 모두 링크하는 방식이다.


  참고로 헤더 파일을 인클루드할 때 다음과 같이 두 가지의 방식이 사용된다.

#include "sum.h"

#include <stdio.h>

  여기서 ""는 현재 폴더에서, <>는 표준 include 디렉터리에서 헤더 파일을 찾으라는 것을 의미한다. -l 옵션을 이용하면 구체적으로 찾을 위치를 명시할 수 있다.

$ gcc -I/home/sanser/include -o hello hello.c



2.2 실제 예제

2.2.1 mirai의 loader

$ gcc -static -O3 -lpthread -pthread src/*.c -o loader


  미라이의 loader는 컴파일 시 위와 같은 옵션이 사용된다. "-static" 옵션은 정적으로 컴파일하라는 옵션이다. 이 옵션이 없으면 디폴트로 동적으로 컴파일 된다. -O 옵션의 경우 0부터 4까지 존재하며 숫자가 커질수록 더 강한 최적화가 진행된다. 


  마지막으로 "-lpthread", "-pthread" 옵션이 있다. pthread 관련 헤더를 소스 코드 상에서 include하였음에도 불구하고 이러한 옵션이 사용되는 이유는 컴파일 시에 해당 라이브러리가 사용되었다는 것을 알 수 있지만 링커에게도 pthread 라이브러리가 사용되었다는 사실을 알려야 하기 때문이다. 문제는 l 유무의 차이이다. 사실 위에서 -l이 헤더 파일의 위치를 지정할 때 사용된 적이 있었다. 하지만 여기서는 조금 다른 의미인 것 같다. 그냥 -pthread만 사용할 시에는 컴파일러와 링커에게 모두 알리는 것이며, -lpthread를 사용하면 링커에게만 알리는 것이라고 한다. 사실 정확히 구체적으로 둘 중 무엇을 언제 사용해야 하는지나 장단점, 그리고 여기에서 사용되었듯이 두 개를 모두 사용하는 이유 등은 파악하지 못했다. 참고로 -include 옵션도 헤더 파일 경로를 지정하는데 사용된다고 한다.



2.2.2 mirai의 bot

  자동화를 위해 함수로 구현되어 있다. 그래서 $1, $2, $3을 인자로 받는다. 여기서는 디버그 옵션 말고 릴리즈 옵션 기준으로 설명하겠다.


$ gcc -std=c99 $3 bot/*.c -O3 -fomit-frame-pointer -fdata-sections -ffunction-sections -Wl,--gc-sections -o release/"$2" -DMIRAI_BOT_ARCH=\""$1"\"


  $3은 소스 코드를 봤을 때 "-DMIRAI_TELNET -DKILLER_REBIND_SSH -static" 또는 "-DMIRAI_SSH -DKILLER_REBIND_SSH -static"를 의미한다. 옵션은 "-D"이며 뒤에 붙은 문자열은 #define 값이다. 즉 소스 코드에서 "#ifdef MIRAI_SSH  ....  #else  ....  #endif" 같은 #define 구문을 사용되었을 때 gcc의 경우 커맨드 라인으로 #define을 지정할 수 있는 것이다.


  "-static"이나 "-O3", "-o release/"$2"" 부분은 위에서 언급하였다. 함수로 구현되어 있기 때문에 $2에는 컴파일 결과 바이너리의 이름을 인자로 넣는다. "-std=c99"는 C언어의 C99 표준을 의미한다. "-fomit-frame-pointer"는 프레임 포인터가 필요하지 않은 함수에서까지 프레임 포인터 레지스터를 사용하지 않겠다는 의미이다. 일반적으로 우리가 보는 함수들은 스택 프레임을 설정하는데 예를들어 "push ebp", "mov ebp, esp"를 이용해 시작하는 것이 그것이다. 이것을 사용할 경우 디버깅이 쉽거나 하지만 작은 함수에서는 스택 프레임이 꼭 필요하지 않으며 이러한 경우처럼 항상 스택 프레임을 만들지 않겠다는 의미이다.


  "-fdata-sections" 옵션과 "-ffunction-sections" 옵션은 컴파일러에게 함수나(코드) 데이터를 해당 섹션에 위치시키라는 의미이다. 이 옵션을 사용하지 않는 경우에는 임의의 섹션에 코드나 데이터가 들어갈 수 있다. 하지만 이 옵션을 이용해 해당 섹션에 강제로 위치시킬 수 있다. 이것은 다음에 나오는 링커 옵션과 연관시켜 설명하겠다.


  "-Wl" 옵션은 이후 링커에게 옵션을 주겠다는 의미이다. 즉 다음에 나오는 "--gc-sections" 옵션이 링커가 받는 옵션이다. 이 옵션은 사용되지 않는 섹션을 만들지 마라는 의미이다. 앞에서 나온 "-fdata-sections" 옵션과 "-ffunction-sections" 옵션으로 코드와 데이터를 임의의 섹션에 위치할 가능성을 없애고 이 옵션을 사용함으로써 안전하게 사용되지 않는 섹션을 제거하는 것이다. 앞에서 정적으로 링크함에 따라 크기가 커지는데 이 옵션을 통해 최대한 크기를 줄이는 목적을 가지고 있다.



2.2.3 strip

  gcc 관련 내용이지만 악성코드 분석에는 strip 또한 중요하므로 여기에서 언급하기로 한다. mirai의 bot은 컴파일 이후 strip 명령 또한 사용한다. 약간 수정하면 다음과 같다.


$ strip release/"$2" -S --strip-unneeded --remove-section=.note.gnu.gold-version --remove-section=.comment --remove-section=.note --remove-section=.note.gnu.build-id --remove-section=.note.ABI-tag --remove-section=.jcr --remove-section=.got.plt --remove-section=.eh_frame --remove-section=.eh_frame_ptr --remove-section=.eh_frame_hdr


  반복되는 내용이 많으므로 실제로 볼 것은 몇 개 없다. 먼저 "-S" 옵션은 디버그 심볼을 삭제하는 옵션이다. 참고로 모든 심볼을 삭제하는 옵션은 "-s"이며 링커 옵션으로 "-s" 옵션이 존재하는데 이것도 실행 파일에서 심볼 테이블을 제거하라는 의미이다. "--remove-unneeded"는 이름과 같이 사용되지 않는 심볼을 삭제하라는 명령이다. "--remove-section="은 섹션 자체를 삭제하는 옵션이다. 정적으로 컴파일한 경우 용량이 상당히 커지기 때문에 앞의 gcc 옵션부터 시작해서 크기 관련한 최적화에 상당히 공을 들인 것으로 보인다.





3. 라이브러리

  윈도우와 마찬가지로 리눅스도 정적 라이브러리와 동적 (공유) 라이브러리가 존재한다. 정적 라이브러리의 경우 윈도우는 ".lib" 확장자를 갖으며 리눅스에서는 통상 이름 앞에는 "lib"을, 뒤에는 ".a"를 붙인다. 동적 라이브러리의 경우 윈도우는 ".dll" 확장자를 갖으며 리눅스에서는 이름 앞에는 "lib"을, 뒤에는 ".so"를 붙인다.



3.1 정적 라이브러리

  앞에서도 언급하였듯이 정적 라이브러리는 libxxx.a라는 형태를 가진다. 일반적으로 다음과 같이 사용한다.


$ gcc -c test1.c test2.c

// test1.o, test2.o 생성

$ ar rc libtest.a test1.o test2.o

// test1.o, test2.o 목적 파일을 가지고 libtest.a라는 정적 라이브러리를 만든다. 정적 라이브러리를 만들 때는 gcc가 아닌 ar이라는 프로그램을 사용한다. r은 이 라이브러리 아카이브에 새로운 오브젝트를 추가할 것이라는 옵션이고 c는 아카이브가 존재하지 않을 경우 생성하라는 옵션이다.

$ gcc -o hello hello.c -L./ -llibtest.a

// 만든 라이브러리를 링크하려면 해당 라이브러리의 위치를 가르쳐줘야 하는데, -L로 디렉토리의 위치를, -l로 라이브러리의 이름을 명시해야 한다.

$ ar t libtest.a

// t는 해당 라이브러리가 어떤 오브젝트 파일을 포함하고 있는지를 보여준다.



3.2 동적 라이브러리

  앞에서도 언급하였듯이 동적 라이브러리는 libxxx.so라는 형태를 가진다. 일반적으로 다음과 같이 사용한다.


$ gcc -fPIC -c test1.c test2.c

// test.o를 생성하는데 위치 독립적인 코드를 만들기 위해 -fPIC 옵션을 준다.

$ gcc -shared -o libtest.so test1.o test2.o

// test1.o, test2.o를 이용해 litest.so 파일을 생성하며, 동적 라이브러리를 만들 때는 gcc에 옵션으로 -shared를 준다. 

$ gcc -o hello hello.c -L./ -ltest

// 동적 라이브러리의 경우 -l 뒤에 "test" 같이 이름만 붙이나 보다.


  참고로 라이브러리를 사용할 때 에러가 뜰 수 있는데 이 때는 etc/ld.so.conf에 경로를 설정한 후 ldconfig로 캐시를 갱신하면 된다. 이 설정 파일은 "include ld.so.conf.d/*.conf"라고 되어 있는데 저 디렉토리에 test.conf라는 파일을 만들고 그냥 내가 만든 라이브러리의 경로만 적어주면 된다. 또는 LD_LIBRARY_PATH 환경변수를 이용하여 설정할 수 있다. export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/me/ 이런 식으로 해주면 된다. 아니면 심볼릭링크를 걸어서 해도 된다.





* 라이브러리 관련 환경 변수

  리눅스에서 공유 라이브러리는 /usr/lib 또는 /usr/lib64에 저장된다. 이곳에 libc.so.6 같은 파일들이 저장되어 있다. 참고로 라이브러리는 관례 상 접두어 lib와 이름 그리고 .so, 마지막으로 버전 번호로 이루어진다. 공유 라이브러리를 호출하기 위해서는 PATH를 지정해야 한다. 로더는 LD_LIBRARY_PATH 환경 변수 또는 ld.so.conf에 명시된 디렉토리에서 라이브러리를 찾아서 로드한다. ldconfig 명령어는 /etc/ld.so.conf 파일에 설정된 동적 라이브러리 정보를 /etc/ld.so.cache 파일로 만들어 주는 일을 하는데 이것은 로더가 ld.so.cache 정보를 가지고 더 빠르게 라이브러리를 찾을 수 있게 한다.


Posted by SanseoLab



1. 개요

2. 예제 소스 코드

3. N-Code ( Console Program )

4. P-Code

5. N-Code ( GUI Program )

6. 기타 링크





1. 개요

  VB 리버싱 관련 내용을 찾아보면 크랙미 분석이 대부분이다. 또한 이것도 거의 전부 Event 관련된 내용이라서 사용될만한 API에 BP를 걸고 진행한 후 문자열 비교 같은 내용만 다룬다. 그래서 여기에서는 전체적인 구조를 공부하고 정리하려고 한다.


  먼저 VB의 경우 컴파일 방식에 따라 P-Code, N-Code로 나뉜다. P-Code 방식은 Pseudo Code로서 바이트코드로 컴파일되어 인터프리터에 의해서 해석되면서 실행되는 방식이다. Java나 C#과 비슷한 형태이다. N-Code는 Native Code로서 일반적인 실행 파일처럼 어셈블리를 통해 해석할 수 있는 방식이다. 물론 VB의 경우 MSVBVM60.dll 같은 VB 엔진에 의해서 실행되기 때문에 바로 스텁을 지나 Main()으로 가는 일반적인 실행 파일과는 차이점이 존재한다.





2. 예제 소스 코드

  N-Code 방식을 공부하려고 했는데 역시 리버싱은 직접 만들어가면서 분석해 보는 것이 가장 좋은 것 같다. 그래서 다음 링크의 소스 코드를 이용했다. 직접 보면 알겠지만 이것은 간단한 형태의 키로거이다. 물론 실제 악성코드는 아니고 메시지 박스를 보여주며 간단한 기능만 포함된 샘플 예제 형태이다. "하지만 악의적인 용도로 사용할 경우 불법이므로 테스트 용도로만 사용해야 한다." 이 샘플은 적당한 크기의 코드이기도 하고 API를 직접 호출하는 부분도 있어서 컴파일해 가면서 분석하기로 했다.

[ http://freesourcecode.net/vbprojects/28938/SpyEx-(Keylogger)-in-Visual-Basic#.WxKaL0iFM2x ]


  해당 소스 코드 또한 GUI 형태이기 때문에 직접 Console 형태로 변환하였다. 일반적인 프로젝트를 생성하면 (표준 EXE) Form이 한 개 생성된다. 이것을 삭제하고 대신 Module을 하나 추가한다. 그리고 해당 링크의 Module1.bas의 소스 코드를 복사해서 붙여넣기 한다. 이후 첫 줄의 Attribute 라인을 삭제하고 다음을 추가한다. 원래 메시지 박스에서 버튼을 클릭했을 때 구현되는 방식이지만 Console 프로그램 형태로 만들기 위해 Form을 삭제하고 직접 호출하는 부분을 추가한 것이다. 수정 후 (일반적인 Visual C++과는 다르게) "파일" 탭에서 "Project1.exe 만들기" 버튼을 클릭해서 exe를 생성한다.


Sub Main()

    Call startup

    Call KeyboardHook

    Call Unhook

End Sub





3. N-Code ( Console Program )

  디버깅 이전에 정적 분석부터 진행해 보자. Import Table을 보면 MSVBVM60.DLL의 여러 함수들이 정의되어 있는 것을 볼 수 있다. 문제는 소스 코드에서 사용된 API 함수들은 보이지 않는다는 점이다. 대신 String을 확인해 보면 해당 API 함수 이름을 확인할 수 있다. 그 이유는 다음에서 살펴볼 API 함수 호출 방식에 대해서 정리하면서 확인해볼 수 있다.


  Ollydbg로 분석을 시작해 보자. 전형적인 VB의 EP를 볼 수 있다.



  특정 구조체를 push한 후 ThunRTMain()을 호출한다. 이 함수는 스텁 코드와 비슷한 역할을 하는데 여러 정보가 담긴 구조체를 기반으로 초기화를 진행한 후 실제 실행이 진행된다. 문제는 그냥 F8을 눌러버리면 끝까지 진행되기 때문에 실제 EP를 찾아야 한다. EP는 해당 구조체 0x00401354에서 오프셋 +2c에 위치한다. 즉 0x00401380에 위치한 0x00401c80이다. 그 외에도 VB5! 등의 시그니처로 시작하는 것을 확인할 수 있다.


  EP에 BP를 걸고 진행해도 되지만 어차피 직접 진행하다 보면 다음과 같이 EP로 분기하는 루틴을 만날 수 있다.



  어쨌든 EP로 가면 다음과 같은 프로시저를 볼 수 있다. 이것이 Main 함수이며 소스 코드를 보면 알겠지만 Main 함수는 3개의 함수를 호출한다. 각각의 함수로 진입해서 확인해보면 알겠지만 개발자가 정의한 프로시저의 경우 이러한 구조를 띄는 것 같다. 물론 Main() 함수는 간단하므로 프로시저도 훨씬 간단하다.



  그 외의 추가 사항을 보자면 프로시저 호출 이후에는 항상 MSVBVM60.__vbaFreeVar()을 호출하는 것으로 보인다. 그리고 인자는 (객체 지향 언어이기 때문에 클래스로 추정된다) 기본적으로 1개이며 인자가 1개 늘어날 때 마다 1개씩 더 추가되는 것으로 보인다. 더 자세한 분석을 위해 각각의 함수로 들어가 보자.


  사실 startup() 함수가 조금 더 복잡한 형태이고, KeyboardHook() 함수의 경우도 자체는 간단하지만 인자 설정과 관련된 내용이 있으므로 가장 간단한 Unhook() 함수부터 살펴보겠다.



  앞의 Main() 함수의 프로시저와 비슷한 형태이다. 확인해 볼 부분은 0x004027F7의 호출문이다. 소스 코드를 보면 알겠지만 이 함수에서는 VB의 함수가 아니라 API를 사용하였다. VB에서 API가 사용될 경우 어셈블리에서 어떻게 보여지는지를 확인하기 위해 이 부분을 살펴보자. 여기에서 의미 있는 함수 호출은 이 부분밖에 없으며 당연히 이 함수 호출이 API UnhookWindowsHookEx()와 관련된 부분을 것이다. 참고로 자세하게 다루지 않았지만 함수 0x00401750 호출 이전에 이 API 함수에 대한 인자가 설정될 것이고 그것을 확인할 수 있다. 여기서는 API 함수 호출 부분을 중점적으로 보겠다.



  0x00401750 함수가 위치한 부분을 위아래로 확인해 보면 이것과 유사한 함수들을 많이 확인할 수 있다. 이 부분만 확실히 보면 이제 직접 API 함수 호출 부분을 찾아나갈 수 있고 추후에 자동화할 수도 있을 것이다.


  MSVBVM60.DllFunctionCall() 함수를 호출하기 전에 특정 주소를 push한다. 이 주소를 확인해 보면 구조체인 것으로 추정되며 두 개의 주소를 멤버로 갖는다. 각각의 주소는 문자열을 가리키는데 첫 번째는 DLL 이름을 그리고 두 번째는 API 함수 이름을 가리킨다. 즉 API 호출 부분이 존재할 경우 이러한 형태의 프로시저가 삽입되며 이것은 호출할 API 함수의 문자열을 가리키는 구조체를 인자로 넣고 DllFunctionCall()을 호출한다. 이 함수는 해당 API 함수의 주소를 EAX 레지스터에 반환하며 마지막으로 "JMP EAX"를 호출하여 API 호출이 완료된다. 참고로 인자의 경우 앞에서 언급하였듯이 0x00401750 호출 이전에 이미 설정되어 있었다.


  지금까지는 API 호출 부분만 살펴보았지만 startup() 프로시저의 소스 코드와 어셈블리를 비교해 보면서 공부할 수 있다. API 호출 외에도 VB 문법에 의거한 변수 선언 및 정의나 조건문 같은 부분을 확인할 수 있다. 변수에 문자열을 선언할 때 사용되는 여러 함수들이 보인다. __vbaStrToAnsi(), __vbaStrToUnicode() 부터 __vbaFreeVar(), __vbaFreeStrList() 등이 그것이다. 이러한 부분은 특별한 구조가 있는 것은 아니고 직접 확인해 가면서 공부해야 할 것 같다.





4. P-Code

  앞의 소스 코드를 P-Code로 컴파일한 후 OllyDbg로 확인해 보았다. 크기의 경우 많이 줄어들었는데 항상 그럴것 같지는 않다. 개인적으로는 간단한 샘플이라서 N-Code에서 디폴트로 포함되는 부분 때문에 그렇지 크기가 커진다면 N-Code에서 추가되는 부분보다는 P-Code에서 추가되는 바이트코드 부분이 훨씬 클 것으로 생각한다.


  EP를 호출하는 부분 까지는 같지만 Main()이 다음과 같은 형태를 가지며 이후 분석 진행도 못하고 예외가 발생하므로 그냥 VB Decompiler를 이용하기로 했다.



  VB Decompiler Pro 버전은 P-Code를 읽어서 디컴파일을 해주는 기능이 있다. (무료 버전은 아예 인식 조차 지원하지 않는다) 테스트 결과 간단한 샘플이기는 하지만 상당히 괜찮은 결과가 나왔다. 혹시나 해서 N-Code 디컴파일러도 테스트한 결과 (VB Decompiler 무료 버전은 디스어셈블까지만 지원한다) 이것도 P-Code 만큼은 아니지만 그래도 볼만한 결과가 나온 것을 확인하였다.



  물론 과거 약간의 난독화가 적용된 N-Code를 테스트해 봤을 때는 반도 인식하지 못했었기 때문에 간단한 형태인 경우에만 괜찮은 결과를 내는 것 같다. 그리고 P-Code의 경우 여러 바이너리를 분석해 본 경험이 없기 때문에 판단하기가 힘들다.


  결론적으로 N-Code의 경우 간단한 것은 VB Decompiler Pro의 디컴파일 기능을 사용하면 되고, 난독화 및 복잡한 샘플은 앞에서 정리한 것을 기반으로 직접 디버깅을 수행해야 할 것이다. P-Code는 디버깅이 불가능하기 때문에 VB Decompiler Pro를 사용할 수 밖에 없으며 난독화는 따로 방법을 알아내야 할 것 같다.





5. N-Code ( GUI Program )

  마지막으로 크랙미 문제에서 많이 보이는 GUI 프로그램을 분석해 봤다. 샘플은 Reversing.kr에서 다운로드 가능한 Music Player이다.


  GUI 형태라서 그런지 구조체 오프셋 +2c에서 EP의 주소를 확인할 수 없었다. 프로시저의 형태라던가 API 호출 부분은 모두 같지만 어디가 초기화 루틴이며 무엇에대해 어떤 핸들러 프로시저가 호출되는지 등의 연관성을 찾지는 못했다. 아마 구조체 쪽을 더 자세히 살펴봐야 할 것으로 보인다.


  하지만 이러한 분석 대신 VB Decompiler Pro만 있어도 된다. 이것은 GUI 형태를 분석할 때 더 도움이 된다.



  스크린샷과 같이 Form을 클릭하면 GUI가 나오며 여기서 "Open" 버튼을 클릭했을 때 호출되는 핸들러 함수를 찾아보았다. 이것은 "CMD_OPEN_CLICK"이었으며 오른쪽 화면에서 이것이 주소 0x004036A0에 위치한 핸들러 함수라는 것을 확인할 수 있었다.


  이렇게 전체 함수들을 주소 및 이름 별로 정리해 주고 GUI에 맞는 핸들러까지 확인할 수 있으므로 GUI 형태를 분석하는데 많은 도움이 된다. 개인적으로 전체 프로시저들에 BP를 걸고 실행했을 때 가장 먼저 호출되던 프로시저가 "Form_Activate"였다는 것을 파악할 수 있었고, 이벤트를 받는 동안 즉 대기 기간 동안 루프문을 돌면서 호출되던 프로시저가 "TMR_Timer"였다는 것도 파악할 수 있었다.





6. RunPE

  VB6는 실제 악성코드를 제작하기 위한 목적보다는 Cryptor 형태로서 자주 사용된다. 즉 실제 악성코드는 보통 exe나 dll 형태로 제작하지만 외형을 VB6로 만드는 경우가 그것이다. VB6 코드는 대부분 쓰레기 코드로 이루어져 있으며 이후 디코딩 루틴을 거쳐 실제 PE를 복구하고 정상 프로세스에 인젝션하거나 자체 메모리를 Unmap한 후 새로 쓰는 방식이다. 이러한 방식을 RunPE라고 부르며 인터넷에도 많은 자료가 나와있다. 가장 대표적인 방식은 쓰레기 코드 이후 디코딩 루틴을 CallWindowProcW() 함수의 인자로 등록하는 방식이다.





7. 기타 링크
  막상 블로그에 올리고 난 후 이것 저것 찾아보다가 좋은 링크들을 많이 발견하였다. 오래된 컴파일러다 보니 이미 많은 분들에 의해 분석되고 잘 정리된 곳도 많아 보인다. EP에서 push 했던 구조체인 EXEPROJECTINFO를 포함해서 중요한 자료들이 자세히 정리되어 있다.




Posted by SanseoLab



0. 개요

1. VBScript

2. Powershell

3. Batch





0. 개요

  VBScript, Powershell, Batch, JScript 등 많은 스크립트들이 악성코드로서 사용된다. 이것들은 기본적으로 스크립트 형태이면서도 윈도우 환경에서 실행 가능하다는 속성을 갖는다. 하지만 이런 특징 외에도 프로그래밍이 쉬워서인지 AutoIt, AutoHotKey 처럼 스크립트로 개발되어 exe 바이너리로 변환된 형태의 악성코드들도 많이 보이는 편이다.


  여기서는 VBScript, Powershell, Batch 스크립트가 exe 바이너리로 변환된 경우 이 바이너리에서 스크립트를 직접 추출하는 디컴파일 방식을 다루려고 한다. AutoIt, AutoHotKey의 경우 각각 [ http://sanseolab.tistory.com/59 ]와 [ http://sanseolab.tistory.com/61 ]에서 다루었다. vbs의 경우 [ http://sanseolab.tistory.com/60 ]에서 간략하게 다루었는데 VbsEdit이라는 프로그램에서 Vbs to Exe라는 컨버터 프로그램을 내부적으로 사용하였기 때문이다. Vbs to Exe는 VBScript 항목에서 더 자세하게 다루기로 한다.





1. VBScript

  Vbs to Exe는 이름과 같이 vbs를 exe로 변환해주는 프로그램이다. 해당 프로그램은 [ http://www.f2ko.de/en/programs.php ]에서 받을 수 있으며 해당 사이트에는 아래에서 설명할 파워셸 컨버터인 Ps1 to Exe, 배치 파일 컨버터인 Bat to Exe도 다운로드 받을 수 있다. 참고로 이 세가지의 변환 프로그램은 모두 원리가 동일하므로 여기에서 자세히 설명한 후 각 항목에서는 간략하게 언급만 하고 넘어가겠다.


  Vbs to Exe의 버전은 3.0.7을 기준으로 한다. 변환 후 생성된 PE를 보면 별다른 특징은 발견할 수 없다. 암호화된 스크립트는 리소스 섹션의 RCDATA에 들어있는데 살펴보면 항목이 4개 정도 존재하며 그 중 하나는 암호화된 스크립트로 보일만 할 길이를 가지고 있다는 것을 확인할 수 있다. 나머지 3개는 너무 짧기 때문에 쉽게 구별할 수 있다. 간단하게 Resource Hacker를 이용해 이 부분을 추출한다.


  미리 말하자면 이것은 RC4 암호화 방식을 이용해 인코딩된 스크립트이다. 그렇기 때문에 이것을 디코딩하기 위해서는 RC4 키가 있어야 한다. 리버싱을 진행하다 보면 어디서인가 RC4 키를 구해서 이 부분을 복호화하는 루틴을 확인할 수 있다. RC4 키 생성 루틴을 추적해서 분석해 보면 결국 RC4 키는 data 섹션의 특정한 값을 이용해 MD5 루틴을 거쳐서 만들어진 md5 해시 값이다. md5 루틴의 경우 루틴이 간단한 편이기도 하고 많은 상수 값들이 사용되기 때문에 검색을 통해서 쉽게 파악할 수 있었다.


  data 섹션에 위치한 이 값은 다음과 같은 부분이다.

[ FF FF FF FF    7F 3B D5 06    xx xx xx xx    BB 8E 8E 91 ]


  xx 부분을 제외한 부분은 항상 같기 때문에 이 부분을 이용해서 검색할 수 있다. 실제로 md5에서 사용되는 인풋은 "7F 3B D5 06  xx xx xx xx" 부분이다. md5는 최소 512비트 즉 0x40 크기의 인풋을 받는데 나머지는 패딩으로 채워지는 것 같다. 개인적으로 md5 루틴을 분석하다가 패딩을 생각하지 못해서 어려움을 겪었기 때문에 참고 사항으로 적어 보았다.


  어쨌든 인풋 0x8 바이트를 추출하던지 해서 MD5를 구해보면 0x10 바이트의 (128비트) 값을 구할 수 있다. md5를 구하는 것은 도구를 이용해도 되지만 온라인에서도 쉽게 획득할 수 있다. [ https://www.fileformat.info/tool/hash.htm ] 이 링크의 경우 0x8 바이트를 파일 형태로 저장한 후 업로드해도 되며 바이너리를 그대로 복사해서 입력해도 md5를 구해주기 때문에 가장 편한 사이트로 보인다. 사실 대부분의 암호화/복호화 사이트들이 입력으로 문자열을 받아서 이렇게 직접 바이너리를 분석하는 입장에서는 매우 불편할 때가 많다.


  md5 값은 "fe6d1fed11fa60277fb6a2f73efb8be2"와 같이 확인할 수 있는데 중요한 점은 이것을 대문자로 변환해야 한다는 것이다. 대문자 변환 후 다음 사이트에서 RC4 디코딩을 진행하자. [ http://rc4.online-domain-tools.com/ ] 아까 RCDATA에서 추출한 파일을 입력하고 키로 대문자로 변환된 md5 값을 입력한 후 Plaintext를 선택하고 Decrypt 버튼을 누르면 복호화된 VBS 명령어들을 확인할 수 있다.


  참고로 이러한 과정 없이 덤프를 떠서 문자열을 추출하면 확인할 수 있다. 디코딩 루틴이 실제 행위 이전인 초기에 진행되기 때문이다. 단점은 덤프를 뜰 때 전체를 뜨던지 해야 하는데 복호화된 문자열이 data 섹션 같은 곳이 아니라 초기 루틴에서 할당된 메모리에 복호화되기 때문에 이 부분을 찾아야 한다.





2. Powershell

  Ps1 to Exe라는 프로그램이며 위의 링크에서 다운로드 받을 수 있고 디컴파일 방식은 동일하다. 확인한 버전은 3.0.6이었다.





3. Batch

  Bat to Exe Converter의 경우도 위와 동일하다. 버전은 3.0..10이었다.


  하지만 Batch 파일의 경우 여러 컨버터가 존재하기 때문에 찾아본 내용들을 같이 추가하기로 한다. 먼저 [ http://bat2exe.net/ ]에서 다운로드 받을 수 있는 bat2exe가 있다. 이것을 이용해 바이너리로 변환한 후 확인해보면 7zip의 SFX 방식을 사용하는 것을 알 수 있다. 또한 압축을 풀어보면 아예 원본 batch 파일을 구할 수 있다.


  다음으로 링크 [ https://softwarebydefault.com/open-source-projects/battoexe/ ] 또는 [ https://archive.codeplex.com/?p=bat2exe ]에서 구할 수 있는 bat2exe 컨버터도 있다. 이것은 약간 특이하게 C# 바이너리 형태로 변환된다. 하지만 C#의 경우 dnSpy를 통해 쉽게 디컴파일이 가능하며 해당 바이너리의 경우도 스크립트를 문자열로 확인할 수 있다.



Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.5
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함