Order Of Six Angles

Main Logo

Х.Р.А.М Сатаны

Home | RU | Translations | Art | Feed.xml

20 June 2024

tags: note

Заметка по Frida

Это не полноценная статья! Это мои заметки по изучению фриды. Когда нужно быстро вспомнить какой-то аспект фриды, можно будет к ним быстро вернуться.

Frida General

Frida - это Open Source Dynamic Code Instrumentation Framework. Instrumentation (инструментация) - это модификация программы для сбора данных о ее поведении, производительности или других характеристиках. Frida работает с процессом в рантайме, вмешиваясь в его работу. Возможности Frida:

  1. API tracing
  2. Логгировать действия уже запущенного приложения, не останавливая его работы
  3. Расшифровка TLS трафика
  4. Фаззинг функций изнутри процесса (в отличии от классических фазеров)
  5. Дамп памяти процесса
  6. Создание протектора, антидебаггера (!)
  7. etc…

Фриду можно воспринимать как заскриптованный дебаггер. Она также как и дебагер подключается к процессу, но позволяет выполнять некоторые действия быстрее дебагера. Принцип работы Фриды заключается в инжекте в процесс QuickJS. QuickJS - маленький, самодостаточный интерпретатор Javascript. Вы пишите Frida скрипт на JS, а интерпретатор внутри процесса его исполняет. Если нет рута (ios, android), то фриду можно добавить непосредственно в исследуемое приложение следующими способами:

  1. Модификация исходников
  2. Патчинг бинарника
  3. Динамическая подгрузка (LD_PRELOAD)

Frida mini examples

Папка frida_mini_examples содержит примеры возможностей фриды.

hello.c:

#include <stdio.h>
#include <unistd.h>

void f (int n)
{
  printf ("Number: %d\n", n);
}

int main (int argc, char * argv[])
{
  int i = 0;

  printf ("f() is at %p\n", f);

  while (1)
  {
    f (i++);
    sleep (1);
  }
}

hook.py 0xaddr - перехватывает функцию f() и выводит аргумент

import frida
import sys

# intercept func at addr from arg
session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter(args) {
        send(args[0].toInt32()); // print func args
    }
});
""" % int(sys.argv[1], 16))
def on_message(message, data):
    print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()

modify.py 0xaddr - модифицирует первый аргумент, меняет его на 1337

import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter(args) {
        args[0] = ptr("1337");
    }
});
""" % int(sys.argv[1], 16))
script.load()
sys.stdin.read()

call_funcs.py 0xaddr - вызов внутренней функции f()

import frida
import sys

session = frida.attach("hello")
script = session.create_script("""
const f = new NativeFunction(ptr("%s"), 'void', ['int']);
f(1911);
f(1911);
f(1911);
""" % int(sys.argv[1], 16))
script.load()

inject_string - создает строку “TESTMEPLZ!” в адресном пространстве процесса hi и вызывает функцию f() с этой строкой в качестве аргумента

hi.c:

#include <stdio.h>
#include <unistd.h>

int f (const char * s)
{
  printf ("String: %s\n", s);
  return 0;
}

int main (int argc, char * argv[])
{
  const char * s = "Testing!";

  printf ("f() is at %p\n", f);
  printf ("s is at %p\n", s);

  while (1)
  {
    f (s);
    sleep (1);
  }
}
import frida
import sys

session = frida.attach("hi")
script = session.create_script("""
const st = Memory.allocUtf8String("TESTMEPLZ!");
const f = new NativeFunction(ptr("%s"), 'int', ['pointer']);
f(st);
""" % int(sys.argv[1], 16))
def on_message(message, data):
    print(message)
script.on('message', on_message)
script.load()

inject_struct - client коннектится к 127.0.0.1 по порту 5000, после нажатия Enter. Скрипт подменяет структуру sockaddr на собственную

nc -lp 5000 nc -lp 5001 ./client 127.0.0.1

client.c

