Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Mergen — Деобфускация с помощью оптимизации с использованием LLVM IR и разбора ассемблера. | Kitploit
Инструменты/GitHubGitHub/nac-l/mergen
Статический анализДинамический анализ (песочница)Обратная инженерияАнализ Бинарных ФайловЭксплуатация Бинарных Файлов
GitHubnac-l/mergen

Mergen

Деобфускация с помощью оптимизации с использованием LLVM IR и разбора ассемблера.

Репозиторий
878943 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Обзор проекта:

Mergen — это инструмент, предназначенный для преобразования ассемблерного кода в промежуточное представление LLVM (IR). Этот инструмент разработан для:

  • Деобфускации или девиртуализации обфусцированного бинарного кода
  • Улучшения процесса обратной разработки, делая его более эффективным и результативным, особенно для сложных программных систем.

Руководство по сборке и запуску

Чтобы собрать и запустить проект, посмотрите docs/BUILDING.md.

Базовый шлюз для проверки переписывания

Работа по переписыванию должна поддерживать зелёный статус базового шлюза регрессии. Шлюз собирает целевые PE-образцы, запускает lifter и проверяет результирующий поднятый IR.

  • Документация рабочего процесса: docs/REWRITE_BASELINE.md
  • Запуск одной командой: scripts\\rewrite\\run.cmd

Основные цели:

  • Деобфускация

  • Девиртуализация

  • Оптимизация

Как это работает?

Мы выполняем символьное выполнение (или символьный подъём) цели. Идея заключается не в подъёме отдельных инструкций, а в подъёме целой функции. Мы не ожидаем, что одна инструкция или один базовый блок будут вести себя одинаково каждый раз; вместо этого мы рассматриваем их как способные вести себя по-разному и для разных целей каждый раз. Мы стараемся сделать генерируемый IR как можно более простым и оптимизируемым. У нас также другие потребности, чем у обычного компилятора. Мы используем анализ для оценки потока управления. Мы не можем полагаться на LLVM во всём анализе, потому что он создан для других целей и может быть неоптимальным для нашего случая.

image

Примеры

Это практический пример, иллюстрирующий, как Mergen работает с виртуализированными программами.

  1. VMProtect
  2. Ветвления/Таблицы переходов
  3. Themida 3.1.6.0 LION64 (Red)

Пример №1 (VMProtect)

Это наша целевая программа

root@kitploit:~
struct test {
    int a;
    int b;
    int c;
};

int maths(test a, int b, int c) {
        return a.a  + b - c;
}

image

image

Настройки VMProtect: всё выключено, мы виртуализируем функцию на ультра-настройке. (Проверенные версии 3.4.0-3.6.0 3.8.1)

image

image

Здесь мы запускаем mergen. Первый аргумент — имя файла, второй — адрес функции. Посмотрите, как просто его запускать. Мы можем скомпилировать вывод, чтобы исследовать его с помощью любимого декомпилятора.

image

root@kitploit:~
; ModuleID = 'my_lifting_module'
source_filename = "my_lifting_module"

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %0, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readonly %memory) local_unnamed_addr #0 {
entry:
  %stackmemory = alloca i128, i128 13758960, align 8
  %1 = trunc i64 %r8 to i32
  %2 = trunc i64 %rdx to i32
  %GEPLoadxd-5369456437- = getelementptr i8, ptr %memory, i64 %rcx
  %3 = load i32, ptr %GEPLoadxd-5369456437-, align 4
  %adc-temp-5370242400- = sub i32 %2, %1
  %realnot-5369532059- = add i32 %adc-temp-5370242400-, %3
  %stackmemory10243.sroa.55.1375304.insert.ext10255 = zext i32 %realnot-5369532059- to i64
  ret i64 %stackmemory10243.sroa.55.1375304.insert.ext10255
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }

После компиляции:

image

image

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

root@kitploit:~
define i64 @main(i64 %rcx, i64 %rdx, i64 %rdx, i64 %r8, i64 %r9 ...)

Поэтому мы просто корректируем сигнатуру функции, чтобы она выглядела нормально. Если у вас есть дополнительные вопросы по этой части, рекомендую изучить соглашения о вызовах и ABI.

Пример №2 (Ветвления/Таблицы переходов)

Итак, предположим, у нас есть такой код. VM возьмут приведённый ниже код и превратят его в косвенный переход, что немного менее удобно для реверс-инженера.

root@kitploit:~
int maths(int a, int b, int c) {
    if (a > b)
        return a + b + c;
    else
        return a - b - c;
}
root@kitploit:~
next_handler = xxx;
if ( a-b > 0 )
  next_handler = yyy;
jump next_handler;

