Вступление
После прочтения нескольких райтапов на реверс Nanomites ( метод антиотладки, при котором родительский процесс запускает дочерний, с которым постоянно обменивается информацией, что и затрудняет дебаг) понял, что проще всего с этим бороться при помощи инъекции в процесс с переопределением функции ptrace(мб не только ее). Проще всего это реализовать при помощи загрузки своей библиотеки через LD_PRELOAD.
Собсна, сами райтапы:
Пример инъекции в процесс через LD_PRELOAD
Код в main.c:
#include "stdio.h"
int main(){
puts("some message");
return 0;
}
Код lib.c:
#define _GNU_SOURCE
#include <stdio.h>
#include "dlfcn.h"
int (*default_puts)(const char* message);
int puts(const char *message)
{
printf("OUR NEW MESSAGE: %s\n", message);
return 0;
}
__attribute__((constructor)) static void Constructor(void) {
default_puts = dlsym(RTLD_NEXT, "puts");
default_puts("constructor called\n");
}
__attribute__((destructor)) static void Destructor(void) {
default_puts("destructor called\n");
}
Компиляция и запуск:
$ gcc lib.c -o mylib.so -fPIC -shared -ldl
$ gcc main.c -o f
$ LD_PRELOAD=./mylib.so ./f
constructor
OUR NEW MESSAGE: some message
destructor
Таск Nanomities с одной CTF
Суть задачи заключается в том, что родитель запускает дочерний процесс, который исполняет шеллкод, прерываясь с помощью int 3, чтобы дать родителю информацию о регистрах.
Код функции main:
Главный процесс сначала запускает процес, а затем берет или кладет в него регистры, используя PTRACE_SETREGS и PTRACE_GETREGS, затем продолжает его исполнение с помощью PTRACE_CONT. Дочерний процесс же запускает child_signal (изначально там была проверка, что процесс никто не отлаживает, которую я убрал, оставив вызов ptrace c PTRACE_TRACEME).
Сам child_signal запускает handler, который исполняет шеллкод (func_rodata), передавая в edi указатель на ввод пользователя. Сам шеллкод довольно длинный, вот его небольшая часть:

Напишем хук для перехвата общения родительского и дочернего процессов, происходящего через PTRACE_SETREGS и PTRACE_GETREGS:
#define _GNU_SOURCE
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include "sys/user.h"
#include <sys/ptrace.h>
typedef struct user_regs_struct user_regs_t;
char* request_names[0xf] = {"PTRACE_TRACEME", "", "", "", "", "", "", "PTRACE_CONT", "", "", "", "", "PTRACE_GETREGS", "PTRACE_SETREGS", ""};
long int ptrace(enum __ptrace_request __request, ...){
va_list list;
va_start(list, __request);
pid_t pid = va_arg(list, pid_t);
void* addr = va_arg(list, void*);
user_regs_t *regs = va_arg(list, user_regs_t*);
long int (*orig_ptrace)(enum __ptrace_request __request, pid_t pid, void *addr, void* data) = dlsym(RTLD_NEXT, "ptrace");
long int result = orig_ptrace(__request, pid, addr, regs);
printf("PTRACE(%s, pid=%d, addr=%lx, data=%lx) -> %lx \n", request_names[__request], pid, addr, regs, result);
if (__request == PTRACE_SETREGS || __request == PTRACE_GETREGS){
printf("%s: rip: 0x%lx, rax: %lx, rcx: %lx, rbx: %lx\n", request_names[__request], regs->rip, regs->rax, regs->rcx, regs->rbx);
}
return result;
}
Запуск бинаря с перехватом ptrace
Please input the flag:
flag_pochti
PTRACE(PTRACE_TRACEME, pid=0, addr=0, data=0) -> 0 // проверяет, что процесс никем не отлаживается
PTRACE(PTRACE_CONT, pid=1789, addr=0, data=8) -> 0 // возобновляет работу остановленного дочернего процесса
PTRACE(PTRACE_GETREGS, pid=1789, addr=0, data=7ffddb9b1ac0) -> 0 // копирует регистры дочернего процесса в структуру user_regs_struct по адресу data
PTRACE_GETREGS: rip: 0x400c77, rax: 1c9032282e381524, rcx: 0, rbx: 7fff427749597942
PTRACE(PTRACE_SETREGS, pid=1789, addr=0, data=7ffddb9b1ac0) -> 0 // кладет регистры из data в процесс
PTRACE_SETREGS: rip: 0x400c81, rax: 1c9032282e381524, rcx: 209a2a23162d490c, rbx: 7fff427749597942
...
Wrong

Следуя логике работы шеллкода, rax должно равняться rcx ^ rbx, то есть N0t_The_. Ок, запускаем с этим содержимым и видим, как успешно проходим проверку.
Please input the flag:
N0t_The_aaaaaaa
...
PTRACE(PTRACE_SETREGS, pid=1971, addr=0, data=7ffc98d45570) -> 0
PTRACE_SETREGS: rip: 0x400c99, rax: 209a2a23162d490c, rcx: 209a2a23162d490c, rbx: 7fff427749597942
PTRACE(PTRACE_CONT, pid=1971, addr=0, data=0) -> 0
PTRACE(PTRACE_GETREGS, pid=1971, addr=0, data=7ffc98d45570) -> 0
PTRACE_GETREGS: rip: 0x400ca3, rax: 7f9e231628381823, rcx: 209a2a23162d490c, rbx: 7fff427749597942
PTRACE(PTRACE_SETREGS, pid=1971, addr=0, data=7ffc98d45570) -> 0
PTRACE_SETREGS: rip: 0x400cad, rax: 7f9e231628381823, rcx: d9011282e6d1504, rbx: 7fff427749597942
PTRACE(PTRACE_CONT, pid=1971, addr=0, data=0) -> 0
...
Wrong

Тут rax все так же должен быть равен rcx ^ rbx, но полученная строка уже будет Fl4g_Sor.
Еще одна проверка пройдена. Продолжаем запускать
Please input the flag:
N0t_The_Fl4g_Soraaaaaa
...
PTRACE(PTRACE_SETREGS, pid=1991, addr=0, data=7ffe2aeecd10) -> 0
PTRACE_SETREGS: rip: 0x400cc5, rax: d9011282e6d1504, rcx: d9011282e6d1504, rbx: 7fff427749597942
PTRACE(PTRACE_CONT, pid=1991, addr=0, data=0) -> 0
...
PTRACE(PTRACE_GETREGS, pid=1991, addr=0, data=7ffe2aeecd10) -> 0
PTRACE_GETREGS: rip: 0x400cda, rax: 15469, rcx: d9011282e6d1504, rbx: 7fff427749597942
PTRACE(PTRACE_SETREGS, pid=1991, addr=0, data=7ffe2aeecd10) -> 0
PTRACE_SETREGS: rip: 0x400cdb, rax: 15469, rcx: d9011282e6d1504, rbx: 7fff427749597942
PTRACE(PTRACE_CONT, pid=1991, addr=0, data=0) -> 0
...
Wrong

Тут двухбайтовый ax должен равняться 0x1130b-0x9999=0x7972, то есть ry. Запускаем, вводя N0t_The_Fl4g_Sorry. Странно, ответ все так же Wrong. Предположим, что инструкция cmp ax, 0D8FEh лежит там не просто так. Пробуем выставить ax равным 0xD8FE - 0x9999 = 0x3f65 = 'e?' и это срабатывает, прога выдает Good.

Ура, флаг получен, задача решена. Флаг не сдается, можете не проверять :)
Как получить настоящий флаг? Оставляю эту задачу вам
Last modified on 2022-05-29