#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int main (int argc, char * argv[])
{
  int sock_fd, i, n;
  struct sockaddr_in serv_addr;
  unsigned char * b;
  const char * message;
  char recv_buf[1024];

  if (argc != 2)
  {
    fprintf (stderr, "Usage: %s <ip of server>\n", argv[0]);
    return 1;
  }

  printf ("connect() is at: %p\n", connect);

  if ((sock_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
  {
    perror ("Unable to create socket");
    return 1;
  }

  bzero (&serv_addr, sizeof (serv_addr));

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons (5000);

  if (inet_pton (AF_INET, argv[1], &serv_addr.sin_addr) <= 0)
  {
    fprintf (stderr, "Unable to parse IP address\n");
    return 1;
  }
  printf ("\nHere's the serv_addr buffer:\n");
  b = (unsigned char *) &serv_addr;
  for (i = 0; i != sizeof (serv_addr); i++)
    printf ("%s%02x", (i != 0) ? " " : "", b[i]);

  printf ("\n\nPress ENTER key to Continue\n");
  while (getchar () == EOF && ferror (stdin) && errno == EINTR)
    ;

  if (connect (sock_fd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
  {
    perror ("Unable to connect");
    return 1;
  }

  message = "Hello there!";
  if (send (sock_fd, message, strlen (message), 0) < 0)
  {
    perror ("Unable to send");
    return 1;
  }

  while (1)
  {
    n = recv (sock_fd, recv_buf, sizeof (recv_buf) - 1, 0);
    if (n == -1 && errno == EINTR)
      continue;
    else if (n <= 0)
      break;
    recv_buf[n] = 0;

    fputs (recv_buf, stdout);
  }

  if (n < 0)
  {
    perror ("Unable to read");
  }

  return 0;
}

inject_struct.py

import frida
import sys

session = frida.attach("client")
script = session.create_script("""
send('Allocating memory and writing bytes...');
const st = Memory.alloc(16);
st.writeByteArray([0x02, 0x00, 0x13, 0x89, 0x7F, 0x00, 0x00, 0x01, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]);
// 0x1389 = 5001d
Interceptor.attach(Module.getExportByName(null, 'connect'), {
    onEnter(args) {
        send('Injecting malicious byte array:');
        args[1] = st;
    }
    //, onLeave(retval) {
    //   retval.replace(0); // Use this to manipulate the return value
    //}
});
""")

def on_message(message, data):
    if message['type'] == 'error':
        print("[!] " + message['stack'])
    elif message['type'] == 'send':
        print("[i] " + message['payload'])
    else:
        print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()

Frida windows

Пример 1

В папке samples_for_demonstration лежат образцы классического plugx.

Запускать:

frida -l winapi_hooks.py -f sample.exe

winapi_hooks.py - Скрипт хукает функции VirtualProtect, VirtualAlloc, CreateFileW, CreateFileA и выводит их аргументы. Делает дамп памяти, к которой применяется VirtualProtect.

var vp = Module.getExportByName(null, "VirtualProtect");


Interceptor.attach(vp, {
    onEnter: function(args)
    {
		var vpAddress = args[0];
		var vpAddr = args[0];
		var vpSize = args[1].toInt32();
        console.log("VirtualProtect called.\n Address:" + vpAddr + " Size:" + vpSize);
	
		console.log(hexdump(vpAddress));
		
		var dump = vpAddress.readByteArray(vpSize);
		var filename = "D:\\dumps\\" + vpAddress + "_dump.bin";
		var file = new File(filename, "wb");
		file.write(dump);
		file.flush();
		file.close();
		
    }
});


var va = Module.getExportByName(null, "VirtualAlloc");

Interceptor.attach(va, {
    onEnter: function(args)
    {
		
		var vaSize = args[1].toInt32();
		var vaProtect = args[3];
        console.log("VA called.\n Size:" + vaSize + " Protect:" + vaProtect);
		
    },
	onLeave: function(retVal)
	{
		console.log("VA ret:" + retVal);
	}
	
});

var createFileWAddr = Module.getExportByName(null, "CreateFileW");
 Interceptor.attach(createFileWAddr, {
        onEnter: function(args) {
            console.log('CreateFileW() call');
            console.log('path:' + args[0].readUtf16String());
            }
    });

var createFileAAddr = Module.getExportByName(null, "CreateFileA");
 Interceptor.attach(createFileAAddr, {
        onEnter: function(args) {
            console.log('CreateFileA() call');
            console.log('path:' + args[0].readAnsiString());
            }
    });	

Пример 2

Я скачал рандомный образец (RemcosRAT) с malware bazaar:

Попробуем посмотреть, что делает малварь с файлами следующим скриптом фрида:

var createFileWAddr = Module.getExportByName(null, "CreateFileW");
 Interceptor.attach(createFileWAddr, {
        onEnter: function(args) {
            console.log('path:' + args[0].readUtf16String());
            }
    });

В результате получим открываемые файлы малварью:

Среди путей видим интересный файл install.vbs, который лежит в папке %TEMP%. Откроем эту папку и видим, что созданный файл отсутствует.

Можно предположить, что он после использования удаляется. Как нам поступить? Мы можем перехватить записываемые данные в файл и сохранить их до удаления. Для этого напишем скрипт перехвата вызова WriteFile, будем сохранять буфер в отдельный файл:

var writeFileAddr = Module.getExportByName(null, "WriteFile");
 Interceptor.attach(writeFileAddr, {
        onEnter: function(args) {
			
			var buff = args[1];
			var size = args[2].toInt32();
			console.log("WriteFile | size = " + size)
			
			console.log(hexdump(buff));
			
			var dump = buff.readByteArray(size);
			var filename = "D:\\dumps\\" + Math.random() + "_dump.bin";
			var file = new File(filename, "wb");
			file.write(dump);
			file.flush();
			file.close();
            }
    });

В результате мы сохранили целевой скрипт, который и вправду самоудаляется после выполнения:

Дамп памяти Android приложения

С помощью Frida можно дампнуть память любого процесса.

  1. Скачиваем любую apk, в нашем случае это будет mcdonalds

  2. Устанавливаем apk

    adb install mcodnalds.apk

  3. Копируем фрида сервер на эмулятор/телефон и запускаем его

adb push frida-server-12.7.26-android-x86_64 /data/local/tmp/frida

chmod +x /data/local/tmp/frida

/data/local/tmp/frida &

  1. Устанавливаем fridump

    git clone https://github.com/Nightbringer21/fridump.git

  2. Открываем приложение mcdonalds. Вводим в поле пароль/телефон и нажимаем Войти!

  1. Делаем дамп процесса. Дампы сохранятся в папку dump

    python fridump.py -U -v -s mcdonalds

  2. Ищем в дампе наш пароль

    grep -arni "pass123" dump/*

Frida Android example aka UnCrackable App for Android Level 2

Описание задания второго уровня

This app holds a secret inside. May include traces of native code.

Objective: A secret string is hidden somewhere in this app. Find a way to extract it.

Описание уже содержит хорошую подсказку, но не будем забегать вперед. Скачиваем apk, устанавливаем на android эмулятор, запускаем. Видим, как и в первом задании, детектирование рута

Давайте обойдем эти проверки другим способом (не выпиливанием кода из apk), с помощью Frida. Это фреймворк, который позволяет вмешиваться в работу приложения, во время выполнения. Инструкция по установке проста:

pip install frida-tools

Далее нам необходимо скачать подходящий сервер frida отсюда. У меня эмулятор android x86, поэтому я скачал архив с названием frida-server-12.7.26-android-x86.xz. Распаковываем и закидываем на эмулятор

adb push frida-server-12.7.26-android-x86_64 /data/local/tmp/frida

Запускаем

chmod +x /data/local/tmp/frida
/data/local/tmp/frida &

После этого, мы должны командой frida-ps -U получить список процессов на эмуляторе, чтобы убедиться, что Frida установлена правильно и сервер запущен

Теперь возвращаемся к приложению и детектированию рута. Оно по прежнему происходит при помощи трех функций, которые мы видим после invoke-static

    invoke-static {}, Lsg/vantagepoint/a/b;->a()Z

    move-result v0

    if-nez v0, :cond_0

    invoke-static {}, Lsg/vantagepoint/a/b;->b()Z

    move-result v0

    if-nez v0, :cond_0

    invoke-static {}, Lsg/vantagepoint/a/b;->c()Z

    move-result v0

С помощью Frida, мы можем заставить эти функции всегда возвращать false и обходить проверку. Для этого нам необходимо написать скрипт на javascript. Чтобы работать с java классами, мы должны использовать обертку, для нашего кода (подробнее о API к java, читайте в официальной документации)

Java.perform(function() {
    //our code...
})

Чтобы получить доступ к классу sg.vantagepoint.a.b (в котором находятся наши проверки), мы должны использовать метод Java.use(classname)

Java.perform(function() {

	var antiRootClass = Java.use("sg.vantagepoint.a.b");
})

Теперь имея доступ к нужному классу, зная имя метода, мы можем переопределить его поведение на возврат false

antiRootClass.a.implementation = function() {
		return false;
	}

Повторим данный код, для всех трех методов и в итоге получим

console.log("Script started...");

Java.perform(function() {

	var antiRootClass = Java.use("sg.vantagepoint.a.b");
	antiRootClass.a.implementation = function() {
		return false;
	}
	console.log("sg.vantagepoint.a.b.a modified");
	
	antiRootClass.b.implementation = function() {
		return false;
	}
	console.log("sg.vantagepoint.a.b.b modified");
	
	antiRootClass.c.implementation = function() {
		return false;
	}
	console.log("sg.vantagepoint.a.b.c modified");
})

Чтобы запустить этот скрипт, вам необходимо знать package name приложения. Его можно получить из манифеста или запустив приложение и посмотрев все той же командой frida-ps.

Сохраняем код скрипта в файл с именем owasp2.js и выполняем такую команду

frida -U -l owasp2.js --no-paus -f owasp.mstg.uncrackable2

которая сама стартует приложение и выполняет скрипт. В итоге мы не получаем окно, так как все наши проверки рута были отключены

Мы получили доступ к вводу секретной строки. Вводим что-нибудь

появилось окно. Ищем в исходниках место, где встречается эта строка и идет проверка. Таким местом оказался метод verify() в классе MainActivity. Его сокращенная версия


    ...

    invoke-virtual {p1}, Landroid/widget/EditText;->getText()Landroid/text/Editable;

    move-result-object p1

    iget-object v1, p0, Lsg/vantagepoint/uncrackable2/MainActivity;->m:Lsg/vantagepoint/uncrackable2/CodeCheck;

    invoke-virtual {v1, p1}, Lsg/vantagepoint/uncrackable2/CodeCheck;->a(Ljava/lang/String;)Z

    move-result p1

    if-eqz p1, :cond_0

    const-string p1, "Success!"

Здесь мы видим, что введенный нами текст в EditText, отправляется в функцию sg/vantagepoint/uncrackable2/CodeCheck;->a и если все удачно, нам говорят “Success!”. Очевидно, что именно там идет проверка нашего ввода на правильность. Откроем класс sg.vantagepoint.uncrackable2.CodeCheck

.method private native bar([B)Z
.end method


# virtual methods
.method public a(Ljava/lang/String;)Z
    .locals 0

    invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B

    move-result-object p1

    invoke-direct {p0, p1}, Lsg/vantagepoint/uncrackable2/CodeCheck;->bar([B)Z

    move-result p1

    return p1
.end method

Наш ввод, передается в функцию bar()

invoke-direct {p0, p1}, Lsg/vantagepoint/uncrackable2/CodeCheck;->bar([B)Z

Обратите внимание на модификатор native функции

.method private native bar([B)Z

Модификатор native означает, что реализация метода находится в библиотеках, написанных на других языках. Чтобы работать с такими библиотеками, используется механизм JNI (Java Native Interface). С помощью этого интерфейса, андроид приложение может вызывать код на С/С++. Значит, наше приложение использует внешнюю библиотеку. Найдем доказательство этому и сделаем поиск по исходникам, по слову loadLibrary. Находим следующий код в MainActivity

# direct methods
.method static constructor <clinit>()V
    .locals 1

    const-string v0, "foo"

    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    return-void
.end method

Этот код говорит нам о том, что приложение загружает библиотеку с именем foo. Если мы распакуем наше приложение, то в папке lib мы видим библиотеку libfoo.so. Теперь пришло время изучить ее. Так как у нас нет исходных кодов библиотеки и написана она на С/С++, то нам необходимо воспользоваться дизассемблером, в нашем случае - Binary Ninja. Можно использовать бесплатную Веб версию.

Открываем файл нашей библиотеки и находим в списке нашу функцию bar

Открываем функцию. Углубляться в анализ ассемблерного кода мы сегодня не будем, возможно в другой жизни это сделаем. Просто обратите внимание на выделенный код и в особенности на hex значения

на адреса они не похожи, а похожи они на строки. Попробуем посмотреть опцией “отобразить, как символы”

Получилось вот так и это похоже на правду

Проделаем для всех значений

Эти части складываются в единную строку “Thanks for all the fish”. Далее по коду мы видим сравнение нашего ввода с этой строкой

Проверим строку в приложении

Это и есть наша секретная строка!

Frida TLS

Подробное обьяснение принципа работы

С помощью Frida можно расшифровывать schannel TLS трафик (IIS, RDP, IE, Outlook, Powershell, LDAP …). Для этого нам понадобится скрипт keylog.js, wireshark и для демонстрации будем использовать Windows 11.

SChannel a.k.a Secure Channel - это подсистема windows, используемая приложениями при работе с TLS (установка соединения, прием соединения от клиента).

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

Скрипт хукает функции библиотеки ncrypt.dll. Например, функция SslHashHandshake используется для генерации хэша во время SSL Handshake.

var shh = Module.findExportByName('ncrypt.dll', 'SslHashHandshake');
if(shh != null){
	Interceptor.attach(shh, {

Дампает входящие аргументы функций связанных с TLS в кейлог файл, который затем добавляется в Wireshark.

  1. Чтобы фрида смогла приатачится к lsass.exe, в настройках Exploit protection выставляем hardware-enforced stack protection off.

  1. В скрипте keylog.js указываем путь к логу. Узнаем PID процесса lsass.exe и аттачимся к нему командой

frida -p PID -l SCRIPT

  1. Открываем Wireshark

  2. Делаем какой либо HTTS запрос. Например:

Invoke-WebRequest -Uri "https://www.google.de" -Method GET -TimeoutSec 5

Убеждаемся, что видим только зашифрованный трафик.

  1. Файл лога keylog.log должен заполниться значениями.

  2. В Wireshark, в настройках Edit->Preferences->Protocols->TLS вводим путь к логу в (Pre)-Master-Secret log filename

  3. Трафик, который был у нас в Wireshark расшифруется.

Чистый запрос Invoke-WebRequest

Чистый ответ гугла

Frinet

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

Ссылки:

https://hex-rays.com/blog/plugin-focus-frinet/

https://blog.ret2.io/2021/04/20/tenet-trace-explorer/

Полезные ссылки

Для 2600 митапа я подготавливал презентацию по возможностям фриды.

Android greybox fuzzing with AFL++ Frida mode

https://8ksec.io/advanced-frida-usage-part-1-ios-encryption-libraries-8ksec-blogs/

Вверх