• XSS.stack #1 – первый литературный журнал от юзеров форума

Обучаюсь плюсам

Посмотреть вложение 64174
Ну в дебаг режиме нормально же
Ну если тебе чисто шкафчики писать то там вообще все нормально.

Смотри есть такая тема как защитное программирование, это когда все обкладывают проверками и логами(обязательно очень много логов, телеметрия там критично важна) лишь бы код не наебнулся, смотрят телеметрию и отслеживают по логам где и что пошло не так. Обкладывают иррацинально, тупо все на свете проверяют и отслеживают. Где и для чего это нужно думаю ты и сам догадаешся и сколько человекочасов туруда оверхеда ты тоже поймешь.

За пределами защитного программирования.
Ассерты нужны там где ты получаешь данные над которыми не имеешь контроля, вот ты эти прилетевшие(по сети, из апи, из файла, ...) проверяешь на адекватность и кидаешь свои ассерты когда что то не так, внутри кода который не может получить непредсказуемые данные это просто брать и размазывать грязь, тупо просирать свое и чужое время. Еще везде напихать ассертов любят те кто не пишет тестов, и в одном и том же продукте эти красавцы ловят баги годами, а по скольку не пишут тестов то это бывает что один и тот же баг исправляют по нескольку раз.
Еще ассерты нужны в тестах - AAA(Arrange, Act, Assert).

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

Погугли про антипаттерны программирования.
 
Ну если тебе чисто шкафчики писать то там вообще все нормально.
Я формашлепчик, мне оно для души зачастую

Еще везде напихать ассертов любят те кто не пишет тестов
Не в бровь, а в глаз. Ты реально прав, то про меня - вообще ни разу в жизни теста не написал,
к сожалению нужно будет посмотреть что к чему, походу пора учится
 
Я формашлепчик, мне оно для души зачастую


Не в бровь, а в глаз. Ты реально прав, то про меня - вообще ни разу в жизни теста не написал,
к сожалению нужно будет посмотреть что к чему, походу пора учится
Читать - Чистая архитектура, Чистый код, Совершенный код.
Не обращай внимание на используемый в примерах язык, там это роли не играет.
 
Читать - Чистая архитектура, Чистый код, Совершенный код.
Ок, спасибо большое!
 
Всем привет. У меня очень много гемора возникает с тем как создавать в c++ проекты с несколькими файлами. Когда нужно отделить какую-то функциональность я создаю условный testfile.cpp и testfile.h . В testfile.h объявления а в testfile.cpp определения. Всё работало хорошо, я сделал include условного testfile.h в главный файл проекта и спокойно использовал классы и функции из него. Однако, когда мне понадобилось создать ещё один файл и использовать там testfile.h начала происходить какая-то фигня - я могу вызывать функции внутри нового файла .cpp , но не могу в новом файле .h

encrypt.h
1693976149445.png

Возникает ошибка.

encrypt.cpp
1693976435301.png


А тут всё компилируется без ошибок.

Если класс объявлен внутри WINRTL.h то всё работает
1693976652772.png


Помогите, пожалуйста) А ещё - можно ли как-то писать код только в отдельном .cpp файле без .h файла, постоянно бегать и менять всё в .cpp файле а потом лезть .h не очень удобно
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну вряд ли кто то из местных будет гореть желанием тебе помогать, если ты даже не зная основ языка уже сел писать локер))
 
Всем привет. У меня очень много гемора возникает с тем как создавать в c++ проекты с несколькими файлами. Когда нужно отделить какую-то функциональность я создаю условный testfile.cpp и testfile.h . В testfile.h объявления а в testfile.cpp определения. Всё работало хорошо, я сделал include условного testfile.h в главный файл проекта и спокойно использовал классы и функции из него. Однако, когда мне понадобилось создать ещё один файл и использовать там testfile.h начала происходить какая-то фигня - я могу вызывать функции внутри нового файла .cpp , но не могу в новом файле .h
Ошибка была в том что у меня был #include "encrypt.h" в WINRTL.h и #include "WINRTL.h" в encrypt.h.
Рекомендую хотя бы туториалы по плюсам посмотреть
Обычно не задаю вопросы которые гуглятся за полсекунды или решаются через chatGPT, решение этой проблемы для меня было не столь очевидным.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ошибка была в том что у меня был #include "encrypt.h" в WINRTL.h и #include "WINRTL.h" в encrypt.h.

