Если помните, недавно у нас выходила статья про молодой, но уже подающий надежды data stealer Loki. Тогда мы подробно рассмотрели этот экземпляр (версия 1.8), получили представление о работе бота и освоили инструмент, облегчающий реагирование на события, связанные с этим ВПО. Для более полного понимания ситуации, давайте разберем еще одно шпионское ПО и сравним исследованных ботов. Сегодня мы обратим внимание на Pony более старый, но не менее популярный образец data stealer’а. Никита Карпов, аналитик CERT-GIB, расскажет, как бот проникает на компьютер жертвы и как вычислить похищенные данные, когда заражение уже произошло.

Разбор функциональности бота

Впервые Pony был замечен в 2011 году и все еще продолжает использоваться. Как и в ситуации с Loki, популярность этого ВПО обусловлена тем, что несколько версий бота вместе с панелью администратора можно без проблем найти в сети. Например, здесь.

Экземпляр Pony, который мы будем изучать, защищен тем же самым упаковщиком, что и Loki, рассмотренным в предыдущей статье. По этой причине не будем еще раз останавливаться на процессе получения чистого ВПО и перейдем сразу к более интересным моментам. Единственное, что следует упомянуть перед разбором ВПО, — ссылка на сервер, по которой мы определяем нужный PE-файл, оканчивается на gate.php, и это один из индикаторов Pony.

При исследовании дизассемблированного кода Pony обратим внимание на участок, содержащий главные функции. Интерес представляют две из них — Initialize_Application и CnC_Func (названия функций переименованы в соответствии с их содержанием). 

Ниже представлена функция Initialize_Application. Она отвечает за инициализацию необходимых элементов (библиотеки, привилегии и т.д.) и за похищение данных. В процессе работы ВПО несколько раз использует значение 7227 — пароль к данному экземпляру бота. В Initialize_Application это значение используется для шифрования буфера, содержащего данные приложений, алгоритмом RC4.

Далее перейдем к декомпилированному коду функции CnC_Func и разберем ее алгоритм:

  1. Буфер, полученный в результате работы функции Initialize_Application, передается в функцию BuildPacket, где собирается пакет данных для передачи на сервер.

  2. По каждому URI из списка бот отправляет данные и ожидает подтверждения со стороны сервера. Если сервер не ответил 3 раза — бот идет дальше.

  3. После завершения первого списка CnC бот пытается загрузить и запустить дополнительное ВПО.

В общем доступе находится готовый билдер, который подтверждает функционал, полученный в процессе статического анализа декомпилированного кода. Пользователю предлагается ввести список URI, куда будут выгружаться похищенные данные, и список, откуда будет выгружаться дополнительное ВПО. Также пользователь может изменить пароль бота и имя дополнительного ВПО.

Pony атакует более сотни приложений, и, хотя у него есть функционал загрузчика, в основном Pony используется именно для похищения пользовательских данных. В таблице ниже перечислены все приложения, из которых бот может похитить данные.

ID 

Приложение

ID 

Приложение

ID 

Приложение

0

System Info

45

FTPGetter

90

Becky!

1

FAR Manager

46

ALFTP

91

Pocomail

2

Total Commander

47

Internet Explorer

92

IncrediMail

3

WS_FTP

48

Dreamweaver

93

The Bat!

4

CuteFTP

49

DeluxeFTP

94

Outlook

5

FlashFXP

50

Google Chrome

95

Thunderbird

6

FileZilla

51

Chromium / SRWare Iron

96

FastTrackFTP

7

FTP Commander

52

ChromePlus

97

Bitcoin

8

BulletProof FTP

53

Bromium (Yandex Chrome)

98

Electrum

9

SmartFTP

54

Nichrome

99

MultiBit

10

TurboFTP

55

Comodo Dragon

100

FTP Disk

11

FFFTP

56

RockMelt

101

Litecoin

12

CoffeeCup FTP / Sitemapper

57

K-Meleon

102

