악성코드 분석/Practical Malware Analysis 실습

[Practical Malware Analysis] Lab 07-03

sec_kero 2023. 5. 21. 20:30

실습 7-3)

※ 이 실습은 컴퓨터에 상당한 해를 입힐 수 있고 설치되면 삭제하기 어렵다. 실행 이전 상태로 돌릴 수 있는 스냅샷이 존재하는 가상머신이 아니라면 실행하지 말자.

 

  책에서 이번 실습은 정적 방법과 동적 방법 모두를 사용하여 분석하되, 너무 세부적인 것 보다는 큰 그림에 초점을 맞추고 분석을 해보라고 한다.

 

● 기초 정적 분석

 

03.exe strings, imports 함수

 

  먼저 03.exe를 살펴보자. strings를 보면 "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE"이라는 아주 위협적인 문구를 볼 수 있고, "kernel32.dll" 이름과 더불어 이와 비슷한 "kerne132.dll" 문자열을 볼 수 있다. 문자열만 놓고 유추하면 "kerne132.dll"이 정상 파일인 "kernel32.dll"처럼 보이게 위장해서 악성행위를 하는 악성파일이 아닐까 의심이 된다.

 

   다음으로는 imports 함수를 보자. 사용된 함수들은 아래와 같다.

함수 분류
기능별 함수 이름 함수 설명
메모리 매핑용 CreateFileMappingA 지정된 파일에 대한 명명되거나 명명되지 않은 파일 매핑 개체를 만들거나 엽니다.
MapViewOfFile 파일 매핑의 보기를 호출 프로세스의 주소 공간에 매핑합니다.
UnmapViewOfFile 호출 프로세스의 주소 공간에서 파일의 매핑된 뷰를 해제합니다.
IsBadReadPtr 호출 프로세스에 지정된 메모리 범위에 대한 읽기 권한이 있는지 확인합니다.
파일 찾기 FindFirstFileA 특정 이름(또는 와일드카드가 사용된 경우 부분 이름)과 일치하는 이름을 가진 파일 또는 하위 디렉토리에 대한 디렉토리를 검색합니다.
FindNextFileA FindFirstFile , FindFirstFileEx 또는 FindFirstFileTransacted 함수 에 대한 이전 호출에서 파일 검색을 계속합니다 .
FindClose FindFirstFile , FindFirstFileEx , FindFirstFileNameW , FindFirstFileNameTransactedW , FindFirstFileTransacted , FindFirstStreamTransactedW 또는 FindFirstStreamW 함수 로 열린 파일 검색 핸들을 닫습니다 .
파일 관리 CreateFileA 파일 또는 I/O 디바이스를 만들거나 엽니다. 
이 함수는 파일 또는 디바이스 및 지정된 플래그 및 특성에 따라 다양한 유형의 I/O에 대한 파일 또는 디바이스에 액세스하는 데 사용할 수 있는 핸들을 반환합니다.
CloseHandle 열려 있는 개체 핸들을 닫습니다.
파일 복사(저장) CopyFileA 기존 파일을 새 파일로 복사합니다.

 

   다른 함수들은 찾아보면서 사용 목적이 이해가 갔지만, 메모리 매핑용 함수들은 설명만 놓고 봤을 때 사용 목적이 이해가 가지 않아서 메모리 매핑에 대해 추가로 더 찾아보았다.

 

  "메모리 맵 파일은 운영체제에서 파일을 다루는 방법 중 하나로, 프로세스의 가상 메모리 주소 공간에 파일을 매핑한 뒤 가상 메모리 주소에 직접 접근하는 것으로 파일 읽기/쓰기를 대신한다." 결론적으로는 파일 내용을 조작하기 위해서 사용한 함수인 것으로 보인다.

+) 출처 : https://ko.wikipedia.org/wiki/%EB%A9%94%EB%AA%A8%EB%A6%AC_%EB%A7%B5_%ED%8C%8C%EC%9D%BC

 

  위의 정보들을 봤을 때 .exe 파일은 kerne132.dll이라는 파일을 만들고 조작해서 악성행위를 하지 않을까 추측된다.

 

+) 이번 .exe 파일의 특이점은 LoadLibrary, GetProcAddress 함수가 사용되지 않았다는 점이다. 외부 DLL의 함수를 사용할 때 보통 두 함수를 사용하는데 이 파일에서 사용되지 않는 점을 보아, 뭔가 다른 방법으로 접근하지 않을까 짐작됩니다.

 

