- PVSM.RU - https://www.pvsm.ru -

Облегчаем реверсинг Golang бинарников или зачем вообще писать скрипты в IDA

Golang — отличный язык. Строгая типизация, сборщик мусора, вызов си функций через cgo, reflect, chan — просто сказка! Очевидно что так считаю не только я, т.к. go популярен, а значит его использует много программистов, а значит есть высокая вероятность что когда-то кому-то понадобится реверсить его бинарники — этим мы сейчас и займемся.

Подготовка

Для начала берем свежий golang 1.8 под windows/amd64. Для бинарника я возьму одну из своих поделок [1]. Компилируем с помощью go build. Размер файла — 7256576 байт, DWARF на месте. Так не пойдет. Чаще всего, при релизе, из бинарника вырезают все лишнее. Стандартные утилиты вроде strip плохо работают [2] с go, находим в гугле один из популярных вариантов обрезания бинарника: go build -ldflags "-w -s". Смотрим что делают эти флаги [3], узнаем что -w удаляет DWARF, а -s убирает таблицу символов и информацию для отладки. Компилируем, смотрим размер файла — 4894720, ну вроде как никто не обещал [4] что будет мало.

Ищем функции

Открываем наш бинарник в ida и грустим, ведь из обнаруженных функций только entrypoint. Копаем чуть глубже и видим что ida уперлась в:

lea     rax, qword_452018+128h
jmp     rax

Переходим по адресу, объявляем функцию, запускаем анализ — обнаружено 215 функций.

Облегчаем реверсинг Golang бинарников или зачем вообще писать скрипты в IDA - 1

Если продолжать в таком духе, то на разбор уйдет много времени. Настало время подумать. Вспоминаем что reflect из стандартной библиотеки позволяет вызывать функции по их именам [5], а значит названия функций, ну или хотя бы их хеши должны где-то храниться, и мы сможем их получить чтобы скормить иде.

Проматываем в начало файла в надежде с ходу напороться на указатели к нужным нам структурам, но к встречаем строку Go build ID: "e07bfb8669c13efb74574c0ad220c5f2cfae5cd4". Грепаем исходники golang, благо они нам доступны, и находим упоминание [6] об этой строке. Замечаем в коде линкера строку

ctxt.Syms.Lookup("go.buildid", 0)

которая говорит о поиске в какой-то таблице символов go.buildid. Предполагаем что найдем эту строку в бинарнике, и находим.

Эти структуры мы и искали

Прямо над строкой видим число 401000h, которое является указателем на нашу строку. Ниже видим определения функций, а выше — массив пар, в котором одно из значений похоже на указатели. Проверяем куда указывают эти указатели и не промахиваемся — на функции.

Часть массива, преобразованная в указатели

Чтобы узнать что мы видели выше, скомпилируем код оставив дебаг информацию. Откроем его в ida, узнаем что этот массив называется pclntab [7], а заполняется он в линкере функцией func (ctxt *Link) pclntab() [8].

У нас есть информация о таблице pclntab в которой хранятся названия функций и есть способ выхода на эту таблицу через buildid. Пишем код:

Код, который именует функции

def go_find_pclntab():
    pos = idaapi.get_segm_by_name(".text").endEA
    textstart = idaapi.get_segm_by_name(".text").startEA
    while True:
        # hex дата содержит строку "go.buildid" и некоторые другие значения структуры,
        # которые не должны меняться
        gobuilddefpos = FindBinary(
            pos, SEARCH_UP,
            "67 45 23 01 " + "00 "*20 +
            "67 6f 2e 62 75 69 6c 64 69 64")
        if gobuilddefpos < 100 or gobuilddefpos > pos:
            # указатель невалидный, все варианты пройдены
            # buildid pclntab entry не найден :(
            break
        # проверяем что buildid entry валидный
        if Dword(gobuilddefpos-0x10) == textstart:
            # вычитаем из имени символа смещение имени символа от pclntab
            # тем самым получаем смещение pclntab
            return gobuilddefpos + 24 - Dword(gobuilddefpos-0x8)
        pos = gobuilddefpos
    return None