Обычно не задаю вопросы которые гуглятся за полсекунды или решаются через chatGPT, решение этой проблемы для меня было не столь очевидным.
Исправляется при помощи include guard. Как пример encrypt.h:
C:
#ifndef _ENCRYPT_H_INCLUDED_
#define _ENCRYPT_H_INCLUDED_

# code

#endif   /* _ENCRYPT_H_INCLUDED */
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Исправляется при помощи include guard. Как пример encrypt.h:
C:
#ifndef _ENCRYPT_H_INCLUDED_
#define _ENCRYPT_H_INCLUDED_

# code

#endif   /* _ENCRYPT_H_INCLUDED */
Или просто #pragma once в начале заголовочного файла, выйдет немного компактнее
 
Я решил проблему с использованием unique_ptr, получилось собрать хелоу ворлд, при этом выделив память для класса и в импорте пусто.
C++:
#include <Windows.h>
#include <memory>
#include "WINAPI.h"
//#include "Loader.h"
#include "DYNMEM.h"
//#include "syscall.h"                //for test ( delete this)




class TestMem : public DynMem::WinDynMem
{
public:
    int age;
    LPSTR text;
};


int main()
{
    DynMem::MemoryResource res;
    DynMem::WinDynMem mem;
    mem.InitRes(&res);      //init memory functions and hHeap


    std::unique_ptr<WINIMPORT::WINIMPORT> winImportPtr(new(&res) WINIMPORT::WINIMPORT);     //init import
    WINIMPORT::WINIMPORT* winimport = winImportPtr.get();

    std::unique_ptr<TestMem> testPtr(new(&res) TestMem);
    TestMem* test = testPtr.get();
    test->text = "Hello from shellcode!!!";

    WINCALL(winimport, DLLID_USER32, MessageBoxA)(0, test->text, test->text, 0);
    return 0;
}

Решить проблему получилось путём создания класса:
C++:
#include "DYNMEM.h"
#include "WINRTL.h"
#include "NT_STRUCTS.h"
#include "OBFS.h"




    void DynMem::WinDynMem::operator delete(void* ptr)
    {
        WinDynMem* memObj = static_cast<WinDynMem*>(ptr);
        memObj->res->free_func(memObj->res->hHeap, 0, ptr);
    }

    void* DynMem::WinDynMem::operator new(size_t size, MemoryResource* res_)
    {
        if (size)
        {
            void* ptr = res_->alloc_func(res_->hHeap, 0, size);
            WinDynMem* memObj = static_cast<WinDynMem*>(ptr);
            memObj->res = res_;
            return ptr;
        }
        return nullptr;
    }

    void DynMem::WinDynMem::operator delete[](void* ptr)
    {
        WinDynMem* memObj = static_cast<WinDynMem*>(ptr);
        memObj->res->free_func(memObj->res->hHeap, 0, ptr);
    }

    void* DynMem::WinDynMem::operator new[](size_t size, MemoryResource* res_)
    {
        if (size)
        {
            void* ptr = res_->alloc_func(res_->hHeap, 0, size);
            WinDynMem* memObj = static_cast<WinDynMem*>(ptr);
            memObj->res = res_;
            return ptr;
        }
        return nullptr;
    }

    void DynMem::WinDynMem::unalloc(void* ptr)
    {
        this->res->free_func(this->res->hHeap, 0, ptr);
    }

    void* DynMem::WinDynMem::alloc(size_t size)
    {
        if (size)
        {
            return this->res->alloc_func(this->res->hHeap, 0, size);
        }
        return 0;
    }

    
    void DynMem::WinDynMem::InitRes(MemoryResource* res_)
    {
        HMODULE ntdll = WINRTL::GetNtdll();
        res_->alloc_func = (decltype(&RtlAllocateHeap))(WINRTL::HashGetProcAddress(obfs::CONStrHashA("RtlAllocateHeap"), ntdll));
        res_->free_func = (decltype(&RtlFreeHeap))(WINRTL::HashGetProcAddress(obfs::CONStrHashA("RtlFreeHeap"), ntdll));
        res_->hHeap = ((decltype(&GetProcessHeap))(WINRTL::HashGetProcAddress(obfs::CONStrHashA("GetProcessHeap"), WINRTL::GetKernel32())))();
    }
