SEH : Structured Exception Handling의
약자로, Windows를 위한 네이티브 예외 처리 메커니즘이다. 각
스레드마다 독립적으로 설치되고 처리된다.
__try, __except, __finally 구문을 사용하여 SEH를 설치하고 해제할 수 있다. __try{} 선언은 컴파일러에서
정의된 EH_prolog 함수를 호출하는데, 이 함수는 스택에
_EXCEPTION_REGISTRATION_RECORD를 할당하고 SEH 링크드 리스트의 헤드에 레코드를 추가한다. (출처)
[SEH 설치 디스어셈블 모습]
어셈블리 코드를 확인해 보면 익셉션 핸들러 함수 주소를 먼저 push하고, FS:[0x00]에 접근하여 그 값을 Push하는 것을 볼 수 있다.
_EXCEPTION_REGISTRATION_RECORD는 다음과 같은 구조를 갖고 있다.
Stack은 높은 주소에서 낮은 주소로 할당되므로 Handler 함수를 먼저 push하였고, 두번째로 push한 값은 Next
값이 되는데 이는 이 레코드에서 처리하지 못했을 경우 처리될 다음 _EXCEPTION_REGISTRATION_RECORD의
주소이다. 위에서 언급한FS:[0x00]에는 TIB(Thread Information Block) 또는 TEB(Thread
Environment Block)이라고 불리는 구조체가 존재한다.
그림과 같이 FS[0x00]에 할당된 TIB는 첫번째 인자로 현재 SEH 프레임의 주소, 즉 링크드 리스트의 헤더 주소를 갖고 있다.
[SEH Chain]
_EXCEPTION_REGISTRATION_RECORD는 __try 문의 중첩 정도에 따라 그림과 같은 링크드 리스트 구조를 갖고 스택에 할당된다. 먼저 pExceptionHandler 함수가 호출되고, 해당 핸들러에서 익셉션을 처리하지 못할 경우 pNextSEHRecord를
참조하여 다음 핸들러를 호출한다. 이를 계속 반복하다가 pNextSEHRecord에
0xFFFFFFFF가 할당되어 있는 디폴트 핸들러인 UnhandledExceptionHandler까지
도달할 경우 해당 익셉션을 커널로 넘겨 프로세스를 재개하거나 종료시킨다.
[SEH in Stack]
함수가
Call 될 때 자동으로 스택에 저장되는 ret과 함수 프롤로그에서
자동으로 생성되는 SFP 이후에 함수 내부에서 __try 구문을
사용했다는 가정 하에 스택 프레임에는 위와 같은 구조로 _EXCEPTION_REGISTRATION_RECORD가
쌓일 것이다. Buffer에 원하는 만큼 데이터를 집어넣어서 BOF를
일으킬 수 있다면 가장 쉬운 공격 방법은 역시 ret을 덮어씌워서 공격하는 것이지만, 이는 보통 Stack cookie 또는 Stack canary라고 불리는 보호 기법에 의해 공격이 여의치 않다.
[Stack Canary]
Stack
Canary는 고전적인 BOF 방지 기법 중 하나로, ret과
SFP 뒤에 랜덤한 값을 생성하여 설정한 후 함수가 끝날 때 검사하여 값이 바뀌었을 경우 탐지하고 강제로
종료시켜버리는 보호 기법이다. 이런 상황에서는 Canary를
덮어쓰고도 공격이 가능하게 하기 위해 SEH Overwrite를 이용할 수 있다.
[Basic SEH Overwrite]
가장
간단하게 SEH Overwrite를 사용하는 방법은 위와 같다. pExceptionHandler를
덮어쓰기 위해 Canary 값을 변경했으므로 함수 에필로그 직전에 Canary를
검사하는 루틴을 지나가는 순간 프로그램이 종료되어 버린다. 따라서 SEH
Overwrite가 제대로 기능하도록 하기 위해서 Canary 검사가 진행되기 전에 다른
Exception이 발생하도록 만들어야 한다. 이 방법은
여러 가지가 있지만 대표적으로는 위 그림처럼 파라미터로 넘어온 포인터까지 Dummy 값으로 덮어씌우고
이를 사용했을 때 Exception이 발생하도록 할 수 있겠다.
위처럼
단순하게 스택에서 _EXCEPTION_REGISTRATION_RECORD의 pExceptionHandler를 Shellcode의 주소로 덮어쓰는
방법을 쓸 수 있으면 좋겠지만, 이 방법은 Microsoft에서SafeSEH라는 보호 기법을 도입하면서 불가능하게 되었다. 이
보호 기법은 pExceptionHandler 내부에 스택 주소가 들어가거나, MS에서 핸들러로 등록한 주소가 아닌데 kernel32.dll 등
MS에서 지정한 모듈 주소가 들어갈 경우 실행되지 않도록 한다. 따라서
이를 우회하기 위해서는 다른 방식을 사용해야 한다.
위의 함수 정의부는 pExceptionHandler 함수의 프로토타입이다. SEH 처리가
시작되면 pExceptionHandler 주소를 위의 인자들과 함께 Call하게
되는 것인데, 여기서 중요한 부분은 두번째 인자인 EstablisherFrame이다. EstablisherFrame은 이 pExceptionHandler 함수를
호출한 _EXCEPTION_REGISTRATION_RECORD 구조체의 주소를 갖게 된다. 즉, 익셉션 발생 후 SEH 처리가
정상적으로 실행될 경우 다음과 같은 스택 상황이 만들어진다.
[정상적으로 실행된 SEH]
여기서
주의 깊게 봐야 할 부분은 바로 ESP+8에 위치한 EstablisherFrame값이 우리가 직접 변조 가능한pNextSEHRecord를 가리키고 있다는 점이다. 만약 pExceptionHandler를 변조하여 Pop-Pop-Ret가젯의 주소로 바꿔놓는다면 위 스택에서 ret이
먼저 Pop되고, ExceptionRecord가 Pop되고, EstablisherFrame이 ret되면서 자연스럽게 EIP가 스택의 pNextSEHRecord를 가리킬 것이다.
[변조된 SEH]
위에서 적은 대로 pExceptionHandler를 &ppr로 덮어씌우고 추가적인 작업을 더 해서 Exploit을
완성한 모습이다. ppr 가젯에 의해 EIP가 &pNextSEHRecord를 가리키게 되면서 최대 4바이트의
임의의 OPCode를 실행시킬 수 있다. 여기서 사용할 수
있는 가장 간단하면서 강력한 OPCode는 short relative-jmp로, EB XX의 2바이트짜리 점프이다.
이 명령은 [현재 주소] + [Second Byte Value(XX)] + 2주소로 점프하게 된다. 여기서 +2가 붙은 이유는 이 명령 자체의 크기가 2이기 때문이다. 예를 들어 0x00120100이라는 스택 주소에 EB 9A 90 90 이라는 OPCode가 들어갈 경우 0x9A는 signed int8 형에서 -102와 같으므로 -102 + 2 하여 현재 EIP에서 -100의 주소로 점프한다. 이를 잘 이용하면 위의 버퍼에 미리 입력해 둔 NOP Sled +
Shellcode로 점프하는 것이 가능하다.
PUBG Ransomware는2018년 4월 10일쯤
발견된 랜섬웨어로, 기존에 장난삼아 제작되고 배포되어 이슈가 되었던 동방프로젝트 랜섬웨어(http://thegear.co.kr/14295)와
비슷한 느낌으로 제작된 랜섬웨어입니다. 장난삼아 만들었다고는 하지만 실제로 파일에 대한 암호화를 진행하기
때문에 자칫하면 실제 피해가 발생할 수 있어 주의가 필요합니다. 무척 간단히 분석이 가능할 거라고 예상이
가능했기 때문에 한번 분석해봤습니다.
이번에도 별다른 아이콘은 없고, 또 .NET 기반의 Win32
PE 파일입니다. 파일 크기는 40.5KB로
상당히 작습니다.
[그림 1. PUBG_Ransomware.exe
Exeinfo PE]
Exeinfo PE를 이용하여 분석해본 결과
패킹은 되어 있지 않은 것 같았습니다. 그럼 원본 바이너리 자체 크기가 40KB밖에 되지 않았다는 것인데 랜섬웨어 치고는 너무 작아서 꽤 놀랐습니다.
[그림 2. PUBG_Ransomware.exe
Bintext 분석]
패킹이 전혀 되어 있지 않기 때문에 내부
문자열을 찾아보면 재미있는 문자열이 무척 많이 보이지만, pdb 정보가 박혀 있길래 이를 가져왔습니다. 경로는 "C:\Users\ryank\source\repos\PUBG_Ransomware\PUBG_Ransomware\obj\Debug\PUBG_Ransomware.pdb”
로 지정되어 있으며 유저 이름이 ‘ryank’
인 것으로 보아 제작자는 ‘Ryan K’ 라는 닉네임을 사용하는 사람인 것으로 추정이 가능합니다.
[그림 3. PUBG_Ransomware.exe
dotpeek 디컴파일]
Jetbrain사의 Dotpeek을
이용하여 분석해본 결과 실행 직후 바탕 화면 아래의 특정 파일들을 암호화하도록 되어있는 것을 볼 수 있습니다. 여기서
함수명이 익숙하지 않아 확인해보니 스페인어인 것을 알 수 있었고, 이 랜섬웨어의 제작자는 스페인 국적의 인물인 것으로 추정해볼 수 있었습니다.
암호화를 진행하는 확장자들은 위와 같았습니다. 암호화가
완료된 파일은 뒤에 ‘.PUBG’ 확장자를 추가로 붙입니다.
[그림 4. 암호화
루틴]
AES 256 CBC 암호 방식을 사용하며 Zeros 방식의 패딩을 사용합니다. 이 패딩 방식은 모든 바이트를
0으로 채워 패딩하는 방식입니다. 인자로 넘어가는 문자열을
확인해 보니 항상 Key가 되는 문자열은 “GBUPRansomware”로
동일했습니다.
[그림 5. 프로세스
체크]
메인 Form이
로드됨과 동시에 시작된 루틴에서는 특정 이름을 가진 프로세스를 가져와서 존재할 경우 playtime을
늘리고, 이것이 3이 되는 순간 복호화 루틴으로 들어갑니다. 해당 프로세스 이름은 “TslGame”으로 게임 배틀그라운드의 프로세스
이름입니다. 타이머의 Interval을 1000으로 설정한 후 3번이므로 3초간만
저 프로세스를 켜두면 자동으로 복호화가 되는 로직입니다. 여기서 맹점이 하나 있는 것이, 프로세스 이름으로 체크하는 것이기 때문에 적당히 아무 프로그램이나 파일명을 TslGame.exe로
바꾼 후 실행시키면 알아서 복호화를 해줍니다.
[그림 6. 리소스
내부 그림]
리소스 내에는 Form의 배경에 지정할 이미지 파일 하나만 들어있었습니다. 이외에
딱히 레지스트리를 건드리는 부분도 없었고, 정적 분석이 더 필요한 부분도 없어서 바로 동적 분석으로
들어갔습니다.
바탕
화면 밑의 파일들만 암호화하기 때문에 바탕 화면에 실행 파일과 암호화될 파일들을 함께 둔 후 동적 분석을 시작했습니다.
[그림 8. 랜섬노트(?) 및
암호화된 파일들]
바탕 화면 아래의 파일들만 암호화하는 단순함
때문인지 실행 직후 바로 파일들이 암호화됩니다. Form에는 배틀그라운드 게임을 1시간동안 플레이하거나, ‘s2acxx56a2sae5fjh5k2gb5s2e’라는
키 값을 주면서 이것으로 복구하라고 씌여 있습니다. 하지만 Restore
Code를 입력하는 Input Box와 Restore 버튼은
Disabled 상태라 키 입력이 불가능합니다.
[그림 9. 복호화된 파일들]
정적
분석으로 알아낸 것처럼, 프로세스 이름을 기반으로 게임이 실행 중인지 여부를 판단하기 때문에 적당히
아무 프로그램이나 이름을 ‘TslGame.exe’로 변경 후 실행시키면 3초 뒤 자동으로 복호화를 완료해줍니다. Hex Editor로 열어
봤을 때 정상적으로 복호화된 것을 확인할 수 있었습니다. 이후 랜섬웨어는 자동으로 종료됩니다.
PUBG
Ransomware는 분류는 랜섬웨어가 되겠지만 금전적인 요구는 전혀 하지 않고 사실상 거의 대가 없이 모든 파일을 복호화 해 주는
등 악의가 느껴지지 않는 프로그램입니다. 이전에 장난삼아 개발되었던 다른 ‘게임 플레이 시간에 따라 복호화를 해 주는 랜섬웨어’들과 비슷한 느낌으로
개발된 것으로 보입니다. 악의가 느껴지지 않는다고는 하나 실제로 유저의 파일들을 암호화하여 피해를 줄
수 있는 것은 사실이기 때문에 수많은 안티바이러스 벤더사에서 악성으로 탐지하고 있습니다.
패킹도
프로텍팅도 전혀 되어 있지 않고 .NET 기반 프로그램인데다 난독화 처리도 되어 있지 않아서 그냥 정적
문자열 패턴 기반으로도 충분히 탐지가 가능하며, 바탕화면의 특정 파일들을 암호화하는 행위 자체도 무척
뚜렷합니다. 하지만 랜섬노트를 따로 드랍하거나 보여주는 행위가 없어 자동 분석은 약간 힘들 수도 있겠습니다. 네트워크 행위도 전혀 하지 않고, 단순히 암호화 및 복호화 행위만
수행합니다.
패킹
및 프로텍팅이 되어 있지 않다는 점을 보면 분석에 익숙하지 않은 분들이나 간단히 분석할 수 있는 .NET 기반
악성코드를 찾아보고 있는 사람들에게는 좋은 샘플일 것 같습니다.
그림 3을 확인해 보면 “mssecsvc2.0”이라는 서비스를 열어서 특정
함수의 인자로 넘기고, ServiceStartTable의 lpServiceProc에
다른 함수를 등록한 뒤 StartServiceCtrlDispatcherA함수를 호출하여 해당 함수를
실행시키는 것을 볼 수 있습니다.
[그림 4. SERVICE_CONFIG_FAILURE_ACTION
설정]
위에서
넘어간 특정 함수는 서비스가 특정 상황일 때 어떤 동작을 할 지를 설정하는 부분입니다. 여기에는 ChangeServiceConfig2라는 API가 사용되는데, 이 API의 세번째 인자는 두번째 인자에 따라 달라지며, 현재 그림 5에서 보이는 2는
서비스가 SERVICE_CONFIG_FAILURE_ACTIONS, 즉 서비스가 실행 도중 실패할 때를
나타냅니다.
[그림 5. SERVICE_FAILURE_ACTIONS
구조체]
세번째
인자인 SERVICE_FAILURE_ACTIONS 구조체의 lpsaActions
멤버를 보면 특정 상황일 때 어떤 Action을 취할 지 설정할 수 있습니다. 그림 4에서 scAction.Type에 1을 지정하는 것은 SC_ACTION_RESTART이므로 서비스를
재시작하는 것이라고 볼 수 있으며, 1000 * delaySec으로 실행 딜레이를 지정하는데 60이 인자로 넘어왔으므로 서비스가 실행 도중 실패할 경우 60초 후
서비스를 재시작하도록 설정하는 것이라고 볼 수 있습니다.
[그림 6. SMB
Exploit Thread]
그림
3에서 StartServiceCtrlDispatcher 함수에
넘기는 구조체에 설정된 disseminateServiceProc 함수 내부를 확인해 보면 위와 같은
부분이 있습니다. 내부에서 랜덤하게 IP Address를
생성하여 SMB Exploit을 시도하기 때문에 위와 같이 함수를 네이밍 해두었습니다.
[그림 7. SMB
Exploit Function]
내부에선 랜덤하게 IP Address를 생성하여 SMB 포트인 445포트로 접속을 시도한 후, 접속에 성공하면 해당 IP의 마지막 1바이트를 1~254까지
바꿔가며 접속 및 Exploit을 시도합니다. SMB 익스플로잇의
경우 EternalBlue 라고 명명된 CVE-2017-0144를
사용하는데 이는 따로 분석하지 않겠습니다. 이 방식으로 수많은 취약한 호스트를 자동으로 공격하고 전파되는
방식을 사용했기 때문에 첫 등장 이후 큰 이슈가 되었습니다.
인자가
존재하지 않을 경우 “mssecsvc2.0” 서비스를 생성하고 실행합니다. 어디서 많이 본 서비스인데요, 위에 인자가 존재할 경우에 실행된
루틴에서 핸들을 가져와 SERVICE_FAILURE_ACTIONS를 정의한 그 서비스입니다. CreateService 함수의 인자인 Binary Path에는
“argv[0] –m security” 형식이 되어 인자가 들어간 상태로 실행되도록 설정되어 있습니다. 이러면 자동으로 새로 생성된 서비스가 직접 스스로에게 SERVICE_FAILURE_ACTIONS를
정의하고 SMB Exploit을 하는 Thread를 만들어서
계속 실행될 것입니다.
[그림
9. 리소스 드랍 및 실행]
서비스
생성을 마친 후 “C:\Windows\tasksche.exe” 파일을 “C:\Windows\qeriuwjhrf” 파일로 옮긴 후, 리소스
영역에서 PE 파일을 가져와 “C:\Windows\tasksche.exe”에
드랍합니다. 이후 CreateProcess를 이용하여 실행합니다. 이 파일은 tasksche.exe 라고 부르겠습니다. 분석해본 결과 실제 파일을 암호화하고 랜섬노트를 드랍하는 랜섬웨어의 행위는 여기서 드랍된 tasksche.exe에서 실행되었습니다.
WannaCry.exe의
리소스 영역에서 그대로 드랍된 파일입니다. 파일 크기가 거의 변화가 없는 것으로 보아 WannaCry.exe의 거의 대부분은 이 파일이 차지하고 있었던 것으로 보입니다. 이 파일을 드랍한 후 WannaCry.exe의 나머지 부분은 서비스를
만들어서 특정 Host에 EternalBlue취약점을 이용하여
SMB Exploit으로 전파되는 행위를 수행했습니다. 이
파일에서는 실제로 랜섬웨어의 행위를 진행합니다.
[그림
10. 자가복제 작업]
tasksche.exe 파일은 실행되고 나면 argv[1]을 검사하여 “/i”와 일치하는지 확인합니다. Anti-Sandbox 기법인지는 모르겠으나 이 값이 없을 경우 아무것도 하지 않고 종료됩니다.
[그림
11. 랜덤 문자열 생성 루틴]
SetRandomString_by_PCName이라고
이름 붙인 함수 내부에서는 PC 이름에 따라 8~15글자의
소문자 알파벳 + 3글자의 대문자 알파벳으로 이루어진 문자열을 생성하여 파라미터로 전달받은 포인터에
저장합니다. 이 값은 특정 루틴 내에서 PC 이름을 이용해
seed를 생성하고, 이를 이용해 srand한 후 생성한 문자열이므로 PC 이름이 같을 경우 다른 환경에서도
똑 같은 값을 뽑아낼 것입니다. 이후 그림 10의 루틴을
계속 타면서 “C:\Windows\ProgramData\<RANDOM>”,
“C:\Windows\Intel\<RANDOM>”, “C:\Windows\<RANDOM>”,
“%TEMP%..\<RANDOM>” 경로에 순차적으로 시도해가면서 디렉토리를 생성하고 CurrentDirecotry를
설정합니다. 그 다음 CopyFile API를 이용해 자가복제하고
복제한 파일을 서비스 또는 프로세스를 이용하여 새 프로세스로 생성하고, 다시 원래 디렉토리로 돌아옵니다. 그리고 현재 실행되고 있는 디렉토리를 “HKEY_CURRENT_USER\Software\WannaCrypt0r”,
“HKEY_LOCAL_MACHINE\Software\WannaCrypt0r”에 등록합니다.
[그림
12. 리소스 unzip]
unzipResource라고 이름붙인 함수 내에서는 리소스 파일을 하나
가져와서 unzip을 수행합니다. 인자로 넘어간 s_WNcry_2o17의 값은 “WNcry@2ol7”로, 압축파일의 비밀번호에 해당했습니다. 해당 리소스를 덤프하여 확인해
보면 확실히 zip 압축 파일이었고, 압축을 해제하면 여러
파일들이 드랍됩니다.
[그림
13. 리소스 내부 파일들]
[그림
14. msg 내부]
msg 폴더 내부에는 PC 언어
환경에 따라 다른 메시지를 보여줄 수 있도록 다양한 언어로 랜섬노트가 작성되어 있습니다.
[그림
15. b.wnry]
b.wnry 파일은 BMP 포맷
이미지 파일로, 파일 암호화가 완료된 뒤 이것으로 바탕 화면을 변경할 것으로 보입니다.
[그림
16. c.wnry]
c.wnry 파일은 Tor브라우저
관련 설정이 들어 있는 파일로, 도메인과 토르 브라우저를 다운받을 수 있는 URL이 적혀 있었습니다.
[그림
17. r.wnry]
r.wnry 파일은 랜섬노트 텍스트 파일이었습니다.
[그림
18. s.wnry]
s.wnry 파일은 zip 포맷
압축 파일로, 내부에는 토르 브라우저 관련 파일들이 들어 있었습니다.
[그림 19. 랜섬 행위 시작]
현재 디렉토리를 숨김 설정하고 모든 사용자에게 폴더 접근 권한 준 후 t.wnry 파일을 읽고 랜섬 행위를 시작합니다. t.wnry 파일은
리소스에도 존재하지 않았고 어디서 드랍되는지 찾을 수가 없어 분석이 불가능했습니다. 핵심 부분은 그림
19의 p_CustomFuncPointer에 접근해야 알
수 있는데, 이는 t.wnry 파일에서 가져온 버퍼이기 때문에
내용을 알 수 없었으며 분석하는 도중 깨달은 것이지만 정상적으로 실행되지도 않기 때문에 동적으로 분석하는 것도 불가능했습니다. 마지막으로 문자열 내부를 확인하여 랜섬행위를 진행하는 확장자명을 뽑아 보았는데, 이는 다음과 같습니다.
API Monitor를 이용하여 동작을 확인해 보니 정적 분석에서 확인한
것처럼 “mssecsvc2.0”으로 서비스를 만들고,
“C:\Windows\tasksche.exe”에 리소스 드랍 후 CreateProcess로
실행시키지만 드랍한 파일이 정상이 아닌지 제대로 시작되지 않는 것을 볼 수 있습니다. 이 문제로 실제
암호화 행위를 진행하는 부분은 분석이 불가능한 상태에 있습니다.
[그림
21. WannaCry.exe 동적 분석 - 2]
서비스로 등록되어 실행된 루틴을 보니 수많은 쓰레드들이 계속해서 생성되면서
무언가 작업을 진행하고 있었습니다.
[그림
22. 등록된 mssecsvc2.0 서비스]
mssecsvc2.0 이름으로 등록하는 루틴이 존재했던 것과 같이 서비스가
등록되어 실행되고 있었습니다.
[그림
23. WannaCry.exe 네트워크 분석]
네트워크로는 수많은 호스트의 445포트로
연결을 시도하고, SMB 익스플로잇을 시도하는 내용이 보입니다. 드랍했던
파일인 tasksche.exe도 동적으로 분석하고 싶었지만 위에 언급한 대로 정상적인 실행이 되지 않았기
때문에 분석이 불가능했습니다.
WannaCry.exe는 2017년
5월경 등장하여 SMB 취약점을 이용한 강력한 자가 전파
능력으로 전 세계에 큰 충격을 안겨줬던 랜섬웨어입니다. 처음에 연결을 시도한 도메인은 킬
스위치였던 모양입니다. 어떤 방식으로 접속에 성공할 경우 전파되지 않는지는 잘 모르겠지만, 기사는 이렇게 나와 있습니다. ( http://www.itworld.co.kr/news/104756
)
분석을 시작하기 전까진 몰랐지만 패킹이나 프로텍팅이 전혀 되어 있지 않은
깔끔한 바이너리였고, 따라서 정적으로 분석하는 데에 큰 무리는 없었습니다. 그냥 문자열 패턴만으로도 내부에 “*.wnry”와 여러 확장자명들, 킬 스위치로 사용된 도메인 등이 들어 있기 때문에 정적으로도 무리 없이 탐지가 가능할 것으로 보입니다. tasksche.exe 내부 리소스로 zip파일에 암호를 걸어서
넣어둔 것은 데이터를 자동 분석으로 뽑아내기는 어렵게 만들었지만 zip 파일 구조 특성상 파일, 디렉토리명 등은 전혀 암호화가 되지 않기 때문에 정적으로 탐지하는 데에 전혀 지장을 주지 않았습니다. 또한 동적 분석으로도 특정 이름의 서비스를 등록하거나, 파일을 드랍하거나, 특정 포트로의 수많은 접근을 시도하는 등 뚜렷한 행위가 나타났기 때문에 탐지에 큰 어려움이 없을 것으로 보입니다. 탐지 가능한 지표에는 대표적으로 “C:\Windows\tasksche.exe” 파일을 생성하고, “mssecsvc2.0” 서비스를 등록하거나, “*.wnry” 확장자의 파일을
읽고 쓰는 행위 등이 있겠습니다.
요약하면, 이 샘플은 취약점을
Exploit하여 네트워크를 통해 자동으로 전파되는 기능을 가진 랜섬웨어입니다. 정상 파일로 위장하기 위해 “mssecsvc2.0”이라는 이름의 서비스를 생성하고 실패시 자동으로 다시 시작하는 루틴을 만들고, 여러 프로세스와 파일을 넘나들며 실행되는 것을 보면 상당히 정교하게 만들어져 있습니다. 하지만 그럼에도 패킹이나 프로텍팅이 전혀 되어있지 않아 정적 분석에 어려움이 별로 없었다는 점이 특징이며, 리소스에서 드랍된 tasksche.exe는 실제 VirusTotal등에서 수많은 벤더가 악성으로 진단하고 있는 상황임에도 정상 동작하지 않았습니다. 정적 시그니쳐는 물론이고 패턴, 문자열 등으로 충분히 악성 판단이
가능하고, 동적으로도 서비스 등록 및 네트워크를 통한 Exploit 등
특징이 두드러지기 때문에 쉽게 악성 판단이 가능한 샘플인 것으로 보입니다.
IDA를 이용하여 정적 분석을 하다 보면 악성코드가 직접 만든 custom struct들을 자주 만나게 됩니다. 특히 이번에 분석했던
WannaCry의 경우는 그게 더 심했는데요, 1000바이트가
넘어가는 크기의 struct를 선언하고 사용하는 경우가 있어 이를 직접 만들긴 힘들어 보여 idapython을 사용하였습니다. idapython은 IDA와 연동하여 사용할 수 있는 일종의 Python 라이브러리인데, IDA를 켰을 때 아래에 있는 커맨드라인 입력 창에서 인터프리트 형식으로 사용이 가능합니다. 예를 들어, 크기가 상당히 큰 Struct이고
멤버 변수를 모두 4바이트 DWORD로 선언하고 싶은 경우
다음과 같이 사용이 가능합니다.
[그림
24. idapython 사용]
IDA 에서 Python 스크립트를
이용하는 것은 2~3년쯤 전 BoB를 하면서 배웠었지만 지금까지
실감하지 못하고 있었는데 상당히 강력한 도구인 만큼 잘 활용하면 분석에 큰 도움이 될 것으로 보입니다.
게임 핵으로 위장한 악성코드를 분석해보자는
생각이 들어서 초록 검색창에 ‘배틀그라운드 핵 다운’ 이라고
검색하여 딱 봐도 수상해 보이는 블로그 포스트에서 수상해 보이는 압축 파일을 다운받았습니다. 비밀번호까지
걸려있는 걸로 봐선 악성코드임이 거의 확실해 보여서 분석에 들어갔습니다.
[그림 2. 압축파일
내부]
내부에는 EXE로
보이는 파일 하나 뿐이고, 압축파일의 비밀번호는 블로그에 적힌 대로 ‘PUBGH‘
였습니다. 하나밖에 없으니 대상을 고를 것도 없이 바로 분석에 들어갑니다.
Exeinfo PE라는 툴을 이용하여 분석
대상을 조사해본 결과 Safeengine
Shielden 2.3.9.0버전으로 패킹되어 있는 것을 확인할 수 있었습니다. 실제 IDA로 열어 보면 라이브러리나 함수 섹션보다 파싱하지 못한
데이터 영역이 매우 크고, EntryPoint 지점의 함수가 xor 연산으로
끝나는 것을 확인할 수 있습니다. 내부에서 언패킹 후 실제 PE파일을
실행시키는 부분이 있는 것으로 예측이 가능합니다. 패킹된 상태로 정적 분석은 불가능에 가까우므로 PE Tools를 이용하여 언패킹된 바이너리를 덤프합니다.
[그림 4. PUBG H.exe
PE Tools Dump]
PE Tools를 실행한 후 대상 파일을
실행시키고, 생성된 프로세스를 그대로 풀 덤핑합니다. 악성코드가
실제로 실행되는 것이므로 VM 내에서 진행하며 미리 스냅샷을 찍어 덤프 전 상태로 돌아갈 수 있도록
합니다. 여기서 덤프된 파일을 Dumped_PUBG_H.exe라고 부르겠습니다.
언패킹 전인 PUBG H.exe 파일보다 크기가 좀 더 커졌고, 파일 타입이 .NET 으로 바뀐 것을 볼 수 있습니다. .NET 실행 파일의 경우
Reflector나 Jetbrain사의 dotpeek을 이용하여 쉽게 디컴파일이 가능하므로 dotpeek으로
시도해 보았습니다.
[그림 5.
Dumped_PUBG_H.exe dotpeek 디컴파일]
무려 프로젝트명부터 ‘NanoCore Client’로 네이밍되어 있는 것을
확인할 수 있습니다. NanoCore는 해외에서 개발된 오픈소스 악성 프로그램으로, NanoCore의 개발자는 이것을 합법적이라고 주장했지만 결국 2017년
7월에 유죄를 선고받았습니다. (기사: https://www.thedailybeast.com/nanocore-coder-pleads-guilty-to-aiding-and-abetting-hackers)
NanoCore는 RAT(Remote Access Trojan)으로, 감염된 PC를 외부에서 원격조작하여 중요 정보를 탈취하거나, 추가로 악성 프로그램, 스크립트를 내려 받아 실행하게 하거나, DDoS 공격을 수행하도록 할 수도 있습니다.
소스코드 내부에 드래그하여 표시해둔 부분을
보면 변수명이 무려 ‘#=qzDzg9a$HVGG1G5cdhqbdwO3OG_SFijGXN8Towa37$TQ=’ (으악)인 것을 볼 수 있습니다. 이 변수 뿐만 아니라 거의 모든 변수와
함수, 클래스명이 위와 같은 형태로 난독화되어 있기 때문에 코드 분석이 무척이나 까다롭습니다. 정적으로 계속 분석하기 위해서는 난독화를 해제할 필요성이 있고, 이를
해결하기 위해 de4dot이라고
하는 프로그램을 사용했습니다. (Github: https://github.com/0xd4d/de4dot)
[그림 6. Deobfuscate
Dumped_PUBG_H.exe]
de4dot의 난독화 해제 기능을 이용해
본 결과 Eazfuscator.Net
3.3버전을 사용하여 난독화된 것을 확인할 수 있었습니다. 난독화 해제된 파일을 Deobfuscated_PUBG_H.exe라고 부르겠습니다.
[그림 7. Obfuscated
vs Deobfuscated]
왼쪽이 난독화가 적용된 상태의 샘플, 오른쪽은 난독화가 해제된 상태의 샘플입니다. 확실히 읽고 분석하기
편해진 것을 볼 수 있습니다. 이제 난독화 해제된 파일을 분석할 차례입니다.
.NET 디컴파일 툴은 reflector와 dotpeek 두 개가 대표적이고, 둘 모두 VS Project로 디컴파일한 소스를 export할 수 있는 기능을 갖추고 있습니다. dotpeek을 이용하여 VS Project로 내보내어 Visual Studio를 이용해 IDA의 Hex-rays 기능을 이용하듯이 하나하나 이름을 변경해가며
분석하면 그냥 소스만 보고 분석하는 것보다 훨씬 이해하기 쉽게 분석이 가능합니다.
[그림 8. Main
Function]
Main
함수를 찾아보면 바로 아래 ClientLoaderForm_Shown 함수 내에서 Class8의 정적 멤버 함수인 RunMalware를 실행하는 모습이
보입니다. 원래는 Class8.smethod_8() 이지만
보기 쉽도록 직접 코드를 분석하면서 함수 네이밍을 어느 정도 다시 해 뒀습니다. 그 위에서는 실행된
것이 보이지 않도록 Visible을 False로 설정하는
부분도 있으며, Form이 닫힐 경우 자동적으로 악성 행위에 사용한 각종 설정 값들을 저장하는 부분도
존재합니다.
[그림 9. RunMalware
Function]
앞서
언급했다시피 함수 네이밍을 어느 정도 해둔 상태이기 때문에 이제 함수 명만 보고도 어느정도는 행위를 파악하는게 가능합니다. 코드를 피킹해 둔 부분을 보면 Exception이 발생했을 때 이를
전부 로깅하는 코드가 존재함을 알 수 있습니다. 코드를 분석하면서 알게 된 점은 이 프로그램은 악성코드이지만
익셉션 핸들링이나 로깅 등이 매우 충실히 잘 이루어지고 있는 상당히 수준 높은 구조를 가지고 있다는 것입니다.
[그림 10. Decrypt Resource]
코드
길이가 상당히 방대하고 이를 하나하나 다 짚으면서 보고서를 쓰면 끝이 없기 때문에 가장 중요한 부분만 작성하기로 했습니다. 이 파일은 리소스 영역에 딱 하나의 리소스를 가지고 있는데, 이
리소스는 암호화되어 있기 때문에 복호화를 해야 읽을 수 있고 사용 가능한 데이터가 나타납니다.
[그림 11. Save
Resource to Dict]
복호화
루틴을 지나면서 복호화가 완료된 데이터는 함수 내에서 클래스 멤버 변수인 dictionary_1에 뽑아낸
키와 값으로 각각 저장됩니다. 내부에 저장된 데이터는 Key와
Value값을 가진 Json Object 형태의 데이터일
것이라고 예상이 가능합니다.
[그림 12. GetValueWithKey
Function]
dictionary_1에서 Key를 이용해 Value를 받아오는 함수가 존재하여 GetValueWithKey라고 이름 붙이고 사용되는 부분을 모두 찾아보았더니
Key값들의 이름이 Timeout이나 Delay,
Server 주소 등 Configuration과 관련된 내용들인 것을 확인할 수 있었습니다. 이 리소스를 복호화하는 것이 가능하다면 이 악성코드가 어떻게 동작할지 예측하는 것이 가능할 것입니다.
[그림 13. 리소스
첫 부분 복호화]
복호화
루틴을 살펴보면 리소스 영역을 한번에 전부 복호화하는 것이 아니라 첫 4바이트를 읽고, 그 값만큼을 다시 읽어와서 특정 키를 이용하여 먼저 복호화한 후 그 값을 나머지 리소스 영역 복호화를 위한 Initialization Vector와 Key값으로 사용하는 것을
알 수 있습니다. DES 암호화를 사용하며, IV를 사용하는
것에서도 알 수 있듯이 64비트 크기의 블록 암호입니다.
[그림 14. 리소스
첫 부분 복호화 키 루틴]
리소스
복호화 루틴의 경우 모두 코드 형태로 존재하였기에 새로 C# 프로젝트를 생성하고 코드를 그대로 가져다가
완성하였지만, C#에 대한 이해가 부족하여 그림 13, 및
그림 14에서 Class8.DecryptBuffer 함수의
두 번째 인자로 들어가는 Guid 값의 경우 똑같이 작성해도 복호화 도중 익셉션이 발생하는 문제가 있었습니다.
[그림 15.
CustomAttribute Guid]
GetCustomAttributes의 어감에서도 추측할 수 있듯이 코드
내부 어딘가에 Guid값이 Set 되어 있을 것이라고 보고
파일들을 뒤져가며 찾아보니 지정된 Guid 문자열을 발견할 수 있었습니다.
[그림 16. Set
Decrypt Key]
발견한
값으로 Guid를 만들어서 같은 복호화 루틴에 Key로 설정해
둔 코드입니다. 이후 복호화 루틴을 돌려서 나온 결과를 Dictionary
자료형에 담게 되는데, 이 변수를 Json으로
덤프하면 다음과 같은 결과를 볼 수 있습니다. (복호화에 사용한 .NET
소스코드는 블로그 포스트에 첨부하였습니다)
{
"BuildTime":
"2018-02-08T03:06:08.3425287Z",
"Version": {
"Major": 1,
"Minor": 2,
"Build": 2,
"Revision": 0,
"MajorRevision": 0,
"MinorRevision": 0
},
"Mutex":
"19d9428a-7d48-4c41-b850-2e92448ad594",
"DefaultGroup":
"Default",
"PrimaryConnectionHost":
"TestZb.Ze.Am",
"BackupConnectionHost":
"TestZb.Ze.Am",
"ConnectionPort": 54984,
"RunOnStartup": true,
"RequestElevation": false,
"BypassUserAccountControl": false,
"ClearZoneIdentifier": true,
"ClearAccessControl": false,
"SetCriticalProcess": false,
"PreventSystemSleep": true,
"ActivateAwayMode": false,
"EnableDebugMode": false,
"RunDelay": 0,
"ConnectDelay": 4000,
"RestartDelay": 5000,
"TimeoutInterval": 5000,
"KeepAliveTimeout": 30000,
"MutexTimeout": 5000,
"LanTimeout": 2500,
"WanTimeout": 8000,
"BufferSize": 65535,
"MaxPacketSize": 10485760,
"GCThreshold": 10485760,
"UseCustomDnsServer": true,
"PrimaryDnsServer":
"8.8.8.8",
"BackupDnsServer":
"8.8.4.4"
}
중요하다고 생각되는 데이터에는 볼드 + 붉은
색 처리를 하였습니다. 접속을 시도하는 서버 도메인과 그 포트, 자동
실행 프로그램으로 등록 여부, UAC 우회 여부 등 여러 옵션을 선택할 수 있는 것을 알 수 있습니다. 아무래도 TestZb.Ze.Am:54984로 접속 시도를 할 것으로 보입니다.
[그림 17. 도메인
VirusTotal 조회 결과]
접속하는
서버의 메인 도메인에 대한 정보를 알아보기 위해 VirusTotal에 조회 및 직접 접속해 보니 2018년 2월 7일까지
URL Shortener 서비스를 운영한 한국인의 도메인인 것으로 추정되었습니다. 서브 도메인을 확인해 보니 다음과 같았습니다.
spr17.ze.am
youcy111.ze.am
www.hasangwook.ze.am
spr1.ze.am
www.xn----qz5e7tr56dq9g2qdzxr3ue56g.ze.am
www.xn----rz5e7t44s3tj2ik5nc1ummty2pj.ze.am
spr18.ze.am
unlocker.ze.am
video.ze.am
shyyset.ze.am
xn--o39az1zdmbpa021j2ufp8f.ze.am
excel.ze.am
ekfak15.ze.am
spr39.ze.am
deim.ze.am
sgddos.ze.am
www.xn----c28e37pwzi89ex7av7h7zzdtdxyc.ze.am
www.play21.ze.am
coca-cola.ze.am
cuzz.ze.am
fileup.ze.am
lillllili.ze.am
compcomp.ze.am
njj.ze.am
minoo.ze.am
mj.ze.am
zombie24.ze.am
hdream.ze.am
deabakgam.ze.am
daumpotlncoder.ze.am
agwgsdgd.ze.am
spectre.ze.am
lmj.ze.am
val.ze.am
naverddos.ze.am
ktv.ze.am
www.agwgsdgd.ze.am
hwalang0929.ze.am
kisa5323.ze.am
spynote.ze.am
ddosattack.ze.am
teeluk.ze.am
sel.ze.am
ysc3514.ze.am
xink.ze.am
bggonline.ze.am
djtmdgh5517.ze.am
adminwww.ze.am
web.dkfate.ze.am
batmanblog.ze.am
nutri999.ze.am
enal153.ze.am
ssul5.ze.am
4shared.ze.am
www.dbns.ze.am
spr99.ze.am
spr.25.ze.am
qer.ze.am
gooddoctor.ze.am
mingkey.ze.am
nutrino20.ze.am
xn--299ayym43bd2d8zctu1ay7f.ze.am
dbns.ze.am
dinte.ze.am
spr68.ze.am
zb.ze.am
dkssudak11.ze.am
www.iam.ze.am
swoo.ze.am
pora.ze.am
soulms.ze.am
netbot.ze.am
godofwarv502.ze.am
xn--lg3bujr1jg5w.ze.am
djchoice.ze.am
bfz.ze.am
nutrino19.ze.am
spr40.ze.am
ipzero123.ze.am
dltjwns.ze.am
VirusTotal에 등록된 서브 도메인만 총 99개이며, 개중에는 ddos,
naverddos, netbot, zombie24 등 노골적으로 악성임을 드러내는 이름이 다수 존재했습니다. 도메인 서비스의 계정을 탈취당해 해커에 의해 서브 도메인이 다수 만들어지고,
해커의 서버 혹은 탈취된 다른 서버로 연결된 것으로 보입니다.
[그림 18. 서버에서
받은 데이터 처리]
서버에
연결하여 받아온 데이터는 그림 18의 함수에 인자로 넘어가며, 리소스를
복호화 할 때 사용한 함수와 같은 함수를 사용하여 데이터를 복호화하고 파싱합니다.
[그림 19. 파싱된
데이터 값 처리]
파싱된
데이터는 여러 개의 switch 문을 거치면서 값에 따라 다른 명령을 처리하는데, 대표적인 타입이 그림 19에 나타난 FileCommand 입니다. 네 번째 인자는 타입 내의 구체적인 명령
Offset, 다섯 번째 인자는 해당 명령에 대한 인자로 보입니다.
[그림 20. Command
Type]
그림
20에 나타나 있듯이 이 악성코드에는 현재 BaseCommand, FileCommand,
PluginCommand의 세 가지 타입이 존재하며 서버에서 받아온 값에 따라 저 중에서 명령을 선택적으로 수행합니다. PluginCommand라는 타입에서 유추할 수 있듯이 이 악성코드는 플러그인을 이용해 선택적으로 특정 명령을
추가 및 제거할 수 있습니다. 서버에 연결하여 명령어를 받아와 실행하는 것을 보면 RAT(Remote
Access Trojan)이며 상당히 고도화되어 있음을 알 수 있습니다.
일반적으로
패킹이나 난독화는 동적 분석의 결과에 영향을 미치지 않으므로 처음 다운받은 샘플 그대로 VM 내의 Cuckoo 자동분석시스템에 넣고 돌려봤습니다.
[그림 21. 실행
후 스크린샷]
정적분석에서
예상했던 대로 Visible 값이 False가 되어있기 때문에
스크린샷에는 아무런 창도 나타나지 않았습니다.
[그림 22. 네트워크
분석]
눈에
띄는 DNS 주소는 리소스 복호화를 통해 알아낸 TestZb.Ze.Am
입니다. DNS 조회를 통해 1.241.72.196이라는
IP 주소를 알아냈습니다. 하지만 TCP 연결 목록에는 눈에 띄지 않았고, Cuckoo 특성상 접속 실패의
경우 TCP 탭에 표시하지 않으므로 접속 시도는 했으나 성공하지 못한 것으로 예상할 수 있습니다.
[그림 23. 네트워크 pcap 분석]
pcap 파일을 직접 다운로드 받아서 분석해 본 결과 해당 IP와 리소스 내에서 찾아낸 54984 포트로 접속을 시도하기 위해
SYN 패킷을 계속 발신하지만 응답은 RST/SYN으로 해당
포트는 닫혀 있는 것을 확인할 수 있습니다. 현재는 악성코드 서버가 동작하지 않고 있다는 의미입니다.
[그림 24. GeoIP 결과]
GeoIP를 이용하여 IP 주소를
가진 서버의 위치를 확인해 보니 국내에 위치한 서버였습니다. Whois 조회에서는 SK Broadband에 할당되었다고 합니다.
[그림 25. 드랍한
버퍼]
버퍼
내에서 드랍한 세 파일 중 두 파일은 VirusTotal에서 결과가 존재했으며 둘 모두 NanoCore Client에서 사용되는 .NET 기반 DLL 파일인 것으로 확인되었습니다.
‘PUBG
H.exe’ 파일은 대한민국 최대 포털 사이트의 블로그를 통해 유포되고 있었으며, 공격에
사용된 해커의 서버 또한 접속되지는 않았지만 국내에 위치한 서버였습니다. 유포할 때 포스트 제목을 유명
게임인 배틀그라운드 핵을 공유하는 것처럼 위장한 것으로 보아 국내 게이머들을 타겟팅 한 것으로 보입니다. 언패킹된
샘플이 .NET 기반이며 NanoCore Client 라는
네이밍이 되어 있고, 실제 NanoCore가 사용하는 DLL과 플러그인을 사용하며 특정 서버로의 접속을 시도하고 받아온 값에 따라 정의된 명령을 수행하는 것으로 보아
오픈소스인 NanoCore의 소스를 이용하여 제작한 RAT(Remote Access Trojan)로 분류할 수 있습니다.
이
샘플은 컴파일 이후 난독화와 패킹 과정을 거치면서 본래 .NET 기반 실행 파일이었던 사실까지 알아채기
힘들게 변형된 후 배포되고 있었습니다. 따라서 언패킹을 하지 않는 이상 정적으로는 시그니쳐 방식을 제외하고는
탐지하기 어려울 것으로 보입니다. 또한 네트워크 상에서도 암호화된 리소스 영역을 복호화한 후 그 내부에
미리 설정된 값들을 로드하여 접속할 서버, 포트 등을 정해 악성 행위를 수행하기 때문에 접속하는 서버와
포트는 샘플마다 쉽게 변경될 수 있고, 패킷 송/수신에도
암호화를 사용하여 탐지하기 상당히 어렵습니다. 남은 것은 동적 행위 분석 뿐인데, 다행인 것은 C&C 서버와의 통신이나 따로 악성 행위를 하지
않아도 '%APPDATA%\<Guid>\run.dat’
파일을 생성하고, 이 파일의 크기는 항상 8바이트라는
점입니다. run.dat 파일은 준비 과정에서 존재하지 않을 경우 생성하여 현재 Datetime을 구하고 이를 저장하도록 되어 있습니다. 그 외에도
행위가 수행되면 catalog.dat, storage.dat, task.dat, settings.bin중 일부 파일들이 위 경로에 드랍되며 이를 탐지 지표로 사용할 수 있겠습니다. 또한 실행 후 메모리 상에서 언팩이 완료된 후에는 NanoCore나 NanoCore.ClientPlugin등의
문자열이 드러나기도 합니다.
요약하면, 이 샘플은 .NET 기반 오픈소스인 NanoCore를 이용하여 제작한 RAT이고 유명 게임 배틀그라운드의
핵으로 위장하여 국내 게이머들을 타겟팅하였으며 난독화와 패킹을 통해 휴리스틱, 패턴 기반 탐지를 우회하고자
하였습니다. 따라서 동적 분석으로 드랍되는 파일과 메모리에서 발견되는 문자열 등을 통해 악성 여부를
판단할 수 있겠습니다.
아래 첨부파일은 악성코드 내부의 리소스를 복호화하는 데에 사용했던 C# 프로젝트 소스코드입니다.