안녕하세요. Message 입니다.

<실전 악성코드와 멀웨어 분석> 책의 실습 문제 9-1을 분석합니다.

점점 분량이 늘어나고 있습니다...

스크롤 압박이 다소 있지만, 중간중간 필요한 서브루틴을 적절히 검색하시면 편리합니다~

 

※ 분석환경 : WindowsXP, Windows7, Vmwre 12.1.0 build, IDA 6.1 and 6.6

 

 

 

-----------------------------------------------------------------------------------------------------------------

0x00 기초 동적 분석

-----------------------------------------------------------------------------------------------------------------

문제풀이에 앞서 간단한 동적 분석을 진행합니다.

 

1. PE구조 & 패킹

별도의 패킹이 되어 있지 않습니다.

 

2. DLL

KERNEL32, ADVAPI32, WS2_32 등은 자주 보아왔던 DLL이었지만

SHELL32 ShellExecuteA 함수가 눈에 띄었습니다.

어디선가 보았다 싶어서 찾아봤더니 OLE 취약점 분석 포스팅에서 VBScript에 있던 함수명이네요.

해당 취약점에서는 ShellExecute 함수를 이용하여 cmd.exe 파일을 실행시키는 용도로 사용하였는데

이번 케이스는 어떻게 사용될지 궁금합니다.

 

3. String

1) .data 섹션

레지스트리 경로, URL, system32 폴더, CMD 명령어, Service 관련 문자열들이 눈에 띕니다.

네트워크 통신과 더불어 시스템에 영향을 미치는 기능을 수행하는 악성코드로 예상됩니다. 

 

2) .rdata 섹션

대부분이 프로그램 생성시 포함되는 소스코드 또는 Import DLL 목록으로 판단됩니다.

R6016~R6028로 시작하는 문자열들이 많이 등장하고 있습니다.

프로그램 로드 또는 실행시 CRT에 의해서 발생하는 C Runtime Error 관련 에러 문자열입니다.

이번 문제 역시 Lab06-01 문제와 동일하게 printf 를 WIndows API로 명명해주지 않기 때문에

내부에서 사용되는 함수/문자열들이 Hex-ray를 통해 소스코드로 변경되면서 수면 밖으로 나온것으로 판단됩니다.

(해당 문자열들을 XREF로 추적하면 sub_402E7E(printf) 에서 사용되고 있습니다.)

MSDN : https://msdn.microsoft.com/en-us/library/6f8k7ad1.aspx

 

Tip. String Window에서 .data vs .rdata 섹션

 IDA를 이용한 초기 동적분석에서 항상 살펴보는 곳이 String Window입니다.

 그리고 String 값들은 대게 data/rdata 섹션에 존재했습니다. 어떤 차이일까요?

 

 data Section

 섹션헤더 Chracteristics 필드를 살펴보면 READ/WRITE 속성을 모두 가지고 있습니다.

 저장되는 변수는 초기화된 전역변수(global)와 정적변수(static) 등 입니다.

 

 ② rdata Section

 CONST변수, 하드코딩(printf 등) 문자열, 소스코드에 포함된 문자열 등이 저장됩니다.

 또한 Import 되는 DLL 목록이 저장되기로 하므로, API들과 DLL 이름등이 저장됩니다.

 

 ③ Test

 VS2015로 global, static, local 타입의 포인터/배열을 선언하여 테스트해보았습니다.

 테스트 환경은 학습을 위해 release 모드로 빌드후 XP에서 실행하였습니다.

   LOCATION : 선언된 변수의 위치,   VALUE : 문자열 위치,   STR : 문자열

 

 

 위 결과를 기반으로 정리하면 아래와 같습니다.

 ① ptr String : .rdata 섹션

 ② arr String : 전역변수  data섹션 (const 제외) , 지역변수  Thread Stack (static 제외)

 포인터 스트링은 모두 .rdata 섹션이며, 배열 스트링은 상황에 따라 복잡합니다.

 배열이라 하더라도 CONST는 상수이므로 자연스럽게 rdata 섹션에 저장되며,

 나머지는 data + Thread Stack 영역에 저장됩니다. 

 

 <참고>

 주소값들을 살펴보면 스택이 PE보다 낮은 주소에 있습니다.

 처음에는 오류인줄 알고 헷갈렸습니다. 왜냐하면 스택은 높은곳에서 거꾸로 자라며,

 계속 자라면 Heap하고 충돌...등등 어디서 주워들은 얘기로만 기억하고 있었고,

 구글링을 해보아도 같은 얘기를 하는게 맞나 싶을 정도로

 다양한? 메모리 구조 그림들이 나왔습니다.

 (아마 Unix계열과 Windows계열이 섞여서 돌아다녀서 그런가봅니다.) 

 

 Vmap으로 메모리를 힙/스택 모두 PE보다 상단(낮은주소)에 있었으며

 이것에 맞는 메모리 구조를 표현한 그림은 아래와 같았습니다.

  Thread Stack : 0x00030000 ~ 0x0012FFFF

 

 

 *그림출처 : http://www.securitysift.com/windows-exploit-development-part-1-basics

 

 

 

-----------------------------------------------------------------------------------------------------------------

0x01 정적 분석