Мы стараемся всегда анализировать значения и отслеживать их. Это позволяет нам понимать поток управления. Для ветвлений, похожих на таблицы переходов Оптимизированный вывод будет простым:

root@kitploit:~
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readnone %TEB, ptr nocapture readnone %memory) local_unnamed_addr #0 {
fake_ret:
  %0 = lshr i64 %rcx, 62
  %common.ret.op = and i64 %0, 2
  ret i64 %common.ret.op
}

Неоптимизированный вывод. (DCE для читаемости)

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr %TEB, ptr %memory) {
  %lsb = and i64 %rcx, 255
  %pf1 = mul i64 %lsb, 72340172838076673
  %pf2 = and i64 %pf1, -9205322385119247871
  %pf3 = urem i64 %pf2, 511
  %pf4 = and i64 %pf3, 1
  %pf5 = icmp eq i64 0, %pf4
  %0 = zext i1 %pf5 to i64
  %createrflag2 = shl i64 %0, 2
  %creatingrflag = or i64 2, %createrflag2
  %zeroflag = icmp eq i64 %rcx, 0
  %1 = zext i1 %zeroflag to i64
  %createrflag21 = shl i64 %1, 6
  %creatingrflag2 = or i64 %creatingrflag, %createrflag21
  %signflag = icmp slt i64 %rcx, 0
  %2 = zext i1 %signflag to i64
  %createrflag23 = shl i64 %2, 7
  %creatingrflag4 = or i64 %creatingrflag2, %createrflag23
  %GEPSTORE-5368713221- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %creatingrflag4, ptr %GEPSTORE-5368713221-, align 4
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7
  %3 = mul i64 %shr-lshr-5368713233-, 4
  %bvalue_indexvalue = add i64 5368713249, %3
  %4 = icmp eq i64 %bvalue_indexvalue, 5368713253
  %lolb- = select i1 %4, i64 5368713264, i64 5368713257
  %GEPSTORE-5368713248- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %lolb-, ptr %GEPSTORE-5368713248-, align 4
  br i1 %4, label %real_ret, label %real_ret41

real_ret:                                         ; preds = %fake_ret
  %inc-5368713273- = add i64 %shr-lshr-5368713233-, 1
  ret i64 %inc-5368713273-

real_ret41:                                       ; preds = %fake_ret
  ret i64 %shr-lshr-5368713233-
}

Обратите внимание на эту часть

root@kitploit:~
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7

Мы получаем флаги, затем получаем 7-й бит, который является флагом знака (Sign Flag), и используем его для вычисления адреса. С помощью анализа мы определяем, что адрес может быть одним из двух значений: 5368713257 или 5368713264, и превращаем это в сравнение. Если адрес равен 5368713257, переходим по одной ветке, иначе — по другой. При этом важно пометить условие соответствующим значением, так как позже может потребоваться вычислить другой переход с тем же значением.

Хотя мы решаем косвенные переходы, переходы с более чем двумя возможными адресами не поддерживаются. Это связано с тем, что анализ для них ещё не реализован. Это позволяет нам решать ветвления в стиле VM, но вызывает проблемы с реальными таблицами переходов.

Пример №3 (Themida 3.1.6.0 LION64 (Red))

Наша целевая программа:

image

Настройки Themida (нас пока интересуют только VM):

image

image

image

После виртуализации:

image

Запуск Mergen:

image

Вывод кода: нажмите здесь

Итак, почему наш результат не так успешен, как подъём бинарного файла, защищённого VMP?

Themida активно записывает данные в секцию .themida. В отличие от стека, мы не можем игнорировать эти записи, потому что эти значения могут быть прочитаны другими компонентами позже.

Но у нас есть временное решение: удалить все сохранения в секцию .themida. Поскольку наша программа не записывает в память, я просто закомментировал все сохранения. Теперь осталось это:

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr writeonly %memory) local_unnamed_addr #0 {
  %trunc = trunc i64 %r8 to i32
  %trunc1 = trunc i64 %rdx to i32
  %trunc2 = trunc i64 %rcx to i32
  %realadd-5369771371- = add i32 %trunc1, %trunc2
  %realadd-5369582686- = add i32 %realadd-5369771371-, %trunc
  %trunc457139 = zext i32 %realadd-5369582686- to i64
  ret i64 %trunc457139
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }

Технические сложности

  • Циклы
  • Самомодифицирующийся код (особенно с условной модификацией)
  • Нахождение во вселенной, где не существует проходов "outlining" и "unrolling".

Связь

Присоединяйтесь к нашему серверу Mergen в Discord, чтобы обмениваться идеями или просто общаться.

Скачать инструмент