03.dll strings, imports 함수

 

   다음으로는 03.dll을 살펴보았다. strings에서는 "exec", "sleep", "hello"의 문자열과, 통신에 사용될 것으로 보이는 IP주소("127.26.152.13"), 의미를 알 수 없는 문자열인 "SADFHUHF"를 볼 수 있었다.

 

   imports 함수에서는 크게 Sleep 함수, Process 생성 함수, 뮤텍스 관련 함수, 네트워크 통신 관련 함수들(ws2_32.dll)을 볼 수 있었다. 정보들을 보았을 때 이 DLL 파일은 네트워크를 이용한 행위가 메인으로 보인다. 어떤 행위를 하는지는 좀 더 살펴봐야 할 것 같다.

+) 이 DLL 파일은 exports 함수가 없다는 특이한 점이 있다. exports 함수가 없으면 다른 프로그램에서 어떻게 사용할 수 있는지 궁금함이 든다.

 

  정적분석을 어느 정도 마친 것 같아, 동적 분석을 진행했지만 어떠한 유의미한 결과를 얻을 수 없었다. 그래서 상세한 분석이 필요해보여 IDA로 분석을 진행해 보기로 했다.

 

 

● IDA 분석

 

  DLL 파일의 동작이 비교적 간단해서 DLL 파일을 먼저 분석했다.

 

 - Lab07-03.dll

 

  DLL 파일은 alloca_probe 함수를 실행해 공간을 할당받고, 중복 실행 방지를 위해 "SADFHUHF" 라는 이름의 뮤텍스를 생성 후 네트워크 통신을 위해 소켓을 준비한다.

 

 

  소켓의 IP 주소로는 "127.26.152.13"를 설정한 후 connect로 연결을 시도한다. 연결될 경우에는 해당 주소로 "hello"라는 문자열을 보낸다(send). 전송에 실패한 경우에는 shutdown 시켜 소켓을 해제하고, 성공한 경우에는 해당 주소에서 전송 데이터를 받아서(recv) 버퍼에 저장한다. 

 

 

  이후에는 받은 데이터(buf)를 비교해서 Sleep 함수를 수행하거나, CreateProcessA 함수를 수행한다. CreateProcess의 경우 CommandLine 값을 이용해 프로세스를 생성한다. (StartupInfo, ProcessInformation, CommandLine가 변수로 설정되어있긴 하지만, 따로 값을 설정하는 지점이 보이지 않는다.) 위 과정들을 send 함수가 동작하지 않거나 recv 한 내용이 "q"일 때까지 반복한다.

 

+) CommandLine 값을 못 찾았지만 이는 분명히 어딘가에서는 저장되어 있을 것이다. 책에서는 CreateProcessA의 파라미터로 사용하기 이전에 쓰여졌을 것이라 확신하기에 추가적으로 작업을 했다.

 ㄴ 이는 IDA의 자동화된 명명 방식으로 실제 CommandLine이 어디에서 작성됐는지 알기 어려운 경우에 해당한다. 

 ㄴ Dllmain의 변수로 buf는 -1000h에 위치하고, CommandLine은 -0FFBh에 위치한다. 여기서 알 수 있는 점 (1)은 buf와 CommandLine은 연속된 위치에 저장되어 있고, buf로부터 CommandLine까지의 바이트 차이가 0x5이기 때문에, buf에 담길 인자는 0x5만큼의 크기를 가진다는 것이다.

  (2) 코드 중간에 보면 lea로 저장하는데 이는 스택에 데이터가 저장되고, 데이터를 가리키는 단순 포인터가 아니라는 의미라고 한다. 이 경우에는 원격 서버에서 받은 데이터가 exec FullPathOfProgramToRun일 것이라고 한다. 원격 서버에서 명령어 문자열을 수신할 때 FullPathOfProgramToRun을 통해 CreateProcessA를 호출한다. (책에서는 이렇다고 하고 이는 실제로 분석해보면 정확히 이해할 수 있을 것 같다.)

 

  +) 이는 함수와 DLL을 종료시키며, 아직 익스포트 함수를 갖고 있지 않으면서 어떻게 동작하는지 미스터리이기에 나중으로 질문을 미룰 필요가 있다.

 

 

  DLL 파일의 동작을 정리해 보면 뮤텍스를 생성해서 중복 실행 방지를 하고 "127.26.152.13"과 송수신하면서 특정 정보를 받아 sleep 하거나, 프로세스를 생성한다.

 

 

  다음은 exe 파일을 분석해 보자.

 

 - Lab07-03.exe

 

   우선 시작 부분에서 프로그램의 인자(argc)가 2개(추가 인자는 1개)인지 확인하고, 맞을 경우에는 입력한 추가 인자가 "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE"과 일치하는지 확인하는 과정을 거친다. 두 조건 중 하나라도 해당이 되지 않는다면, 프로그램은 실행되지 않는다.

   이 조건을 만족하지 못해서 일반 동적 분석을 할 때 어떠한 결과도 얻지 못한 것으로 보인다. IDA로 분석을 마저 하고 동적 분석도 해보도록 하자.

 

 

  위의 인자 확인 과정을 마친 후에는 System32 폴더에 있는 Kernel32.dll을 읽기 모드로 불러와 메모리에 매핑한 후, 매핑한 값은 [argc]에 저장해 놓는다. 그다음으로는 03.exe와 같은 폴더 내에 있는 03.dll을 읽고 쓸 수 있도록 메모리에 매핑한다. 이 값은 [argv]에 저장한다.

 

 

  이 과정까지 성공적으로 마치게 되면 loc_401538에 위치한 코드부터 실행한다. 그래프 모양을 보면 알 수 있는데 아주 길게 어떠한 작업을 하는 것을 볼 수 있다. 이전에 수행한 함수들과는 달리 인자를 달리하여 sub_401040 함수를 여러 번 시행하는 과정을 볼 수 있다. 

 

Lab07-03.dll(좌 : 작업 전, 우 : 작업 후)

  책에서 큰 그림을 보라고 했듯, 해당 과정은 이번 문제를 해결하기 위해서는 깊게 분석하지 않아도 되지만 궁금해서 분석을 해보았다. 결과만 얘기하자면 해당 과정은 kernel32.dll의 export table 정보들을 가져와 lab07-03.dll에, "kerne132.dll"이라는 정보 및 "Kernel32."이라는 문자열을 추가하여 저장한다. 이 과정은 악성파일이 될 것으로 보이는 "kerne132.dll"을 "Kernel32.dll"로 위장하기 위해서 kernel32.dll의 정보들을 가져온 것으로 보인다.

 

  +) 해당 과정의 조금 더 자세한 설명은 접은 글로 작성하겠다. 

더보기

   앞에서 매핑 함수를 실행해서 불러온 dll 파일들("kernel32.dll", "lab07-03.dll")은 현재 03.exe 프로세스 메모리에 올라와있다. 이렇게 올려놓기만 해서는 해당 파일들의 정보를 활용할 수 없고, 내가 접근하려는 정보가 담긴 주소를 검색해서 이를 활용해야 한다.

 

   이 프로그램에서는 그 과정을 수행하기 위해 sub_401040 함수를 사용했다. sub_401040 함수는 인자로 다음 세 가지 값을 받는다. 1) 내가 접근하고자 하는 항목의 RVA 값이 담긴 주소(arg_0), 2) 파일의 NT_Header 주소(arg_4), 3) 파일의 시작 주소("MZ", arg_8)

 

  어떤 동작이 수행되는지 확인하기 위해 내부 구조를 살펴보았다.

 

< sub_401040(get_fileoffset) 함수 >

 

sub_401040(get_fileoffset) 구조

 

   sub_401040 함수의 구조를 살펴보니 함수 안에서, NT_Header 주소(arg_4)와 내가 접근하고자 하는 항목의 RVA 값이 담긴 주소(arg_0)를 인자로 넘겨 sub_401000 함수를 호출하는 것을 볼 수 있었다. sub_401000 함수가 어떤 동작을 하는지 더 살펴보았다.

 

< sub_401000(get_section_header) 함수 >

 

  sub_401000 함수의 구조는 위 사진과 같았다. 인자로 넘겨받은 NT_Header 주소 값을 이용해서 섹션의 개수(numberOfSections)를 알아내고 IMAGE_SECTION_HEADER의 시작주소를 알아낸다. 섹션 개수만큼 비교할 IMAGE_SECTION_HEADER들을 바꿔가며(. text,. data,...) "내가 접근하고자 하는 항목의 RVA"의 위치와 비교하는 반복문이 동작한다.

 

  이 반복문은 "내가 접근하고자 하는 항목의 RVA"가 어느 섹션에 존재하는지 확인이 되면 종료되고, 해당 섹션의 IMAGE_SECTION_HEADER 주소를 eax로 반환한다. (이러한 결과를 토대로 sub_401000을 필자는 get_section_header로 칭했다.)

 

 

   sub_401000(get_section_header) 함수를 성공적으로 실행하면 위 사진에서 빨간 네모 영역의 명령어들을 실행한다. get_section_header 함수를 실행해서 얻은 IMAGE_SECTION_HEADER에 담긴 정보들을 이용해서, 결과적으로는 "내가 접근하고자 하는 항목의 File Offset"을 계산해 eax로 반환한다. ("접근하고자 하는 항목의 RVA" - 해당 섹션의 RVA + 해당 섹션의 File Offset + 해당 파일(.dll) 시작주소)

 

  즉, sub_401040 함수는 "내가 접근하고자 하는 항목의 File Offset"을 구하는 함수다. (필자는 sub_401040을 get_fileoffset이라는 함수로 칭했다.) 

 

 

 

  위에서 설명한 함수들을 이용해서 kernel32.dll의 Export와 관련된 정보들에 접근해서 저장한 다음, lab07-03.dll의 Export 정보를 만들어나가는 과정이 일어난다.

  

  이외에도 ecx, eax 레지스터와 repne scasb 명령어를 이용해서 문자열의 길이를 측정하는 구문이나, ecx(문자열의 길이) 레지스터와 repne movsd/b 명령어를 이용해서 문자열을 복사하는 구문 등 신기한 구문들이 있었다. kernel132.dll이라는 정보를 기록하거나 export 함수 이름 사이에 "Kernel32."라는 문자열을 집어넣는 신기한 과정도 있었는데, 이는 직접 분석해 보면 좋을 것 같다.


 

 

  그렇게 kernel32.dll의 export 정보들을 다 가져와 조작을 마치면 사용했던 핸들을 정리하고, 수정이 된 Lab07-03.dll을 복사해 "C:\windows\system32\kerne132.dll"로 저장한다. 여기까지가 악성파일인 "kerne132.dll"을 생성하는 과정이다.

 

   저장을 성공적으로 마치면 이어서 "C:\*"라는 문자열을 인자로 넘겨 sub_4011E0 함수를 실행하는데, 이 함수에서 본격적으로 "kerne132.dll"을 이용해 어떠한 행위를 하는 것으로 보인다. 해당 함수를 좀 더 살펴보자.

 

 

< sub_4011E0 함수 >

sub_4011E0 함수 구조

 

  사진을 보면 알겠지만 이 함수의 구조도 조금 복잡해 보인다. 분석하기에는 오랜 시간이 걸릴 것으로 보아서 해당 함수의 큰 동작 과정 및 결과만 설명한다. 

 

  이 함수는 인자로 넘어온 "C:\"를 기준으로 시작해서 하위 디렉토리들을 모두 검색한다. 이 과정에서 파일 검색 함수들(FindFirstFileA, FindNextFileA)이 사용되고, 검색된 항목의 종류에 따라 크게 두 가지 과정으로 나뉘어 동작한다. 

 

(1) 디렉토리 항목 발견 시

 

  (1) 검색하면서 발견한 항목이 디렉토리일 경우로, 해당 디렉토리 주소를 인자로 넘겨서 sub_4011E0 함수를 실행한다. 일종의 재귀함수로, 하위 디렉토리로 더 들어가서 검색하는 과정이 되겠다.

 

 

(2) .exe 파일 발견 시

 

  (2) 검색하면서 발견한 항목이 파일일 경우로, 해당 파일이 .exe 파일인지 확인하는 과정을 거친다. .exe 파일이 아닐 경우에는 다음 항목을 검색하고, .exe파일일 경우에는 해당 파일을 인자로 넘겨 sub_4010A0 함수를 호출한다. 해당 함수에서 어떤 일이 일어나는지 살펴보자.

 

 

< sub_4010A0 함수 >

 

sub_4010A0 함수 구조

 

  sub_4010A0 함수의 구조는 위의 함수들보다는 비교적 양호해 보이나, 반복문 구조가 있어서 동작과정을 다 설명하기에는 시간이 걸릴 것 같다. 그래서 이 함수도 동작 과정과 결과만 설명하겠다.

 

 

   함수 초반을 보면 메모리 매핑 함수들을 사용한 것을 볼 수 있다. PAGE_READWRITE 조건을 달고 만든 것을 보아 파일 내용을 수정도 할 수 있도록 했음을 알 수 있다. 메모리 매핑을 마치면 IsBadReadPtr 함수를 이용해서 해당 메모리에 접근 가능한지 확인하는 과정을 여러 차례에 걸쳐서 확인한다. 이러한 점을 보아, 어떤 값을 수정할 수도 있겠다는 추측을 할 수 있다.

 +) IsBadReadPtr 함수를 검색해 보면, 이제는 사용 권장을 하지 않는 함수인 것을 볼 수 있다. 악용될 여지가 있는 함수로 본다는 뜻이므로, 이러한 점도 알아두자.

 

 

 

   메모리에 매핑한 파일에 접근하기 위해, 이 함수에서도 get_fileoffset 함수를 사용한 것을 볼 수 있다. 해당 함수를 통해 얻어낸 fileoffset을 확인해 보니, Import Table을 가리키는 오프셋이었다. Import 테이블에 담긴 값을 이용할 것으로 볼 수 있고, 더 진행하다 보니 해당 파일에서 Import한 dll 파일의 Name 값에 접근하는 것을 알 수 있었다.

 

 

 

   이후에는 import 한 dll 파일의 이름이 "kernel32.dll" 확인하는 과정을 거치는데, 아닐 경우에는 다음 import dll의 이름을 확인하도록 다음 반복문으로 넘어가고, "kernel32.dll"이 맞을 경우에는 "kernel32.dll"을 "kerne132.dll"로 바꾸는 동작을 하는 것을 볼 수 있었다. 

 

   즉, 4010A0 함수는 인자로 넘겨받은 .exe 파일이 "kernel32.dll"을 import 하고 있을 경우, 이를 "kerne132.dll"로 바꾸는 동작을 하는 함수이다.

 

  결과적으로 03.exe 파일은 내 C드라이브에 있는 모든 파일들을 탐색하면서, 정상 "kernel32.dll" 파일을 import 한 파일들을 찾아 이를 "kerne132.dll"로 바꾸어서 동작하도록 하는 파일이라고 결론지을 수 있겠다. 

 

 

● 동적 분석

 

  위의 과정들을 통해 03.exe와 03.dll의 동작과정들을 살펴보았으니, 이제 다시 조건을 맞추어 실행시켜서 어떠한 변화가 있는지 확인해 보자.

 

system32 폴더

 

  우선 System32 폴더에 kerne132.dll 파일이 새로 생긴 것을 볼 수 있었다. 

 

ProcMon 분석

 

   ProcMon으로 관찰해 본 결과 Lab07-03.exe 실행 시 kerne132.dll이 잘 쓰인 것을 볼 수 있다. 이 때는 이미 실행 중인 파일들은 "kerne132.dll"을 참조하지 않지만, 위 사진에서 볼 수 있듯 7zip이나 python 같이 새로 실행한 프로세스들은 "kerne132.dll" 파일을 참조하는 것을 볼 수 있다.

 

ProcExp 정보

 

   위 사진처럼 ProcExp로 확인해 보면, 새로 실행한 Python이 "kerne132.dll"을 참조하는 것을 볼 수 있다. 시스템을 재부팅한 후에도 이는 적용된다.

 


 

1. 이 프로그램은 어떤 방식으로 컴퓨터가 재시작할 때마다 실행(지속 메커니즘)을 보장하는가?

- 모든 .exe 파일들이 실행 시 "kernel32.dll" 파일 대신, "kerne132.dll" 파일을 import 하도록 변경했기 때문에, 컴퓨터가 재시작을 하던 하지 않던 .exe 파일들이 실행될 때마다 실행이 된다.

 

 

2. 이 프로그램을 탐지할 때 호스트 기반으로 좋은 시그니처는 무엇인가?

- 03.exe 파일에서는 "kerne132.dll" 문자열을 시그니처로 잡으면 될 것 같고, 03.dll 파일에서는 뮤텍스로 사용된 "SADFHUHF"을 시그니처로 잡으면 될 것 같다. 네트워크 기반 시그니처로는 "127.26.152.13"을 잡으면 될 것 같다.

 

 

3. 이 프로그램의 목적은 무엇인가?

- 공격자로 보이는 "127.26.152.13"에서 감염자 pc에 "sleep"이나 "exec(명령어)"를 송신하여 pc를 조작하는 일종의 백도어 목적으로 보인다.

 


4. 일단 악성코드가 설치된다면 어떻게 삭제할 수 있는가?

- 삭제하기 굉장히 까다로울 것으로 보인다. 위 프로그램을 모방하여서 "kerne132.dll" 파일을 정상 "kernel32.dll"파일로 바꾸는 프로그램을 만드는 방법도 있겠으나, 가장 쉬운 방법은 감염되기 이전의 상태로 롤백하는 방법으로 보인다.

 

 


 

+) 참고 사이트

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=heobk1&logNo=221407406445 

문자열 길이 측정하는 어셈블리 명령어 구문에 대해 설명한 사이트

 

https://jumpzero.tistory.com/13

문제와는 전혀 상관없지만 그냥 리버싱하는 과정에 대해 참고하면 좋아 보이는 사이트