-----------------------------------------------------------------------------------------------------------------

문제풀이를 위한 정적 분석을 진행합니다.

 

 

1. main 함수 - 인자가 1개인 경우

main 함수에 진입하면, __alloca_probe 함수를 통해 스택을 0x182C 만큼 할당한뒤

전달된 인자의 개수가 1인지 체크합니다. 만약 결과값이 1이라면 sub_401000 함수를 호출합니다.

__akkica_probe 함수의 경우 지난 문제에서 상세 분석을 마쳤으므로, 무난하게 패스합니다.

 

 

sub_401000 함수를 살펴보면,

RegOpenKeyExA 함수를 이용하여 빨간색으로 배경이 지정된 문자열의 레지스트리 키를 Open하고

"Configuration" 항목값을 가져옵니다.

 

가끔 IDA에서 String 값을 제대로 못읽어와서 위에처럼 빨간색으로 처리되는 경우가 종종 발생하는데

(불행하게도 아직 해결하는 방법을 몰라요)

함수에 들어가는 인자값들은 OllyDbg를 통해 좀더 직관적이고 빠르게 분석이 가능합니다.

레지스트리키는 HKLM하이브의 "SOFTWARE\Microsoft\WPS" 이며, 해당 키에서 "Configuration" 항목값을 가져옵니다.

※ sub_401000 정리 : 레지스트리 존재 여부 체크

 

레지스트리 open에 실패하거나, "Configuration" 항목값이 존재하지 않을 경우 sub_402410 서브루틴을 호출합니다.

sub_402410 함수의 경우, 아래 그림에서 보다싶이 main에서 매우 빈번하게 호출되는 함수입니다.

해당 함수의 내부를 살펴보면, GetModuleFileNameA, GetShortPathNameA 함수를 이용하여

Full/Short Path를 얻으며, 이후에 "/c del" 문자열을 Parameters 변수에 복사합니다. 

 

 

이후 이어지는 코드는 IDA에서는 비교적 복잡해 보이지만

이전 문제에서 한번 분석해본 내용으로서, do-while 문으로 문자열의 길이를 구하는 내용입니다.

현재 szLongPath 변수에 Short Path가 들어있으니, Short Path의 문자열 길이를 얻게 됩니다.

이러한 do wile문은 버전이 낮은 Hex-rays에서 문자열이 들어가는 함수가 나올경우 자주 등장하는

패턴입니다. 버전을 높이면 생략되더군요.

 

 

첫번째 탈출 조건은 변수 v0에 0xFFFFFFFF(-1)을 할당한 후 0이 될때까지 -1을 하여서 0이 되면 break 합니다.

두번째 탈출 조건은 문자열이 담긴 v1 변수에서 문자열을 끝을 나타내는 null 문자 발견입니다. 

처음에는 아래의 소스코드가 헷갈렸는데,

*v1++ == 0 조건이 성립할때 v2에 *v1++을 할당하는 내용으로 해석하면 될 것같습니다.

 ex) v2 = *v1++ == 0;   if( 변수 또는 계산식 == 0 ) v2 = 변수;

 

이후에 v4 = ~v0; 코드로 값을 반전하여 글자수를 얻습니다. 글자수는 8자리네요.

 

이어지는 코드는 비슷하게 Parameters 변수에 저장된 "/c del " 문자열의 길이를 동일 로직으로 얻습니다.

특이하게 v7 변수에 &v1[-v4] 주소값을 할당하는 부분이 있습니다.   v2 = *v1++ == 0;  

위 코드에서 *v1++ 로 인해 증가된 v1의 의 주소값을 다시 "/c del " 문자열을 가리키는 주소로 보정하기 위함입니다.

 

이후에 memcpy 함수를 이용하여 v7에 저장된 문자열을 (void *)(v6 -1)

주소값에 복사하여 아래의 문자열을 완성합니다.

경로값을 고려해서 del 문자열 뒤에 한칸 스페이스가 삽입되어 있는 부분을 체크합니다.

 

역시 동일한 방식으로 ">> NUL" 문자열의 글자수를 계산하고

memcpy 함수를 통해 최종문자열 "/c del c:\단축경로\Lab09.01.exe >> NUL" 문자열을 완성시킵니다.

해당 문자열의 의미는 스스로 파일을 삭제함과 동시에, 에러 출력을 버리는 의미를 가집니다.

완성된 문자열은 ShellExecuteA 함수를 통하여 CMD 명령어로 사용됩니다.

 

저는 몰랐지만, OllyDbg로 실행하고 있기 때문에 윈도우가 파일삭제를 허용하지 않는다고 하네요.

(사실 실수로 몇번 브레이크 포인트를 지나서 실행해버린 바람에 가슴철렁 했었는데...)

이후 exit 함수로 프로그램을 종료합니다.

※ sub_402410 정리 : 레지스트리 체크 + 디스크에서 악성코드 삭제

 

sub_401000 서브루틴에서 레지스트리가 존재할 경우, 프로그램이 종료되지 않고 sub_402360 서브루틴을 호출합니다.

해당 서브루틴의 내부 로직을 살펴보면, 원하는 조건이 충족될때까지 무한 루프를 수행합니다.

정확한 행위를 분석하려면 while문 내부의 sub_401280sub_402020 함수의 분석이 필요합니다.

 

sub_401280 서브루틴을 살펴보면,

sub_401000 서브루틴에서도 보았던 "\\XPS" 레지스트리를 Open하고 "Configuration" 값을 한번더 체크합니다.

계속 중복체크하는것을 보니, 분명 악성코드의 중요 행위중에 하나로 보여집니다.

만약 해당 레지스트리가 존재하지 않을 경우 함수의 인자로 받은 a1, a3, a5, a7 변수에

"Configuration" 항목에서 받아온 내용을 글자수만큼 주소값을 이동하며 복사합니다.

하지만 아직은 어떤 문자열이 해당 변수에 저장되는지는 알 수 없습니다.

※ sub_401280 정리 : 레지스트리에서 "Configuration" 항목값을 얻어와서 변수에 할당 

Tip. Hex-ray에 주석남기기

 이번 문제와 같이 함수에 함수가 꼬리를 무는 경우, 헷갈리기 시작하면서

 분석시간이 길어질 수 있습니다.

 이럴땐 단축키 "/" 를 눌러서 아래와 같이 주석을 남길 수 있습니다.

 사실 간단한 부분인데...이런 노하우가 늘어날수록 분석 시간이 조금이라도 단축되겠죠.

 

 

다시 while 문으로 돌아갑니다.

위에서 분석한 sub_401280 서브루틴으로 "Configuration" 항목값에 있는 값들을 변수에 담아 왔으니

이후에 실행되는 sub_402020 서브루틴에서는 해당 값들을 활용할 것으로 예상됩니다.

 

 

 

2. 명령어 제어 프로토콜(sub_402020) - Part1(명령어수신)

서브루틴 내부를 살펴보면, 가장 먼저 실행하는 sub_401E60 서브루틴을 비롯한 여러 문자열들이 보입니다.

"SLEEP", "UPLOAD", "DOWNLOAD", "CMD" 등의 문자열이 무엇을 의미하는지 알아볼 필요가 있습니다.

 Tip. Hex-ray 옵션 - Strings Generate names

 IDA 버전을 6.6으로 업그레이드 했습니다.

 Hex-ray 코드가 좀더 간결해 졌지만, 문자열이 아래와 같이

 변수명으로 표현되어 가독성이 떨어집니다.

 

 IDA 옵션에서 한참 삽질을 하다보니.. Hex-ray 옵션이 따로 있더군요.

 IDA 설치경로의 cfg 폴더에서 hexrays.cfg 파일로 옵션을 설정합니다.

 Analysis 옵션의 HEXOPTIONS 값에서 read-only만 literal로 출력하는

 HO_CONST_STRING 옵션 비트 조정합니다. ex) 0x01FF → 0x01BF

 

 이제 아래와 같이 Hex-ray 코드에서 문자열이 표현됩니다.

 

 

먼저  sub_401E60 서브루틴을 살펴봅니다.

저같은 경우 sub_401E60 서브루틴 내부에 있는 함수들이 단발성(Xref에서 하나의 상호 참조만 있는) 함수들이

많은데다가, 전체적인 기능 파악이 안되어 있는 상태에서 하나만 계속 파고드는 느낌이 들어서

바로 분석하기 부담을 느꼈습니다. 그래서 처음엔 pass 하고 가장 마지막에 분석하였습니다.

이건 아마 분석하시는 분들의 성향에 따라 달라질듯하네요.

 

일단 가장 먼저 실행되는 서브루틴은 sub_401420 서브루틴으로서, 내부를 살펴보면

바로 위에서 분석한 sub_401280 서브루틴의 축소판입니다. 함수 자체가 BOOL 타입이지만,

포인터를 활용하여 인자로 전달된 &name 변수에 레지스트리에 저장된 문자열을 가져옵니다.

아래쪽에서 레지스트리를 등록하는 부분이 분석되어 있지만, 미리 컨닝을 하자면 아래의 URL을 가져옵니다.

※ sub_401420 정리 : 등록된 레지스트리에서 URL 값을 가져옴, sub_401280의 축소판 

 

두번째로 실행되는 서브루틴은 sub_401470 입니다.

해당 서브루틴 역시 내부에서 sub_401280 서브루틴을 호출하고 있으며,

인자로 전달된 a1 변수에 아래와 같이 포트번호로 보이는 문자열 "50(=80)" 값을 저장합니다.

레지스트리에 등록되는 값이 문자열이기 때문에 atoi 함수를 이용하여 정수값으로 변환합니다.

※ sub_401470 정리 : 등록된 레지스트리에서 포트번호를 가져옴, susub_401D80b_401280의 축소판 

 

세번째로 실행되는 서브루틴은 sub_401D80 입니다.

눈에 띄는 부분은 time 함수와 왠지 복잡하게 보이는 여러개의 for문입니다.

먼저 time 함수값이 어떻게 사용되는지 살펴보면,

sub_4030E0 서브루틴에 전달되어 단순하게 dword_40C180 변수에 값을 할당합니다.

왜 이런 함수가 있을까 궁금해서 Xref로 검색해보니 rand 함수 내부에서 해당 변수가 사용되고 있습니다.

 

rand 함수 내부를 살펴보면 time(NULL) 값을 바탕으로 난수값을 return 하고 있습니다.

rand 함수는 srand 함수를 미리 호출하지 않으면 동일한 난수값을 발생시키므로,

time(0) + sub_4030E0 서브루틴의 조합은 srand(time(NULL)) 과 동일한 기능으로 판단됩니다.

 

for문의 경우 포인터의 주소값을 1씩(BYTE 단위) 증가시키며 sub_401D10 서브루틴의 값을 저장합니다.

중간중간 구분자로 보이는 47("/"), 46("."), 0(NULL) 값을 넣어주는군요.

 

어떤값을 채우느냐가 관건입니다. sub_401D10 서브루틴을 살펴봅시다.

do-while 문으로 조건이 충족안될때까지 실행을 반복합니다.

while( (v1 < 48 || v1 > 57) && (v1 < 97 || v1 > 122) && (v1 < 65 || v1 > 90) )

실제로 찍어보거나, 엑셀로 계산하거나, 암산을 하시거나... 확인 방법은 자유입니다.

결과는 영어대소문자 + 숫자 아스키코드 범위이며, 결과값은 아래와 같습니다.

※ sub_401D80 정리 : 특정 구분자가 포함된 문자열 반환 

 

네번째로 실행되는 서브루틴은 sub_401AF0 입니다.

초반부에서 sub_401640 서브루틴을 호출하고 있습니다. 먼저 분석하고 되돌아옵니다.

 

sub_401640 서브 루틴을 살펴보면,

Lab07-03 문제에서 나왔던 패턴이 보입니다. 윈속으로 통신을 수행하기 위해서

WSAStartup → gethostbyname → socket → connect 순서로 연결을 시도합니다.

※ sub_401640 정리 : 인자로 전달된 도메인(=name)에 winsock으로 connect 수행 

 

다시 sub_401AF0 서브루틴으로 돌아갑니다.

strcpy + stcat 함수를 이용하여 buf 변수에 "GET VuHD/2tCo.tjd HTTP/1.0\r\n\r\n" 문자열을 생성합니다.

이후 connect로 연결한 원격지에 send 함수를 이용하여 통신을 수행합니다. 

 

만약 정상적으로 서버와 연결이 되었다면,

서버로부터 변수 v6 에 512 길이만큼 데이터를 전송받습니다.

수신된 데이터는 qmemcpy 함수를 통해 a4 배열에 차곡차곡 복사합니다.

반복 루틴이 실행될때마다 strstr 함수를 이용하여 "\r\n\r\n" 문자열이 존재하면 break 합니다.

 

 Tip. qmemcpy 

 qmemcpy 함수는 msdn에 검색해도 나오지 않았습니다.

 하지만 Hex-rays Home에서 6.6 버전을 릴리즈하며 게시한 내용에

 간략한 설명이 있어서 올립니다.

 왜 앞에 "q" 문자가 붙었는지는 모르겠지만

 중요한건 memcpy와 사용방법이 동일하며, byte by byte 방식으로

 낮은 주소에서 높은 주소로 복사를 한다는 점이겠죠.

 

 

 

recv 함수는 정상적인 데이터 전송이 이루어졌다면, 수신한 바이트의 길이를 리턴합니다.

하지만 리턴값이 0 이하일 경우(데이터 못읽음, 세션종료, 오류 등) 또는

"누적된 데이터 길이 + 방금 받은 데이터 길이" 가 인자로 넣어준 변수 a5(=4096)의 크기를 초과할 경우

sub_401740 서브루틴을 호출하여 shutdown, closesocket, WSACleanup 등의 세션종료 함수를 호출합니다.

 Tip. Windows 함수에서의 ErrorCode

 위에서 등장한 recv, shudown 등의 windosck 함수들을 호출하고나서 결과값이 0 미만일 경우

 result 값을 1로 반환하여, 정상적인 진행이 되지 않았을 경우의 실행 코드가 존재합니다.

 0 미만의 값이 무엇을 의미하는지 MSDN에서 살펴보려고 했습니다만,

 아래와 같은 WSAGetLastError 함수와 함께 에러코드들의 링크가 존재하더군요.

 그런데 문제점은 모두가 하나같이 0 이상의 상수값이라서 헤멨습니다.

 (분명 0 미만일 경우의 코드들이 존재했는데, 왜 모두 0 이상이지? 라는 생각과 함께요)

 

 자세하게 설명을 읽어보지 못한 저의 불찰입니다만,

 GetLastError 또는 WSAGetLastError 함수들은 에러가 발생한 이후에

 별도로 호출하여 에러코드를 얻어내는 함수들입니다. 

 recv 함수는 정상적이라면 수신한 바이트 길이를 리턴하며, shutdown 함수는 정상적인 경우

 0(zero)을 반환합니다. 그래서 보통 0 미만의 값을 가지면 실패 또느 오류로 인지하는것이죠.

 

sub_401740 서브루틴에서도 원하는 결과를 얻지 못했다면, 마지막 else 문으로 진입합니다.

strstr 함수로 " `'`'` " 문자열의 위치를 검색하는 루틴입니다.

문자가 헷갈려서 간단하게 찍어보니 16진수값 0x27(" ' "), 0x60(" ` ") 이 나왔습니다.

교재에서는 백틱, 어포스트로피라고 되어 있는데...백틱이란 문자는 생소하네요.

만약 해당 문자열들이 둘다 검색된다면,

qmemcpy 함수를 이용하여 v5 변수가 가지고 있는 주소값의 내용을 a1 변수로 복사합니다.

그 다음줄은 마지막 라인에 0(null)값을 넣어주면서 문자열의 끝을 나타내는것으로 생각됩니다.

변수 v10 에는 sub_401AF0 서브루틴에서 GET 으로 얻어온 파일이 담겨있습니다.

서버에서 가져온 파일 또는 문서에서 특정 내용을 a1 변수에 반환하여

위에서 잠시 살펴보았던 "SLEEP", "UPLOAD" 등의 명령어일 경우 해당되는 함수들을 호출합니다.

※ sub_401E60 정리 : 등록된 레지스트리에서 항목값을 가져오거나, URL로부터 명령어 수신 

 

 

 

 

3. 명령어 제어 프로토콜(sub_402020) - Part2(명령어별 동작)

이제 명령어를 수신하였으니, 명령어별로 어떤 동작을 수행하는지 살펴봅니다.

v14 변수에 담긴 문자열과 명령어들과의 비교를 통해 일치할 경우, strotok 함수를 이용하여 문자열을 분리합니다.

strtok 함수의 특성상, 처음에만 주소값을 넣어주고 이후에는 Null 을 넣어줌으로서 지속적인 분리를 수행합니다.

이렇게 분리하는 이유는, 위쪽에서 strncmp 함수를 이용하여 문자열의 앞부분에서 특정 길이만큼만 체크한뒤

그 뒤에 "(공백)" " ' "  구분자로하여 이어지는 다른 명령을 파싱하기 위해서일겁니다.

그래서 각 명령어마다 서로 다른 sub_401790 / sub_401870 / sub_4019E0 함수 등을 호출하는것으로 보입니다.

 

① "SLEEP"

해당 명령의 경우 말그대로 Sleep 함수를 실행시킵니다.

함수 인자의 단위는 밀리초(=ms, milliseconds, 1/1000초) 이므로,

"1000 * v3" 계산에서 v3 변수가 몇초 동안 sleep 상태로 대기할 것인지를 결정합니다.

 

 "UPLOAD"

해당 명령의 경우 sub_4019E0 서브루틴을 실행합니다.

 

sub_4019E0 서브루틴의 내부 동작을 살펴보겠습니다.

CreateFileA 함수로 파일을 생성(또는 open) 하고 있으며,

실패할 경우 sub_401740 서브루틴으로 winsock 연결을 해제(shutdown, closeconnect) 합니다.

 

하지만 성공하였을 경우에는, 

방금 얻어낸 handle + recv 함수+ WriteFile 함수로 파일의 로우 데이터를 입력합니다.

여기서 의아했던 점은 분명 "UPLOAD" 인데 서버에서 데이터를 수신하고 있다는 점입니다.

뒤쪽에서 알게되었지만, "DOWNLOAD" 역시 반대로 서버에 데이터를 송신합니다.

이것으로 미루어보아, 명령어의 주체가 감염된 PC가 아닌 서버 or 악성코드 제작자임을 알 수 있습니다.

 

서버와의 연결이 성공적으로 종료되면, sub_4015B0 + sub4014E0 서브루틴을 호출하여

특정 파일 생성/복사 등을 수행합니다. 자세한 내용은 아래쪽의 "-in" 기능을 분석에 있습니다.

(내용이 길어지고 함수가 반복적으로 쓰이다보니 왔다갔다하네요..^^;)

 

 

"DOWNLOAD"

위에서 어느정도 파악을 했다싶이, 실제로는 감염된 PC에 파일이 다운로드 되어지는 루틴입니다.

 

호출되는 sub_401870 서브루틴을 살펴봅니다.

CreateFile 함수를 통해 파일을 오픈하는 과정은 들어가는 몇개의 인자값을 제외하고는 동일합니다.

다운로드의 경우 업로드와는 달리 "dwCreationDisposition" 인자값을 EXCREATE_ALWAYS OPEN_EXISTING

설정하였기 때문에 덮어쓰지 않고 기존에 존재하는 파일을 오픈합니다.

이후에 ReadFile 함수 + send 함수를 이용하여 서버 또는 악성코드 제작자에게

파일에서 읽어온 데이터(&Buffer)를 송신합니다.

 

 

 "CMD"

해당 함수는 말그대로 CMD 기능을 사용하는 명령어이며,

_popen 함수를 이용하여 cmd에서 명령어를 실행하고 있습니다.

"rb" 옵션의 경우 프로세스가 결과값을 볼 수 있음(=r) + 이진 데이터 반환(=b)을 의미합니다.

이후에 sub_401790 서브루틴을 호출합니다.

 

sub_401790 서브루틴의 경우 _popen 함수의 결과값을 인자로 받고 있으며,

fread 함수를 이용하여 결과값을 읽은 후에 send 함수로 서버에 송신하는 역할을 수행합니다.

 

 

4. main 함수 - 인자가 여러개인 경우