def go_pclntab_travel(pclntab):
    nfunc = Dword(pclntab+8)
    # проходимся по всем функциям в массиве pclntab
    for i in xrange(nfunc):
        entry = pclntab + 0x10 + i * 0x10
        sym = Qword(entry)
        info = Qword(entry + 8) + pclntab
        symnameoff = Dword(info + 8) + pclntab
        symname = GetString(symnameoff)
        # объявляем функцию
        go_pclntab_handle_function(sym, symname, info)

Запускаем и смотрим на результаты:

Каеф

Почти все 5728 функции имеют название

Имена содержат полный путь до пакета

Ищем типы

Второе что меня интересовало после названия функций — идентификация типов. Первым делом интересуемся как выделяется динамическая память для структур, что нас сразу приводит к функции newobject [9], которой передается указатель на runtime._type [10].

Структура runtime._type

type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldalign uint8
	kind       uint8
	alg        *typeAlg
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

Заметили? Подсказка: поле str nameOff. Подсказка: str — строка. Подсказка: nameOff → name → имя → имя структуры типа → имя типа. Структура типа содержит имя типа! Только оно какое-то относительное. Выясняем что str — это смещение от начала types секции модуля, указатель на которую можно получить из moduledata [11]структуры модуля. Значит для того чтобы узнать тип, нам надо найти moduledata. Для простоты примем что у нас всегда один модуль. Находим в функции runtime.resolveNameOff [12] что первый модуль находится в переменной firstmoduledata, её надо найти в нашем бинарнике. Для этого ходить далеко не надо, можно посмотреть в той же функции resolveNameOff, ведь адреса и названия всех функций мы уже собрали скриптом выше. Остается только найти типы для распознавания, для этого я просто взял все вызовы функции newobject и забрал из параметров указатели на типы.

lea     rbx, _type_p_elliptic_p256Point_6dad60 ; *elliptic.p256Point
mov     [rsp+0A8h+var_A8], rbx
call    runtime_newobject ; теперь то мы знаем что за тип тут аллоцируется

Итог

Можно и дальше продолжать разбирать этот бинарник, но цели мы своей уже добились и показали что golang бинарники достаточно легко исследовать, а скриптинг в IDA сильно облегчает работу.

Исходный код: github.com/mogaika/golang_ida_scripts [13]

Код написан на правах «proof of concept» и может содержать недоработки, все претензии можно выразить в личных сообщениях тут, либо на github.

Текст написан на правах «всем добра», претензии к качеству текста ожидаются в личных сообщениях.

Автор: mogaika

Источник [14]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/golang/251640

Ссылки в тексте:

[1] поделок: https://github.com/mogaika/god_of_war_browser/

[2] плохо работают: https://github.com/docker/docker/blob/2a95488f7843a773de2b541a47d9b971a635bfff/project/PACKAGERS.md#stripping-binaries

[3] флаги: https://golang.org/cmd/link/

[4] никто не обещал: https://github.com/golang/go/issues/6853

[5] по их именам: https://golang.org/pkg/reflect/#Value.MethodByName

[6] упоминание: https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/cmd/link/internal/ld/data.go#L1996

[7] pclntab: https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub

[8] func (ctxt *Link) pclntab(): https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/cmd/link/internal/ld/pcln.go#L201

[9] newobject: https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/runtime/malloc.go#L802

[10] runtime._type: https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/runtime/type.go#L28

[11] moduledata : https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/runtime/symtab.go#L269

[12] runtime.resolveNameOff: https://github.com/golang/go/blob/361af94d5d4df310a90d924b4ce3dd4e3c96ee38/src/runtime/type.go#L173

[13] github.com/mogaika/golang_ida_scripts: https://github.com/mogaika/golang_ida_scripts

[14] Источник: https://habrahabr.ru/post/325498/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best