Namecoin

13

CoreFTP

58

Epic

103

Terracoin

14

FTP Explorer

59

Staff-FTP

104

Bitcoin Armory

15

Frigate3 FTP

60

AceFTP

105

PPCoin (Peercoin)

16

SecureFX

61

Global Downloader

106

Primecoin

17

UltraFXP

62

FreshFTP

107

Feathercoin

18

FTPRush

63

BlazeFTP

108

NovaCoin

19

WebSitePublisher

64

NETFile

109

Freicoin

20

BitKinex

65

GoFTP

110

Devcoin

21

ExpanDrive

66

3D-FTP

111

Frankocoin

22

ClassicFTP

67

Easy FTP

112

ProtoShares

23

Fling

68

Xftp

113

MegaCoin

24

SoftX

69

RDP

114

Quarkcoin

25

Directory Opus

70

FTP Now

115

Worldcoin

26

FreeFTP / DirectFTP

71

Robo-FTP

116

Infinitecoin

27

LeapFTP

72

Certificate

117

Ixcoin

28

WinSCP

73

LinasFTP

118

Anoncoin

29

32bit FTP

74

Cyberduck

119

BBQcoin

30

NetDrive

75

Putty

120

Digitalcoin

31

WebDrive

76

Notepad++

121

Mincoin

32

FTP Control

77

CoffeeCup Visual Site Designer

122

Goldcoin

33

Opera

78

FTPShell

123

Yacoin

34

WiseFTP

79

FTPInfo

124

Zetacoin

35

FTP Voyager

80

NexusFile

125

Fastcoin

36

Firefox

81

FastStone Browser

126

I0coin

37

FireFTP

82

CoolNovo

127

Tagcoin

38

SeaMonkey

83

WinZip

128

Bytecoin

39

Flock

84

Yandex.Internet / Ya.Browser

129

Florincoin

40

Mozilla

85

MyFTP

130

Phoenixcoin

41

LeechFTP

86

sherrod FTP

131

Luckycoin

42

Odin Secure FTP Expert

87

NovaFTP

132

Craftcoin

43

WinFTP

88

Windows Mail

133

Junkcoin

44

FTP Surfer

89

Windows Live Mail

Взаимодействие с сервером

Рассмотрим подробнее сетевое взаимодействие Pony. Как мы уже говорили, Pony сначала выгружает похищенные данные приложений на удаленный сервер, и индикатором такой коммуникации служит gate.php. После этого Pony просматривает второй список ссылок, откуда он пытается загрузить дополнительное ВПО на зараженный компьютер.

Для подтверждения того, что сервер получил и прочитал данные, бот должен получить в ответ строку STATUS-IMPORT-OK, иначе бот считает, что сервер не получил данные.

Данные, передаваемые на сервер, надежно защищаются шифрованием и компрессией. Защиту данных определяет заголовок, который идет перед ними. Стандартная защита пакета выглядит так:

  1. Данные в чистом виде с заголовком PWDFILE0.

  2. Сжатые данные с заголовком PKDFILE0. Для сжатия используется библиотека aPLib, работа которой основана на алгоритме компрессии LZW.

  3. Зашифрованные данные с заголовком CRYPTED0 и ключом в виде пароля, например, 7227 или PA$$. Для шифрования используется алгоритм RC4.

  4. Зашифрованные алгоритмом RC4 данные, ключ указан в первых 4 байтах.

Размер

Значение

Описание

0x4

rc_4key

Ключ для верхнего уровня шифрования

0x12

REPORT_HEADER

(PWDFILE0/ PKDFILE0/ CRYPTED0)

Заголовок отчета о похищенных данных

(normal/packed/crypted)

8 байт — заголовок, и 4 байта — контрольная сумма CRC32

0x4

Версия отчета

Версия отчета о похищенных данных

(константное значение 01.0)

0x4

Размер модуля

Заголовок модуля, присутствует у каждого модуля  

0x8

ID заголовка модуля