메인함수로 되돌아옵니다.  사실 저희는 아직 main의 6라인 밖에 분석을 못했습니다.

얼마 남지 않았음을 믿어 의심치 않으며...달려봅시다.

else문을 살펴보면, 프로그램 실행시 받은 마지막 인자를

v10 변수에 할당하고 sub_402510 서브루틴을 호출하는군요.

 

sub_402510을 살펴봅니다.

매개변수 int achar* 형으로 캐스팅한 뒤 문자열의 길이를 체크합니다.

만약 길이가 4가 아닐 경우에는 바로 result = 0 을 리턴하게 되며, 이럴경우

메인으로 돌아가서 본인 삭제 기능을 가진 서브루틴 sub_402410을 호출됩니다.

앞으로도 어떤 조건을 충족시키지 못하면 sub_402410을 호출하는 횟수가 빈번해집니다.

이는 프로그램 실행시 특정 조건을 반드시 맞춰야 하는 비밀번호와 같은 역할입니다.

문자열의 길이가 4라고 가정하고 다음으로 넘어갑니다.

 

int형 매개변수 a1을 _BYTE * 형으로 캐스팅한뒤 97(=a) 과 비교하여 일치하면 아래의 연산을 수행합니다.

 v2 = *(_BYTE *)(a1 +1) - *(_BYTE *)a1

이때 a1은 int형이므로 "[]" 연산이 불가능하며, a1이 가리키는곳의 문자열은 총 4자리임을 기억해야합니다.

*(_BYTE *)(a1+a1)이 의미하는 바는 a1에 저장되어 있는 숫자(주소값)에 +1을 더하여 값을 가져오므로 

a1이 가리키는 문자열 중에 두번째 문자를 의미합니다.

따라서 v2에는 (두번째 문자) - (첫번째 문자) 연산 결과가 할당됩니다.

v2 == 1이 되려면 자연스럽게 두번재 문자는 98(=b) 가 되며,

v3 = 99* v2 로 인해 세번째 문자는 99(=c) 입니다.

 

위와같은 로직으로 계산하게 되면, 이후에 실행되는 코드도 해석이 쉬워집니다.

변수 v3와 a1이 가리키는 문자열 중에 세번째 문자를 비교하는 코드입니다. 

 if( v3 == *(_BYTE *)(a1+2) ) 

 

만약 일치한다면, 세번째 문자와 네번째 문자가 일치하는지 확인하고, 일치할 경우 result에 저장합니다.

여기서 (char)(v3+1) 은 v3 주소값에서 +1을 하는것이 아니라,

말그대로 v3값(99)에서 +1을 해서 (char)형으로 캐스팅을 한겁니다. 즉, 네번째 문자는 100(=d) 입니다.

 result = (char)(v3+1) == *(_BYTE *)(a1 + 3) 

 

다시 main으로 돌아갑니다.

이후에 _mbscmp 함수를 이용하여 argv[1]에 있는 변수와 "-in" "-re" "-c" "-cc" 문자열을 비교합니다.

_mbscmp 은 strcmp 의 멀티바이트 문자 버전으로서, MBCS기반의 문자 비교를 수행합니다.

각 문자열 별로 비교하는 부분을 살펴봅니다.

 

① "-in"

일치 O : 인자의 개수에 따라 else if 또는 else 문 진입  sub_4025B0, sub_402600 서브루틴 실행

일치 X : IF문 내부로 진입 (_mbscmo : 문자열이 동일한 경우 0 반환)

 

"-in" "-re" "-cc" 의 공통점은 인자가 3개일때 sub_4025B0 서브루틴을 호출하는 부분입니다.

sub_4025B0를 살펴보면, 내부에서 getModuleFileNameA 함수를 호출하는것 외에는

특별한 기능을 수행하지 않습니다. _splitpath 함수는 디렉토리를 분리하는 함수로서

드라이브명, 디렉토리명, 확장자 등을 분리하여 각 변수에 담을 수 있는 함수입니다.

세번째 인자 a1 변수에 파일명이 분리되어 담길것입니다. 이후 0을 반환하고, main에서 sub_402600을 호출합니다.

sub_4025B0 정리 : 인자로 넣어준 변수의 파일명 반환

 

sub_402600를 살펴보면,

strcpy + strcat을 이용하여 "%SYSTEMROOT%\\system32\\서비스명(=파일명).exe" 문자열을 만듭니다.

사실상 여기서 서비스명은 sub_4025B0 에서 반환한 파일명입니다.

이후 OpenSCManagerA + OpenServiceA 함수를 이용해 서비스를 실행합니다.

 

만약 서비스가 제대로 Open 되었다면,

ChangeServiceConfigA 를 실행하고 서비스 핸들을 닫거나 or 그냥 닫아버립니다.

dwServiceType과 dwErrorControl 인자값이 0xFFFFFFFF 이어서 어떤 의미인지 헤메다가

MSDN의 "Changing a Service's Configuration" 예제를 발견하여 참고했습니다.

URL : https://msdn.microsoft.com/ko-kr/library/windows/desktop/ms681987(v=vs.85).aspx 

 

서비스가 정상적으로 Open 되지 않았을 경우,

서비스명 "Lab09-01", 서비스 표시 이름 "Lab09-01 Manager Service" 으로 서비스를 생성합니다.