Структура MemoryResource заполняется: адресом HeapAlloc, HeapFree, и хэндла кучи. При вызове new я передаю MemoryResource как входной параметр, оператор new вызывает переданную функцию для выделения памяти. Каждый класс который хочет выделяться/освобождаться таким образом должен наследоваться от класса WinDynMem, этот класс содержит переопределённые new и delete и содержит поле MemoryResource res*. Во время вызова delete принимается параметр WinDynMem* (т.е. на вход так же могут передаваться классы, которые наследовались от WinDynMem), delete вызывает this->unallocfunc. В итоговом exe-шнике у меня нет импорта
1694688004048.png
 
Ещё давно заметил, что почему-то когда импортируешь некоторые функции из kernel32.dll, например, HeapAlloc по факту импортируется RtlAllocateHeap, раньше мне это особо не мешало - ну заменю импорт на RtlAllocateHeap из ntdll - окей. Но сейчас я пишу свой LoadPE и пытаюсь загрузить calc.exe, заметил что он у меня крашится и полез в дизассемблер, заметил там вызов AcquireSRWLockExclusive который заканчивается ошибкой, попробовал вызвать AcquireSRWLockExclusive в совоём коде и оказалось что ситуация та же самая что с HeapAlloc
1694769775834.png

Виндовый загрузчик ипортирует RtlAcquireSRWLockExclusive, вместо AcquireSRWLockExclusive. Может кто-то объяснить по какому принципу это происходит.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Может кто-то объяснить по какому принципу это происходит.
Это называется форвардинг импортов. В таблице экспорта вместо указателя на функцию лежит по сути ссылка вида "<имя_dll>.<имя_функции_в_dll>": в этом случае при поиске адреса функции будет загружена (или найдена в памяти) указанная dll, в которой уже будет происходить поиск функции по указанному имени. Такие вещи часто встречаются в системе, их даже можно использовать в DLL-Hijacking, хотя с точки зрения антивируса - это легко обнаружить.
 
Это называется форвардинг импортов. В таблице экспорта вместо указателя на функцию лежит по сути ссылка вида "<имя_dll>.<имя_функции_в_dll>": в этом случае при поиске адреса функции будет загружена (или найдена в памяти) указанная dll, в которой уже будет происходить поиск функции по указанному имени. Такие вещи часто встречаются в системе, их даже можно использовать в DLL-Hijacking, хотя с точки зрения антивируса - это легко обнаружить.
Про apiset таблицы ему расскажи, что бы он не искал потом длл которых нет.
 
Спасибо за ваши ответы, допили свою GetProcAddress теперь также работает форвардинг импортов. У меня возник, как по мне, довольно не однозначный вопрос. Я хочу сделать следующим образом - я с помощью indirect syscall читаю knowndlls/knowndlls32 и загружаю своим LoadPE dll-ку и радуюсь жизни, проблема в том что чтобы выполнить LoadPE нужно использовать WINAPI которые могут быть похуканы. Конечно, можно написать код вида:

Код:
if(UseNtAPI)
{
    base = NtAPI->NtAllocateVirtualMemory(.....
}
else
{
    base = import->VirtualAlloc(...
}
Такой подход мне не очень нравится. Насколько вообще во время LoadPE вызовы VirtualAlloc, VirtualProtect палевные, интересно ваше мнение.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Насколько вообще во время LoadPE вызовы VirtualAlloc, VirtualProtect палевные, интересно ваше мнение.
Вообще, весьма палевны, если флаг EXECUTE. Но не пойму, в чем проблема юзать сисколы для памяти (хотя сисколы тоже не панацея).
 
Написал класс который отвечает за вызов сисколов
C++:
#include <Windows.h>
#include <winternl.h>
#include "WINRTL.h"
#include "ntapi.h"

NtAPI::Syscall::Syscall()
{
    ntdll = WINRTL::GetNtdll();
    
    WINRTL::MemZero(&syscall_numbers, sizeof(syscall_numbers));
}

bool NtAPI::Syscall::IsSyscallStubClean(FARPROC proc)
{
    if (*(DWORD*)proc == syscall_stub64)
    {
        return true;
    }
    return false;
}

int NtAPI::Syscall::GetSyscallNumber(FARPROC proc)
{
    return  *(int*)((char*)proc + 4);
}

int NtAPI::Syscall::GetSyscallNumberUsingProcOffset(HASH ntprochash)
{
    WORD orig_ordinal = WINRTL::HashGetProcOrdinal(ntdll, ntprochash);

    for (int i = 1; 1 != 0; i++)
    {
        //get next entry
        FARPROC procaddr = WINRTL::GetProcByOrdinal(ntdll, orig_ordinal + i);
        if (IsSyscallStubClean(procaddr))
        {
            // syscall stub are not damaged
            //save syscall+ret byte sequence
            if (!indirectcall)
            {
                indirectcall = (FARPROC)((ULONG_PTR)procaddr + 0x12);
            }
            return GetSyscallNumber(procaddr)-i;
        }

    }
    return 0;
}

int NtAPI::Syscall::SafeGetSyscallNumber(HASH ntprochash)
{
    FARPROC proc = WINRTL::HashGetProcAddress(ntprochash, ntdll);
    if (!proc)
    {
        return 0;
    }
    if (!IsSyscallStubClean(proc))
    {
        //system call damaged... maybe hookey by AV, find near functions which not damaged and get syscall
        return GetSyscallNumberUsingProcOffset(ntprochash);
    }
    // syscall stub are not damaged
    //save syscall+ret byte sequence
    if (!indirectcall)
    {
        indirectcall = (FARPROC)((ULONG_PTR)proc + 0x12);
    }
    int syscall = GetSyscallNumber(proc);
    if (syscall > 0x300)
    {
        //this is strange -_- syscall number can't be more then 0x200 or 0x300
        return GetSyscallNumberUsingProcOffset(ntprochash);
    }
    return (int) syscall;
    
}

void NtAPI::Syscall::SetSystemCallNumber(HASH ntprochash)
{
    syscalln = SafeGetSyscallNumber(ntprochash);
}

[[clang::optnone]] NTSTATUS NtAPI::Syscall::SyscallStub(void* arg1, void* arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7)
{
    NTSTATUS statuscode;
    __asm
    {
        mov r10, arg1
        mov rdx, arg2
        mov r8, arg3
        mov r9, arg4
        mov eax, syscalln
        add rsp, 0x40
        call indirectcall
        sub rsp, 0x40
        mov eax, statuscode
    }
    return statuscode;
}
Для тех кому лень читать - ищется адрес функции в ntdll по хэшу, проверяется правильно стаба сискола, если всё хорошо возвращается номер системного вызова, если нет то ищется ординал этой функции, и начинают искаться адреса следующих по ординалу функций, если стаб сискола чист то вытаскивается номер сискола из сискола вычитается, сколько шагов по ординалам было сделано. Для вызова сискола сначала нужно сделать вызвать SetSystemCallNumber, после можно вызывать SyscallStub, сделано очень костыльно, не знал как сделать это нормально. Была идея вернуть адрес последовательности syscall + ret, и преобразовать функцию с помощью decltype и вызвать, это выглядело в разы лучше, но проблема в том что компилятор ложил адрес syscall + ret в rax, а rax мне нужен чтобы хранить там номер сискола.
Хочу заранее спросить насчёт SEH, достаточно много гайдов по SEH для win32, а вот для SEH Win64 могу особенно отметить вот эту статью https://www.osronline.com/article.cfm^article=469.htm Однако я так и не понял до конца как виндовый обработчик исключений работает. Как я понял в SEH32 вытаскивается список связанных структур с помощью сегментного регистра и как я понял обработчики лежат на стэке. В SEH64 есть специальная директория ExceptionTable, получается что всё что мне нужно чтобы обработать SEH при загрузке файла, это спроецировать секцию с таблицей исключений, исправить адреса релоками если требуется и изменить ImageBaseAddress в PEB. Получается что в WIN64 загрузчик лезет в сегментный регистр, достаёт ImageBaseAddress из PEB и находит таблицу исключений через OPTIONAL_HEADER IMAGE_DIRECTORY_ENTRY_EXCEPTION. Однако, примитивная программа на c++ которая использует SEH у меня не работает, вернее, исключения не обрабатываются.
 
Написал класс который отвечает за вызов сисколов
C++:
#include <Windows.h>
#include <winternl.h>
#include "WINRTL.h"
#include "ntapi.h"

NtAPI::Syscall::Syscall()
{
    ntdll = WINRTL::GetNtdll();
 
    WINRTL::MemZero(&syscall_numbers, sizeof(syscall_numbers));
}

bool NtAPI::Syscall::IsSyscallStubClean(FARPROC proc)
{
    if (*(DWORD*)proc == syscall_stub64)
    {
        return true;
    }
    return false;
}

int NtAPI::Syscall::GetSyscallNumber(FARPROC proc)
{
    return  *(int*)((char*)proc + 4);
}

int NtAPI::Syscall::GetSyscallNumberUsingProcOffset(HASH ntprochash)
{
    WORD orig_ordinal = WINRTL::HashGetProcOrdinal(ntdll, ntprochash);

    for (int i = 1; 1 != 0; i++)
    {
        //get next entry
        FARPROC procaddr = WINRTL::GetProcByOrdinal(ntdll, orig_ordinal + i);
        if (IsSyscallStubClean(procaddr))
        {
            // syscall stub are not damaged
            //save syscall+ret byte sequence
            if (!indirectcall)
            {
                indirectcall = (FARPROC)((ULONG_PTR)procaddr + 0x12);
            }
            return GetSyscallNumber(procaddr)-i;
        }

    }
    return 0;
}

int NtAPI::Syscall::SafeGetSyscallNumber(HASH ntprochash)
{
    FARPROC proc = WINRTL::HashGetProcAddress(ntprochash, ntdll);
    if (!proc)
    {
        return 0;
    }
    if (!IsSyscallStubClean(proc))
    {
        //system call damaged... maybe hookey by AV, find near functions which not damaged and get syscall
        return GetSyscallNumberUsingProcOffset(ntprochash);
    }
    // syscall stub are not damaged
    //save syscall+ret byte sequence
    if (!indirectcall)
    {
        indirectcall = (FARPROC)((ULONG_PTR)proc + 0x12);
    }
    int syscall = GetSyscallNumber(proc);
    if (syscall > 0x300)
    {
        //this is strange -_- syscall number can't be more then 0x200 or 0x300
        return GetSyscallNumberUsingProcOffset(ntprochash);
    }
    return (int) syscall;
 
}

void NtAPI::Syscall::SetSystemCallNumber(HASH ntprochash)
{
    syscalln = SafeGetSyscallNumber(ntprochash);
}

[[clang::optnone]] NTSTATUS NtAPI::Syscall::SyscallStub(void* arg1, void* arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7)
{
    NTSTATUS statuscode;
    __asm
    {
        mov r10, arg1
        mov rdx, arg2
        mov r8, arg3
        mov r9, arg4
        mov eax, syscalln
        add rsp, 0x40
        call indirectcall
        sub rsp, 0x40
        mov eax, statuscode
    }
    return statuscode;
}
Для тех кому лень читать - ищется адрес функции в ntdll по хэшу, проверяется правильно стаба сискола, если всё хорошо возвращается номер системного вызова, если нет то ищется ординал этой функции, и начинают искаться адреса следующих по ординалу функций, если стаб сискола чист то вытаскивается номер сискола из сискола вычитается, сколько шагов по ординалам было сделано. Для вызова сискола сначала нужно сделать вызвать SetSystemCallNumber, после можно вызывать SyscallStub, сделано очень костыльно, не знал как сделать это нормально. Была идея вернуть адрес последовательности syscall + ret, и преобразовать функцию с помощью decltype и вызвать, это выглядело в разы лучше, но проблема в том что компилятор ложил адрес syscall + ret в rax, а rax мне нужен чтобы хранить там номер сискола.
Хочу заранее спросить насчёт SEH, достаточно много гайдов по SEH для win32, а вот для SEH Win64 могу особенно отметить вот эту статью https://www.osronline.com/article.cfm^article=469.htm Однако я так и не понял до конца как виндовый обработчик исключений работает. Как я понял в SEH32 вытаскивается список связанных структур с помощью сегментного регистра и как я понял обработчики лежат на стэке. В SEH64 есть специальная директория ExceptionTable, получается что всё что мне нужно чтобы обработать SEH при загрузке файла, это спроецировать секцию с таблицей исключений, исправить адреса релоками если требуется и изменить ImageBaseAddress в PEB. Получается что в WIN64 загрузчик лезет в сегментный регистр, достаёт ImageBaseAddress из PEB и находит таблицу исключений через OPTIONAL_HEADER IMAGE_DIRECTORY_ENTRY_EXCEPTION. Однако, примитивная программа на c++ которая использует SEH у меня не работает, вернее, исключения не обрабатываются.

******
совсем плохо
if (syscall > 0x300)
{
//this is strange -_- syscall number can't be more then 0x200 or 0x300

******
лучше
#define MAXIMUM_SYSCALL_ORDINAL (0x300)

if (syscall > MAXIMUM_SYSCALL_ORDINAL)
нет нужды в дополнительном коментарии и так все ясно
{
warning("Syscall out of range
****
хорошо
if (!IsValidSyscallOrdinal(syscall))
нет нужды в дополнительном коментарии и так все ясно, еще нет хардкода порогового значения, но есть возможность проверять валидность в зависимости от контекста
{
warning("Invalid syscall ordinal
****

А еще сисколлы не только в ндлл, и там номера сисколлов будут выше (NtVisualCaptureBits 5409), то есть закладывай что это контекстно зависимое значение.
Еще сисколлы тебе возможно придется получать не из спроецированного нтдлл а из прочитанного из файла.
Вообще избавляй от меджиков и нотифицируй атрибуты класса типа int m_attributeName, это не вкусовщина а вопрос читаемости кода.

Да вот еще что если уж ты назвал функцию safe то убеждайся что там есть эти +12.
Скажу больше - тебе понадобится свой класс для работы с "не своей памятью" без эксепшенов, этот класс будет кешировать указанные регионы и флушить их по мере надобности, сразу делай так что бы этот класс работал не только с памятью своего процесса но любого другого не взирая на свою и чужую битность, тебе с ним придется повозится но он тебе сэкономит много времени потом.
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх