Written by
n0fate
on
on
Komplex Malware Analysis
이 내용은 개인적으로 바이너리를 분석한 내용으로 상세한 내용은 다음 글을 참조 바람.
Sofacy’s ‘Komplex’ OS X Trojan - Paloalto Networks
Komplex Mac backdoor answers old questions - Malware Bytes
라운드 1
- MD5 (2a06f142d87bd9b66621a30088683d6fcec019ba5cc9e5793e54f8d920ab0134.bin) = 81749e780d27ddd15973d19de77c9007
- MachO 파일을 ‘/tmp/content’ 에 드롭, 실행 권한(755) 부여
- 사용자 다운로드 디렉터리의 “roskosmos_2015-2025.app” 디렉터리를 삭제
- 드롭퍼에 내장된 PDF 파일을 사용자 다운로드 디렉터리에 ‘roskosmos_2015-2025.pdf’로 생성
- SetFile 명령어(Deprecated)를 이용하여 PDF 파일에 숨김 속성을 부여
- ‘/tmp/content’ 파일을 실행
- ‘open’ 명령어를 이용하여 PDF 파일을 미리보기 앱에서 보이도록 함. PDF 파일은 러시아 로켓 관련 내용임.
- 자기 자신을 삭제
라운드 2
- MD5 (content) = 4400ec9c4732a32149ca58e7c5806178
- ‘mkdir -p /Users/Shared/.local/ &> /dev/null’
- “mkdir -p ~/Library/LaunchAgents/ &> /dev/null”
- 파일 내부에 있는 Mach O 파일을 ‘/Users/Shared/.local/kextd’에 저장
- 자동실행을 위한 프로퍼티 리스트 생성. 경로는 ‘/Users/Shared/com.apple.updates.plist’
- 프로퍼티 리스트를 LaunchAgent로 실행하는 쉘 스크립트 생성. 경로는 ‘/Users/Shared/start.sh’
- cp /Users/Shared/com.apple.updates.plist $HOME/Library/LaunchAgents/ &>/dev/null
- “/Users/Shared/com.apple.updates.plist” 삭제
- 쉘 스크립트와 MachO 파일에 실행권한(755) 부여
- ‘/Users/Shared/start.sh’ 실행 -> 이 과정에서 다음 스테이지로 이동
- ‘/Users/Shared/start.sh’ 삭제
- 자기 자신을 삭제
라운드 3(마지막)
- MD5 (kextd) = b09fe828904a38f37b7a6f6933188279
- 디버거가 연결되어 있는지 확인 후 있으면 자가지신을 삭제하고 종료(함수 이름은 ‘AmIBeingDebugged’)함. 코드는 iOS Anti-Debugging Protections 와 거의 동일
- 0.06초 간격으로 ‘http://www.google.com’ 에 연결 테스트
- 설정 정보를 복호화하여 접속할 C2 서버 도메인 명 획득. 서버 주소는 ‘hxxp://appleupdate.org’
- 기본적인 정보 Mac OS X, 사용자 이름을 서버에 전달
- URL을 생성하여 C2 서버에 전달 시 반응을 체크, 반응이 없으면 다른 C2 서버(백업)로 연결 시도
- 백업 C2 서버는 ‘hxxp://apple-iclouds.net’, ‘hxxp://itunes-helper.net’
- C2 서버에서 제어
- 모든 데이터는 HTTP POST 방식으로 통신
- 전송하는 데이터는 암호화 및 Base64 인코딩
설정 파일 디코딩
- MD5 (configfile) = ef3471eaddd0683fba4880a380607973
라운드 3의 바이너리에 ‘0x9230 to 0x92F4’ 위치의 196 바이트를 디코딩해야 함. 디코딩 키는 11바이트의 XOR 키로 인코딩 되어 있으며 디코딩 루틴은 다음과 같음.
import sys
from ctypes import *
class _ConfigFile(LittleEndianStructure):
_fields_ = [
('Filename', c_ubyte*0x8),
('PathtoSave', c_ubyte*10),
('BlockShell', c_ubyte*5),
('StartBlockFile', c_ubyte*6),
('BlockExecute', c_ubyte*7),
('BlockDelete', c_ubyte*6),
('EndBlockFile', c_ubyte*7),
('XORKEY', c_ubyte*15),
('MainServer', c_ubyte*0x18),
('BackupServer1', c_ubyte*0x18),
('BackupServer2', c_ubyte*0x18),
('MAC', c_ubyte*3),
('Config', c_ubyte*6),
('GetConfig', c_ubyte),
('Files', c_ubyte*4),
('Log', c_ubyte*3),
('OldConfig', c_ubyte*1),
('ID', c_ubyte*2),
('Token', c_ubyte*10),
('Dummy', c_ubyte*10), # Unused
('Extensions', c_ubyte*20)
]
def _memcpy(buf, fmt):
return cast(c_char_p(buf), POINTER(fmt)).contents
xorkey = [0x60, 0x55, 0x66, 0x45, 0x53, 0x19, 0x01, 0x72, 0x6C, 0x46, 0x3E]
xorsize = len(xorkey)
### MD5 (configfile) = ef3471eaddd0683fba4880a380607973
### config file offset : 0x9230 to 0x92F4 (196 bytes)
f = open('configfile', 'rb')
buf = f.read()
f.close()
EncConfig = _memcpy(buf, _ConfigFile)
for f,t in _ConfigFile._fields_:
if f == 'Dummy' or f == 'XORKEY':
continue
field = getattr(_ConfigFile, f)
offset = field.offset
datasize = field.size
output = ''
if f == 'Extensions':
for fileoff in xrange(0, datasize):
output += chr((ord(buf[offset+fileoff]) ^ xorkey[fileoff%5]));
else:
for fileoff in xrange(0, datasize):
output += chr((ord(buf[offset+fileoff]) ^ xorkey[fileoff%xorsize]));
print '%s : %s'%(f, output.replace('\x00','')) # print type
수행 결과
$ python xor.py
Filename : FileName
PathtoSave : PathToSave
BlockShell : Shell
StartBlockFile : [file]
BlockExecute : Execute
BlockDelete : Delete
EndBlockFile : [/file]
MainServer : appleupdate.org
BackupServer1 : apple-iclouds.net
BackupServer2 : itunes-helper.net
MAC : mac
Config : config
GetConfig : 1
Files : file
Log : log
OldConfig : 2
ID : id
Token : h8sn3vq6kl
Extensions : .xml.pdf.htm.zip