6번째 인자를 통해서 SCManager 가 Setup 될 때 자동으로 시작되는 서비스임을 알 수 있습니다.

들어가는 인자들은 Xref로 어셈블리 코드를 추적한 다음 Symbolic Constant로 변경하면 편리합니다.

 

이후에 ExpandEnvironmentStringA 함수를 호출하여 Src변수에 문자열로 저장해 두었던

환경변수 %SYSTEMROOT%을 확장한 문자열을 얻습니다. 이러한 절차는 설치되는 PC마다 환경변수가 다르기 때문에

오류를 최소화하기 위한 작업으로 보입니다. 다음에 또 해당 코드와 마주친다면 의도파악이나 분석 속도가 빨라지겠죠.

그아래에서는 파일을 다른 경로를 복사하기 위해 GetModuleFIleName 함수와 CopyFile 함수를 호출합니다.

이후에 sub_4015B0 함수 결과에 따라 sub_401070 함수의 호출 여부가 결정됩니다.

 

sub_4015B0 서브루틴을 살펴보면,

GetSystemDirectory + strcat 함수를 이용하여 아래와 같은 문자열을 만듭니다.

이후 sub_4014E0 함수의 결과가 0이 아니면 result로 반환합니다.

 

sub_4014E0 서브루틴을 살펴보면,

인자로 넘어온 2개의 경로를 이용하여 파일을 2개 생성합니다.

① a1 : C:\Windows\system32\Lab09-01.exe (GENERIC_WRITE)

② lpFileName : C:\Windows\system32\kernel32.dll (GENERIC_READ)

CreateFile 함수의 인자값들은 OllyDbg에서 파악하기 쉽습니다.

 

여기서 염두해둘 부분은 kernel32.dll 파일은 이미 존재할 것이므로, OPEN_EXISTING 옵션에 의하여

핸들값을 가져오고 난 이후에 system32 폴더에 생성된 Lab09-01.exe의 타임설정을 kernel32.dll과 동일하게 설정하는 점입니다.

만들어진 Lab09-01.exe의 등록정보를 살펴보면 제가 캡쳐를 위해 접근한 시간인 액세스 날짜를 제외하고

나머지 일자가 동일함을 알 수 있습니다.

sub_4014E0 정리 : 함수에 넘어온 인자를 이용하여 "Lab09-01.exe" 파일 생성, "kernel32.dll" 파일과 동일하게 타임 설정

sub_4015B0 정리 : "시스템 디렉토리 경로 + kernel32.dll" 문자열 생성, sub_4014E0 호출 

 

 

다시 sub_402600으로 돌아가서, sub_401070을 살펴보겠습니다.

함수에 "ups" "http://...." "80" "60" 인자들을 넣어주고 sub_401070 서브루틴을 호출합니다.

 

아마 이부분이 가장 궁금했던 "SOFTWARE\\Microsoft \\XPS" 레지스트리에

어떤 내용이 저장되길래 자꾸 체크하는지 알 수 있는 부분입니다.

함수 호출시 전달된 인자들을 주소값 연산을 통해 &Data 변수에 주르륵 할당합니다.

 

이후 RegCreateKeyExA 함수를 이용하여 "SOFTWARE\\Microsoft \\XPS" 서브키로 레지스트리를 생성하고

RegSetValueExA 함수를 이용하여 "Configuration" 항목값을 Data 변수에 있는 내용으로 채웁니다.

 

regedit을 이용하여 생성된 레지스트리르 체크해보면,

MicroSoft 문자열 뒤에 스페이스가 추가된 서브키가 추가로 생성되어

사용자가 악성코드로 인해 생성된 레지스트리인지 쉽게 인지할 수 없게 되어있습니다.

 

이것으로 길고 길었던 "in" 옵션에 대한 분석이 끝났습니다.

이후의 옵션들은 위에서 분석한 함수들의 내용이 중첩되는 부분이 많아서 좀더 수월할것으로 보입니다.

sub_401070 정리 : 레지스트리 생성 및 등록 

"in" 커맨드라인 옵션 정리 : 시스템 디렉토리에 악성코드 복사, 서비스 생성, 레지스트리 생성 

 

 

② "-re"

일치 O : 인자가 3개일 경우 sub_4025B0, sub_402900 호출, 4개인 경우 sub_202600 호출, 그외 sub_402410(삭제+종료) 호출

일치 X : IF문 내부로 진입 (_mbscmo : 문자열이 동일한 경우 0 반환)

 

인자가 3개 혹은 4개일 경우, "in" 옵션과 동일하게 sub_4025B0(파일명반환) 서브루틴을 호출합니다.

이후 sub_402900 서브루틴을 호출합니다. 해당 함수를 분석합니다.

 

가장 먼저 눈에 들어오는 부분이 OpenSCManagerA + OpenServiceA 입니다.

해당 함수들이 눈에 익어서 어디서 호출했는지 Xref로 확인해보면 sub_402600 서브루틴입니다.

sub_402600 서브루틴에서는 새로운 서비스를 생성하는데 초점이 맞춰져 있었습니다.

 

하지만 분석중인 sub_402900 서브루틴에서는 DeleteService + DeleteFileA 함수를 통해