(chr(2).chr(0)."MODU".chr(1).chr(1))

2 байта, ключевое слово MODU, 1 байт, 1 байт

0x2

ID модуля

0x2

Версия модуля

-

Название системы пользователя

Модуль “module_systeminfo” (module id = 0x00000000)

Содержит информацию о системе пользователя

0x2

Система x32 или x64

-

Страна пользователя

-

Язык системы пользователя

0x2

Является ли пользователь администратором

-

Значение MachineGuid из приложения WinRAR

-

Список модулей всех приложений

По аналогии с модулем “module_systeminfo” записаны данные всех приложений

Парсер сетевых коммуникаций

Как и для Loki, напишем парсер на Python, используя следующие библиотеки:

  1. Dpkt для поиска пакетов, принадлежащих Pony, и работы с ними.

  2. aPLib для декомпрессии данных.

  3. Hexdump для представления данных пакета в хексе.

  4. JSON для записи найденной информации в удобном виде.

Рассмотрим основные части алгоритма работы скрипта:

for ts, buf in pcap:
    eth = dpkt.ethernet.Ethernet(buf)
    if not isinstance(eth.data, dpkt.ip.IP):
        ip = dpkt.ip.IP(buf)
    else:
        ip = eth.data
    if isinstance(ip.data, dpkt.tcp.TCP):
        tcp = ip.data
        try:
            if tcp.dport == 80 and len(tcp.data) > 0:  # HTTP REQUEST
                if str(tcp.data).find('POST') != -1:
                    http += 1
                    httpheader = tcp.data
                    continue
                else:
                    if httpheader != "":
                        pkt = httpheader + tcp.data
                        req += 1
                        request = dpkt.http.Request(pkt)
                        parsed_payload['Network'].update({'Request method': request.method})
                        uri = request.headers['host'] + request.uri
                        parsed_payload['Network'].update({'CnC': uri})
                        parsed_payload['Network'].update({'User-agent': request.headers['user-agent']})
                        if uri.find("gate.php") != -1:
                            parsed_payload['Network'].update({'Traffic Purpose': "Exfiltrate Stolen Data"})
                            parse(tcp.data, debug)
                        elif uri.find(".exe") != -1:
                            parsed_payload['Network'].update({'Traffic Purpose': "Download additional malware"})
                        print(json.dumps(parsed_payload, ensure_ascii=False, sort_keys=False, indent=4))
                        parsed_payload['Network'].clear()
                        parsed_payload['Malware Artifacts/IOCs'].clear()
                        parsed_payload['Compromised Host/User Data'].clear()
                        parsed_payload['Applications'].clear()
                        print("----------------------")
            if tcp.sport == 80 and len(tcp.data) > 0:  # HTTP RESPONCE
                resp += 1
                response = dpkt.http.Response(tcp.data)
                if response.body.find(b'STATUS-IMPORT-OK') != -1:
                    AdMalw = True
                    print('Data imported successfully')
                else:
                    print('C2 did not receive data')
                print("----------------------")

        except(dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
            continue
print("Requests: " + str(req))
print("Responces: " + str(resp))

Поиск пакетов, связанных с Pony, аналогичен поиску пакетов Loki. Ищем все HTTP-пакеты. Парсим запросы, в которых находится информация бота. Остальные запросы фиксируются, но данные в них не обрабатываются. Если в ответ на запрос получена строка STATUS-IMPORY-OK — отмечаем успешную выгрузку данных. Во всех других случаях считаем, что сервер не получил данные. Если после выгрузки данных найдены HTTP-запросы с URI, оканчивающимся на .exe — отмечаем загрузку дополнительного ВПО.

Рассмотрим функцию, отвечающую за снятие всей защиты с данных и импорт модулей:

def process_report_data(data, debug):
    index = 0
    if len(str(data)) == 0:
        return False
    elif len(str(data)) < 12:
        return False
    elif len(str(data)) > REPORT_LEN_LIMIT:
        return False
    elif len(str(data)) == 12:
        return True

    if verify_new_file_header(data):
        rand_decrypt(data)

    report_id = read_strlen(data, index, 8)
    index += 8

    if report_id == REPORT_CRYPTED_HEADER:
        parsed_payload['Malware Artifacts/IOCs'].update({'Crypted': report_id.decode('utf-8')})
        decrypted_data = rc4DecryptText(report_password, data[index:len(str(data))])
        data = decrypted_data
        index = 0
        report_id = read_strlen(data, index, 8)
        index += 8

    if report_id == REPORT_PACKED_HEADER:
        parsed_payload['Malware Artifacts/IOCs'].update({'Packed': report_id.decode('utf-8')})
        unpacked_len = read_dword(data, index)
        index += 4
        leng = read_dword(data, index)
        index += 4
        if leng < 0:
            return False
        if not leng:
            return ""
        if index + leng > len(str(data)):
            return False
        packed_data = data[index:index + leng]
        index += leng
        if unpacked_len > REPORT_LEN_LIMIT or len(str(packed_data)) > REPORT_LEN_LIMIT:
            return False
        if not len(str(packed_data)):
            return False
        if len(str(packed_data)):
            data = unpack_stream(packed_data, unpacked_len)
        if not len(str(data)):
            return False
        if len(str(data)) > REPORT_LEN_LIMIT:
            return False
        index = 0
        report_id = read_strlen(data, index, 8)
        index += 8
    if report_id != REPORT_HEADER:
        print("No header")
        return False
    version_id = read_strlen(data, index, 3)
    index += 8
    if version_id != REPORT_VERSION:
        return False
    parsed_payload['Malware Artifacts/IOCs'].update({'Data version': version_id.decode('utf-8')})
    hexdump.hexdump(data)
    report_version_id = version_id
    parsed_payload['Applications'].update({'Quantity': 0})
    while index < len(data):
        index = import_module(data, index, debug)

    return data

После снятия обязательного шифрования, определяем метод снятия следующего уровня защиты — в зависимости от заголовка. Если присутствует дополнительное шифрование с заголовком CRYPTED0 — скрипт пытается подставить стандартный ключ, и при несоответствия ключа запрашивает файл ВПО, в котором находит используемый в этом боте пароль. Если заголовок данных PWDFILE0 — начинаем импорт модулей приложений.

Для расшифровки мы использовали алгоритм RC4:

def rc4DecryptHex(key, pt):
    if key == '':
        return pt

    s = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s[i] + key[i % len(key)]) % 256
        s[i], s[j] = s[j], s[i]

    i = j = 0
    ct = []
    for char in pt:
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        s[i], s[j] = s[j], s[i]
        ct.append(chr(char ^ s[(s[i] + s[j]) % 256]))
    decrypted_text = ''.join(ct)
    data = decrypted_text.encode('raw_unicode_escape')
    return data

Результат работы парсера представлен ниже. Парсер успешно снял шифрование, произвел декомпрессию и нашел похищенные данные. Следует отметить, что у каждого модуля есть несколько типов представления данных, в зависимости от найденной ботом информации. В нашем примере бот похитил данные Outlook и записал их с типом 7. На первый запрос сервер ответил боту, а остальные коммуникации не несли полезной информации.

В заключение давайте сравним исследованные data stealer'ы Pony и Loki и подведем итог. Список атакуемых приложений и у Pony, и у Loki примерно одинаков, но функционал Loki, особенно в новых версиях, шире, чем у Pony. Pony защищает все передаваемые данные в несколько уровней, что не дает определить без специального инструмента, какие именно данные похитил бот. Loki, в свою очередь, передает все данные в открытом виде, но без знания структуры запросов разобрать эти данные тоже довольно сложно. 

Надеемся, эти две статьи помогли разобраться, какую опасность несут данные data stealer’ы и как можно упростить реагирование на инциденты с помощью реализованных нами инструментов.