악성코드가 생성한 서비스와 파일을 삭제를 수행합니다. 해당 명령어는 "remove"를 의미하는것 같네요.

 

sub_402600 서브루틴의 분석이 선행되어서 그런지 "re" 옵션에 대한 분석이 빨리 끝났습니다.

"re" 커맨드라인 옵션 정리 : 서비스 삭제, 시스템 디렉토리에 복사한 악성코드 삭제 

 

 

③ "-c"

일치 O : 인자가 7개인 경우 sub_401070 호출, 아닌 경우 sub_402410(삭제+종료) 호출

일치 X : IF문 내부로 진입 (_mbscmo : 문자열이 동일한 경우 0 반환

 

해당 명령어는 인자가 7개가 아니면 바로 삭제를 수행합니다.

위에서 이미 분석한 sub_401070(레지스트리 생성 및 등록) 서브루틴을 호출합니다.

해당 함수에 인자가 모두 argv인것으로 보아 레지스트리 값만 직접 등록하고 싶을때 사용하는 명령어입니다.

아마도 최초에는 "in" 명령어를 이용하여 설치할것이고, 향후 변동사항이 있을때 사용할것으로 예상됩니다.

"c" 명령어는 "configuration" 정도가 어울릴까요?

"c" 커맨드라인 옵션 정리 : 인자값을 이용한 레지스트리 직접 생성 및 등록 

 

④ "-cc"

일치 O : 인자가 3개가 아닌경우 sub_402410(삭제+종료) 호출, 그외 sub_401280, sub_402E7E 호출

일치 X : sub_402410(삭제+종료) 호출

 

"cc" 커맨드 옵션은 간결합니다.

sub_401280 서브루틴을 호출하여 레지스트리의 데이터를 얻어옵니다.

이후 sub_402E7E 서브루틴을 호출하는것을 볼 수 있는데, 

해당 함수 내부의 _stbuf _ftbuf 함수는 printf 내부 동작에 사용되는 함수이기도 합니다.

함수 호출시 전달된 인자 "k:%s h:%s p:%s per:%s\n" 의 형태가 화면에 해당값들을 출력하기 위한

용도로 보이기 때문에 직접 실행해봅니다.

"cc" 커맨드라인 옵션 정리 : 현재 레지스트리에 등록되어 있는 Config 값 출력 

 

 

 

------------------------------------------------------------------------------------------------------------------------------------------

0x02 문제풀이

------------------------------------------------------------------------------------------------------------------------------------------

1. 어떻게 하면 악성코드가 자신을 설치하게 할 수 있는가?

  옵션 "-in" 으로 패스워드를 입력해서 프로그램 자체를 초기설치할 수 있습니다.

  OllyDbg를 이용하여 패스워드 검증 부분을 뛰어넘을수도 있습니다.

 

2. 이 프로그램의 커맨드라인 옵션은 무엇인가? 패스워드 요건은 무엇인가?

   "-in" "-re" "-c" "-cc" 4가지 옵션이 있으며, 패스워드는 "abcd" 입니다.

   각 옵션의 기능과 패스워드를 구하는 방법은 위쪽의 분석을 참고하세요.

 

3. 이 악성코드가 특수 커맨드라인 패스워드를 요구하지 않게 영구 패치하려면 OllyDbg로 어떻게 해야 하는가?

   교재에서는 0x402510 주소에서 항상 참을 반환하게 함수의 첫번째 바이트를 변경하라고 되어 있지만

   코드를 패치할 수 있는 포인트는 더 다양할 것 같습니다.

 

4. 이 악성코드의 호스트 기반 지표(indicator)는 무엇인가?

   레지스트리키 생성, "파일명 Manager Service" 생성,

   원격 명령어 프로토콜을 이용한 백도어 기능, 파일 생성/복사 등이 있습니다.

 

5. 이 악성코드가 네트워크를 통해 수행할 수 있는 다른 행동은 무엇인가?

  악성코드는 네트워크를 통해 "SLEEP", "UPLOAD", "DOWNLOAD", "CMD", "NOTHING" 다섯 명령어 중

  하나를 실행하게 명령어를 받습니다.

 

6. 이 악성코드에 대한 유용한 네트워크 기반 시그니처가 있는가?

  기본적으로 악성코드는 http://...(중략)...analysis.com URL으로 통신하지만, 변경가능합니다.

  비컨은 xxxx/xxxx.xxx 형태로 HTTP/1.0 GET 리소스 요청을 합니다.

  여기서 x는 무작위로 구성한 문자와 숫자 ASCII입니다.

 

 

-----------------------------------------------------------------------------------------------------------------

0x03 마무리

-----------------------------------------------------------------------------------------------------------------

이번 포스팅은 IDA 소스코드만 보고 분석하기에도 정신없을 정도로 분량이 많았습니다. 

그러다보니 값을 검증하기 위해 올린 Ollydbg 화면 외에는 어셈블리어를 거의 살펴보지 못한 것 같습니다.

모르는게 많아서 포스팅 속도가 느리네요.

부족한글 봐주셔서 감사합니다.

 

 

posted By Message.

Commit your way to the LORD, trust in him and he will do this. [PSALms 37:5]

 

posted by Red_Message