• Познакомьтесь с пентестом веб-приложений на практике в нашем новом бесплатном курсе

    «Анализ защищенности веб-приложений»

    🔥 Записаться бесплатно!

  • CTF с учебными материалами Codeby Games

    Обучение кибербезопасности в игровой форме. Более 200 заданий по Active Directory, OSINT, PWN, Веб, Стеганографии, Реверс-инжинирингу, Форензике и Криптографии. Школа CTF с бесплатными курсами по всем категориям.

Статья ASM для х86. (4.0) Системные механизмы Windows


В этой части:
  1. Процессы и их потоки
  2. Адресное пространство процесса
  3. Вторжение в чужое пространство памяти
  4. Перехват API-функций
  5. Скрытие процессов
Для просмотра структур системы, на данном этапе понадобится ядерный отладчик WinDbg. Он поставляется как в составе пакета WDK, так и отдельно. Версию на любой вкус и цвет можно Лично у меня версия 6, интерфейс которой отличается от 10-ой.


4.0. Процессы и их потоки

4.0.0. Структуры KPROCESS и KTHREAD

Запущенная на исполнение программа представляет из-себя самостоятельный процесс. У любого процесса есть как минимум один исполнительный поток Thread, который назвали ‘основным потоком’. Этот основной поток при помощи функции CreateThread() может порождать ещё-и-ещё дополнительных потоков так, что процесс становится многопоточным. У каждого потока своё адресное пространство, свой стек и свои регистры. Определение ‘свои регистры’ подразумевает их сохранение в сегменте состоянии задачи TSS Task-State-Segment, при переключении потоков планировщиком Sheduler. Таким образом, базовая единица, которая выполняется в системе - это поток, принадлежащий родительскому процессу.

Поскольку всем заправляет ядро оси Ntoskrnl.exe, процессы и потоки рождаются в нём. На программном уровне, каждый процесс представлен структурой ядра KPROCESS и её расширением исполнительной системы EPROCESS. Соответственно каждый поток - структурами (K/E)THREAD. Эти структуры сильно варьируется от версии к версии ядра, поэтому нет смысла приводить тут полное их описание. В узких кругах программные потоки называют ‘тредами’ от слова Thread.

Что-же представляют из себя системные структуры? Разобраться в этом поможет WinDbg. Для отображения структур в нём имеется команда dt, что означает Display-Type. Эта команда может искать структуры по-маске в определённом модуле DLL. В качестве модуля мы используем Ntoskrnl.exe под кличкой nt!, в качестве маски – ‘thread’. Запускаем WinDbg в локальном режиме ядра [Ctrl+K --> Local] и ищем структуры:


wdbg_0.png


..и точно есть такие, причём в модуле Ntoskrnl.exe, подобраться к которому из юзера будет проблематично - нужен драйвер. Нашей программе доступны только системные библиотеки Ntdll.dll, Kernel32.dll, и прочии, которые система отображает в пространство каждого процесса (отображаемые в память файлы). Ну и ладно.. Всё-равно проникновение в ядро пока не входит в наши планы, мы просто проведём туда экскурсию, а гидом будет WinDbg.

Так-как нужно содержимое структур конкретно нашего процесса, нужно повесить исследуемый процесс в память, чтобы WinDbg вытягивал информацию именно с него. Для этих целей как-нельзя лучше подходит обычный ‘HelloWorld’ с кнопкой ОК, которую на всё время экспериментов нажимать не будем.

C-подобный:
include 'win32ax.inc'
;-------
.data
text    db  'Hello World!',0
;-------
.code
start:  invoke  MessageBox,0,text,0,0
        invoke  ExitProcess,0
.end start
Теперь запускаю этот экзешник и даю WinDbg команду !process 0 0, на что отладчик ответит листингом всех процессов в памяти. Как видно мой – последний, и его структура EPROCESS находится в ядерной памяти по адресу 0х88f72020. Ядерная она потому, что имеет адрес выше 0х80000000, а юзерская область находится ниже, ..до адреса 0x7fffffff (младшая половина). Судя по адресам можно сделать вывод, что в моей юзерской памяти находится только структура РЕВ и DirBase:

wdbg_1.png


Теперь у меня есть личный адрес, который я могу подставлять во-все команды и отладчик будет возвращать мне информацию только о данном процессе. Значит для просмотра структуры KTHREAD мне нужно ввести: dt _kthread 88f72020, на что получаю такой ответ:

Код:
lkd> dt _kthread 88f72020
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY [ 0x88f72030 - 0x88f72030 ]
   +0x018 InitialStack     : 0x66968000 Void
   +0x01c StackLimit       : 0x0af69000 Void
   +0x020 Teb              : (null)
   +0x024 TlsArray         : (null)
   +0x028 KernelStack      : (null)
   +0x02c DebugActive      : 0 ''
   +0x02d State            : 0 ''
   +0x02e Alerted          : [2]  ""
   +0x030 Iopl             : 0xac ''
   +0x031 NpxState         : 0x20 ' '
   +0x032 Saturation       : 0 ''
   +0x033 Priority         : 0 ''
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : 0
   +0x050 IdleSwapBlock    : 0xa0 ''
   +0x051 Spare0           : [3]  "v!??????"
   +0x054 WaitStatus       : 0n-1994295648
   +0x058 WaitIrql         : 0 ''
   +0x059 WaitMode         : 0 ''
   +0x05a WaitNext         : 0 ''
   +0x05b WaitReason       : 0 ''
   +0x05c WaitBlockList    : 0x00000001 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY [ 0x6080000 - 0x100 ]
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : 0x3a000000
   +0x06c BasePriority     : 0 ''
   +0x06d DecrementCount   : 0 ''
   +0x06e PriorityDecrement : 0 ''
   +0x06f Quantum          : 0 ''
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : 0xa6a077e8 Void
   +0x0d4 KernelApcDisable : 0
   +0x0d8 UserAffinity     : 0x40001
   +0x0dc SystemAffinityActive : 0 ''
   +0x0dd PowerState       : 0 ''
   +0x0de NpxIrql          : 0 ''
   +0x0df InitialNode      : 0 ''
   +0x0e0 ServiceTable     : 0x88f72100 Void
   +0x0e4 Queue            : 0x88f72100 _KQUEUE
   +0x0e8 ApcQueueLock     : 0
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY [ 0x0 - 0x893de290 ]
   +0x120 SoftAffinity     : 0x88f74bd0
   +0x124 Affinity         : 0
   +0x128 Preempted        : 0x56 'V'
   +0x129 ProcessReadyQueue : 0 ''
   +0x12a KernelStackResident : 0 ''
   +0x12b NextProcessor    : 0 ''
   +0x12c CallbackStack    : (null)
   +0x130 Win32Thread      : 0xe368e438 Void
   +0x134 TrapFrame        : (null)
   +0x138 ApcStatePointer  : [2] 0xe3c97f58 _KAPC_STATE
   +0x140 PreviousMode     : 24 ''
   +0x141 EnableStackSwap  : 0x58 'X'
   +0x142 LargeStack       : 0x4e 'N'
   +0x143 ResourceIndex    : 0x89 ''
   +0x144 KernelTime       : 0
   +0x148 UserTime         : 0x30
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : 0x80 ''
   +0x165 ApcStateIndex    : 0x21 '!'
   +0x166 ApcQueueable     : 0xf7 ''
   +0x167 AutoAlignment    : 0x88 ''
   +0x168 StackBase        : (null)
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY [ 0x7ffd7000 - 0x0 ]
   +0x1b8 FreezeCount      : 1 ''
   +0x1b9 SuspendCount     : 0 ''
   +0x1ba IdealProcessor   : 0 ''
   +0x1bb DisableBoost     : 0 ''

Судя по листингу - довольно информативная база потока, при чём у каждого из потоков она своя. В добавок, внутри данной структуры есть и несколько вложенных структур, которые предваряются нижним подчёркиванием. Для их просмотра нужно добавить к основной команде имя интересующего поля, и поставить в конце точку. Например, для поля по нулевому смещению _DISPATCHER_HEADER это выглядит так:
C-подобный:
lkd> dt _kthread 88f72020 header.
nt!_KTHREAD
   +0x000 Header  :
      +0x000 Type         : 0x3 ''
      +0x001 Absolute     : 0 ''
      +0x002 Size         : 0x1b ''
      +0x003 Inserted     : 0 ''
      +0x004 SignalState  : 0n0
      +0x008 WaitListHead : _LIST_ENTRY [ 0x88f72028 - 0x88f72028 ]
Описывать все поля структуры KTHREAD не имеет смысла, т.к. их имена говорят сами-за-себя, тем-более что львиная часть нам вообще не интересна. Остановимся только на некоторых из них (цифра – это смещение от начала структуры).

0x00 - заголовок ‘Header’.
С этой структуры начинаются все ожидабельные объекты ядра, т.е. те объекты, которые могут быть переданы функциям ожидания KeWaitFor*** для поддержки синхронизации работы нескольких потоков, между собой.

0x18 – InitialStack, StackLimit и KernelStack;
Это соответственно начальный стек, лимит стека и текущий ядерный стек потока.
Обычно под начальный стек, потокам предоставляются три 4К-байтные страницы памяти, что составляет 12 килобайт. Но такой размер не предел для стека. Четвёртая страница там сторожевая GUARD-PAGE по достижении которой, система выделит ещё одну страницу, и сторожевой станет пятая, и т.д. Армагедоном будет ситуация, когда программа исчерпает весь стек и он достигнет секции-кода (они двигаются навстречу друг-другу). На х32 в этом случае ничего уже не спасёт и процесс тупо исчезнет, т.к. система не обрабатывает исключения подобного рода. На х64 системах стек фактически бесконечен, поэтому такой ситуации не может быть в принципе.


0x2c – DebugActive;
Если поток захватил отладчик, здесь будет 1.

0x2d - State;
Этот байт описывает состояние потока.
Каждый поток может находиться в одном из 7-ми состояний:
инициализирован, готов, на выполнении, приостановлен, завершён, в ожидании, и переходный.
В исходниках
можно найти его определение:
C-подобный:
typedef enum _KTHREAD_STATE {
        Initialized   = 0
        Ready         = 1
        Running       = 2
        Standby       = 3
        Terminated    = 4
        Waiting       = 5
        Transition    = 6
} KTHREAD_STATE, *PKTHREAD_STATE;
0x58 - WaitIrql;
Тут все понятно из названия - IRQL, на котором вызывающий поток стал ожидать на объекте.

0x59 - WaitMode;
Это поле определяет режим ожидания. Сюда записывается соответствующий параметр, переданный функции ожидания KeWaitFor***. Соответственно это KernelMode или UserMode. Если он равен UserMode, то поток может принимать UserMode APC (вызов асинхронных процедур).

0xE0 - ServiceTable;
Определяет сервисную таблицу функций для потока, под названием SSDT.
Обычно там лежит адрес KeServiceDescriptorTable/TableShadow.


0x140 - PreviousMode;
Содержит режим процессора, в которым поток был до вхождения в режим ядра.
Это поле играет большую роль при вызовах API-функций типа Nt и Zw. Система проверяет, с какого режима вызываются эти функции. Если из ядра, аргументы у функций не проверяются, а если из юзера, то исполняющая система ядра Executive проверяет их кол-во на соответствие.
**********************************

Обычно у процессов несколько потоков, и каждый из них занимается своим делом внутри одного приложения. Некоторые потоки тупо простаивают и ждут какого-нибудь события типа ‘вывод на печать’, некоторые активно ведут предварительные расчёты и т.д. Ознакомится с кол-вом потоков каждого приложения можно в системном ‘Диспетчере задач’, активировав одноимённый столбец в меню вид:


task_0.png



4.0.1. Структура PEB

Как упоминалось выше, структуры THREAD ядро держит при себе, но по отношению к юзеру это не справедливо – юзер тоже человек и ему тоже хочется.. Поэтому на определённом этапе создания контекста процесса, система создаёт в пространстве пользователя структуры с окружением данного процесса PEB (Process-Environment-Block), и для каждого из его потоков - структуры TEB (Thread-Environment-Block). На текущий TEB всегда указывает сегментный регистр процессора FS на х32 системах, и GS на х64.


k_0.png


Поскольку эти структуры находятся уже в адресном пространстве пользователя (а не ядра), нужно перезапустить WinDbg и подключиться к процессу нашего ‘Hello World!’ через клавишу [F6] – Attach to Process. Теперь нужно узнать базу исследуемого процесса в памяти, для чего вводим команду lmD Load Module Dynamic. Как и в предыдущем примере, эту базу будем подставлять для просмотра структур PEB и TEB конкретного процесса:

wdbg_2.png


База 0х00400000 является базой по-умолчанию для всех РЕ-файлов, если не указать компилятору другое значение директивой at. РЕВ заполняется загрузчиком Ntdll.dll на этапе создания процесса. Эта структура содержит информацию об окружении, загруженных модулях (LDR_DATA), базовой информации по текущему модулю и другие критичные данные необходимые для функционирования процесса. Получающие информацию о процессах функции API, вызывают ReadProcessMemory() для считывания информации из PEB нужного процесса. Посмотрим на содержимое этой структуры командой [COLOR=rgb(247, 218, 100)]dt _peb 400000[/COLOR]:

Код:
0:001> dt _peb 400000
ntdll!_PEB
   +0x000 InheritedAddressSpace    : 0x4d 'M'
   +0x001 ReadImageFileExecOptions : 0x5a 'Z'
   +0x002 BeingDebugged            : 0x80 ''
   +0x003 SpareBool                : 0 ''
   +0x004 Mutant                   : 0x00000001 Void
   +0x008 ImageBaseAddress         : 0x00100004 Void
   +0x00c Ldr                      : 0x0000ffff _PEB_LDR_DATA
   +0x010 ProcessParameters        : 0x00000140 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData            : (null)
   +0x018 ProcessHeap              : 0x00000040 Void
   +0x01c FastPebLock              : (null)
   +0x020 FastPebLockRoutine       : (null)
   +0x024 FastPebUnlockRoutine     : (null)
   +0x028 EnvironmentUpdateCount   : 0
   +0x02c KernelCallbackTable      : (null)
   +0x030 SystemReserved           : [1] 0
   +0x034 AtlThunkSListPtr32       : 0
   +0x038 FreeList                 : (null)
   +0x03c TlsExpansionCounter      : 0x80
   +0x040 TlsBitmap                : 0x0eba1f0e Void
   +0x044 TlsBitmapBits            : [2] 0xcd09b400
   +0x04c ReadOnlySharedMemoryBase : 0x685421cd Void
   +0x050 ReadOnlySharedMemoryHeap : 0x70207369 Void
   +0x054 ReadOnlyStaticServerData : 0x72676f72  -> ????
   +0x058 AnsiCodePageData         : 0x63206d61 Void
   +0x05c OemCodePageData          : 0x6f6e6e61 Void
   +0x060 UnicodeCaseTableData     : 0x65622074 Void
   +0x064 NumberOfProcessors       : 0x6e757220
   +0x068 NtGlobalFlag             : 0x206e6920
   +0x070 CriticalSectionTimeout   : _LARGE_INTEGER 0x240a0d2e`65646f6d
   +0x078 HeapSegmentReserve       : 0
   +0x07c HeapSegmentCommit        : 0
   +0x080 HeapDeCommitTotalFreeThreshold : 0x4550
   +0x084 HeapDeCommitFreeBlockThreshold : 0x3014c
   +0x088 NumberOfHeaps            : 0x5d11146f
   +0x08c MaximumNumberOfHeaps     : 0
   +0x090 ProcessHeaps             : (null)
   +0x094 GdiSharedHandleTable     : 0x010f00e0 Void
   +0x098 ProcessStarterHelper     : 0x4901010b Void
   +0x09c GdiDCAttributeList       : 0x200
   +0x0a0 LoaderLock               : 0x00000400 Void
   +0x0a4 OSMajorVersion           : 0
   +0x0a8 OSMinorVersion           : 0x2000
   +0x0ac OSBuildNumber            : 0x2000
   +0x0ae OSCSDVersion             : 0
   +0x0b0 OSPlatformId             : 0x1000
   +0x0b4 ImageSubsystem           : 0x400000
   +0x0b8 ImageSubsystemMajorVersion : 0x1000
   +0x0bc ImageSubsystemMinorVersion : 0x200
   +0x0c0 ImageProcessAffinityMask : 1
   +0x0c4 GdiHandleBuffer          : [34] 0
   +0x14c PostProcessInitRoutine   : (null)
   +0x150 TlsExpansionBitmap       : (null)
   +0x154 TlsExpansionBitmapBits   : [32] 0
   +0x1d4 SessionId                : 0x3000
   +0x1d8 AppCompatFlags           : _ULARGE_INTEGER 0x600`00000200
   +0x1e0 AppCompatFlagsUser       : _ULARGE_INTEGER 0x0
   +0x1e8 pShimData                : (null)
   +0x1ec AppCompatInfo            : 0xc0000040 Void
   +0x1f0 CSDVersion               : _UNICODE_STRING ""
   +0x1f8 ActivationContextData    : (null)
   +0x1fc ProcessAssemblyStorageMap : (null)
   +0x200 SystemDefaultActivationContextData : (null)
   +0x204 SystemAssemblyStorageMap : (null)
   +0x208 MinimumStackCommit       : 0

Нужно сказать, что некоторые поля этой структуры ещё не инициализированы, т.к. при подключении к процессу WinDbg останавливается далеко не на точке-входа в отлаживаемый процесс. Если поставить бряк на Е-Рoint и запустить отладчик командой Go, то РЕВ инициализируется полностью. Получить указатель на РЕВ своего процесса на программном уровне можно через регистр FS+30h.

Заслуживающими внимание являются два поля, прочитав которые можно обнаружить Олю и её братию. Первое поле: PEB.02.BeingDebugged при отладки процесса выставляется системой в 1, а второе PEB.68h.NtGlobalFlag - это OR трёх/глобальных флагов системы, которые под отладчиком будут иметь значение 70h. Системная функция IsDebuggerPresent() проверяет как-раз-таки поле(2) и если дизассемблировать её командой(u) в WinDbg, можно в этом убедиться:

C-подобный:
0:000> u kernel32!IsDebuggerPresent
// kernel32!IsDebuggerPresent:
7c813123 64a118000000    mov     eax,dword ptr fs:[00000018h]
7c813129 8b4030          mov     eax,dword ptr [eax+30h]
7c81312c 0fb64002        movzx   eax,byte ptr [eax+2]
7c813130 c3              ret
7c813131 90              nop
При (ун)ассемблировании функций в отладчике, не забываем смотреть на первый столбец с адресами функции в памяти. Если адрес ниже 0х80000000, то функция доступна для модификации и перехвата, иначе от манипуляциями с ней лучше сразу отказаться, т.к. нужно будет пробираться звериными тропами в ядро, или искать уязвимости в драйверах.

Небольшой пример, как обнаружить отладчик сподручными средствами без Win-API представлен ниже. Если запустить его в токсичной среде отладки, то код выдаст предупреждение. При обычных обстоятельствах – это просто приветствие:

C-подобный:
include 'win32ax.inc'
.data
text0   db  'Hello World!',0
text1   db  'Внимание! Обнаружен отладчик.',0
;-------
.code
start:  mov     esi,[fs:30h]             ; берём в ESI указатель на РЕВ
        add     esi,2                    ; смещаемся к PEB.02.BeingDebugged
        cmp     byte[esi],1              ; проверка поля на отладчик
        jne     @01                      ; если не равно
        invoke  MessageBox,0,text1,0,0   ; иначе: мессага Warning!
        jmp     @exit                    ;   ..и на выход.
@01:    invoke  MessageBox,0,text0,0,0   ; если нет отладчика..
@exit:  invoke  ExitProcess,0            ;
.end start

4.0.2. Структура TEB потока

На 32-битный TEB указывает регистр FS, на 64-битный - GS. Эта структура используется для хранения информации о потоках в текущем процессе, и каждый поток имеет свой собственный TEB. Они создаются системной функцией MmCreateTeb(), а PEB создается функцией MmCreatePeb(). Если интересен процесс их создания и заполнения, можно посмотреть исходники ReactOS, или при помощи WinDbg исследовать алго самостоятельно:


Код:
0:001> dt _teb 400000
ntdll!_TEB
   +0x000 NtTib                    : _NT_TIB
   +0x01c EnvironmentPointer       : (null)
   +0x020 ClientId                 : _CLIENT_ID
   +0x028 ActiveRpcHandle          : (null)
   +0x02c ThreadLocalStoragePointer : (null)
   +0x030 ProcessEnvironmentBlock  : (null)
   +0x034 LastErrorValue           : 0
   +0x038 CountOfOwnedCriticalSections : 0
   +0x03c CsrClientThread          : 0x00000080 Void
   +0x040 Win32ThreadInfo          : 0x0eba1f0e Void
   +0x044 User32Reserved           : [26] 0xcd09b400
   +0x0ac UserReserved             : [5] 0x2000
   +0x0c0 WOW32Reserved            : 0x00000001 Void
   +0x0c4 CurrentLocale            : 0
   +0x0c8 FpSoftwareStatusRegister : 0xa0003
   +0x0cc SystemReserved1          : [54] (null)
   +0x1a4 ExceptionCode            : 0n116
   +0x1a8 ActivationContextStack   : _ACTIVATION_CONTEXT_STACK
   +0x1bc SpareBytes1              : [24]  ""
   +0x1d4 GdiTebBatch              : _GDI_TEB_BATCH
   +0x6b4 RealClientId             : _CLIENT_ID
   +0x6bc GdiCachedProcessHandle   : (null)
   +0x6c0 GdiClientPID             : 0
   +0x6c4 GdiClientTID             : 0
   +0x6c8 GdiThreadLocalInfo       : (null)
   +0x6cc Win32ClientInfo          : [62] 0
   +0x7c4 glDispatchTable          : [233] (null)
   +0xb68 glReserved1              : [29] 0
   +0xbdc glReserved2              : (null)
   +0xbe0 glSectionInfo            : (null)
   +0xbe4 glSection                : (null)
   +0xbe8 glTable                  : (null)
   +0xbec glCurrentRC              : (null)
   +0xbf0 glContext                : (null)
   +0xbf4 LastStatusValue          : 0
   +0xbf8 StaticUnicodeString      : _UNICODE_STRING ""
   +0xc00 StaticUnicodeBuffer      : [261] 0
   +0xe0c DeallocationStack        : (null)
   +0xe10 TlsSlots                 : [64] (null)
   +0xf10 TlsLinks                 : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0xf18 Vdm                      : (null)
   +0xf1c ReservedForNtRpc         : (null)
   +0xf20 DbgSsReserved            : [2] (null)
   +0xf28 HardErrorsAreDisabled    : 0
   +0xf2c Instrumentation          : [16] (null)
   +0xf6c WinSockData              : (null)
   +0xf70 GdiBatchCount            : 0
   +0xf74 InDbgPrint               : 0 ''
   +0xf75 FreeStackOnTermination   : 0 ''
   +0xf76 HasFiberData             : 0 ''
   +0xf77 IdealProcessor           : 0 ''
   +0xf78 Spare3                   : 0
   +0xf7c ReservedForPerf          : (null)
   +0xf80 ReservedForOle           : (null)
   +0xf84 WaitingOnLoaderLock      : 0
   +0xf88 Wx86Thread               : _Wx86ThreadState
   +0xf94 TlsExpansionSlots        : (null)
   +0xf98 ImpersonationLocale      : 0
   +0xf9c IsImpersonating          : 0
   +0xfa0 NlsCache                 : (null)
   +0xfa4 pShimData                : (null)
   +0xfa8 HeapVirtualAffinity      : 0
   +0xfac CurrentTransactionHandle : (null)
   +0xfb0 ActiveFrame              : (null)
   +0xfb4 SafeThunkCall            : 0 ''
   +0xfb5 BooleanSpare             : [3]  ""

ТЕВ начинается со-структуры TIB – Thread Information Block, докатившейся до нас ещё с Win98.
C-подобный:
0:001> dt _teb 400000 nttib.
ntdll!_TEB
   +0x000 NtTib  :
      +0x000 ExceptionList    : 0x00805a4d _EXCEPTION_REGISTRATION_RECORD
      +0x004 StackBase        : 0x00000001 Void
      +0x008 StackLimit       : 0x00100004 Void
      +0x00c SubSystemTib     : 0x0000ffff Void
      +0x010 FiberData        : 0x00000140 Void
      +0x010 Version          : 0x140
      +0x014 ArbitraryUserPointer : (null)
      +0x018 Self             : 0x00000040 _NT_TIB
Здесь, поле по смещению TEB.00.ExceptionList указывает на системный SEH-фрейм, который отлавливает аппаратные и программные исключения. SEH переводится как Structured Exception Handle, или структурная обработка исключений. Система не утруждает себя обработкой всех исключений, т.к. физически не может их все предугадать. Поэтому программисту дозволено вставлять в цепочку свои обработчики, помещая указатели на них исключительно в стек. На первый обработчик в цепочке указывает как-раз поле TEB.00.ExceptionList.

seh.png



4.0.3. Локальная память потока TLS

Основной проблемой многопоточных приложений является синхронизация их совместной работы внутри одного процесса. Чем больше у процесса потоков, тем больший получается хаос. Категорически противопоказано использовать в таких приложения глобальные переменные, поскольку за целостность данных в них никто ответственности не несёт. Например, может возникнуть ситуация, когда поток(А) записал своё значение в переменную, а ничего не подозревающий поток(В) тут-же затёр эту переменную под себя.

Логически, правильным решением будет выделение каждому потоку своей памяти, ведь все они исполняются внутри общего пространства родительского процесса. И такая память действительно есть. Это TLS – Thread Local Storage или локальная память потока, которая находится в каждой из структур в поле ТЕВ.0e10.TlsSlots. Внешне она выглядит так:


C-подобный:
//=== TEB (фрагмент структуры) =============================
//==========================================================
    02C   00 00 00 00                   TlsPointer             = 0
    030   00 C0 FD 7F                   PEB                    = 7FFDC000
    E10                                 TlsSlots
     |
     +-- 00 00 00 00 00 00 00 00 | 00 00 00 00 30 3C 89 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00     = Всего: 256-байт.

   F10   00 00 00 00                    TlsLinks.FLink         = 0
   F14   00 00 00 00                    TlsLinks.BLink         = 0
   F94   00 00 00 00                    TlsExpansionSlots      = NULL
Поскольку у каждого потока своя структура ТЕВ, то и память получается своя. Детальное рассмотрение этой памяти выходит за рамки данного топика, поэтому ограничусь лишь системной поддержкой. Для динамического выделения локальной памяти, система предоставляет тредам всего 4 функции. Так-как эти функции оперируют только индексами, тред не может работать с отдельными байтами TLS - мин.порцией является dword'ный слот. Вот эти функции:
C-подобный:
  TlsAlloc() - возвращает индекс очередного/свободного слота в TEB;
  TlsSetValue(index,value) - записывает dword в этот слот;
  TlsGetValue(index) - читает в EAX значение из указанного слота;
  TlsFree(index) - очищает указанный слот в TEB (и сбрасывает бит в РЕВ).
 
Последнее редактирование:

CKAP

Green Team
10.06.2019
68
39
BIT
0
Ну вот подводя некий итог по потокам.. какова их природа?
Что есть поток? То есть скажем Call может породить поток.. или там, все что угодно его порождает? Ну не знаю.. запись в файл.
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
Что есть поток? То есть скажем Call может породить поток.. или там, все что угодно его порождает?
Нет, call не порождает потоков, а лишь передаёт управление на некоторый участок кода в текущем потоке. Дополнительные потоки создаёт основной поток программы, посредством только функции CreateThread(). Результатом этой функции будет отдельная нить программного кода, которая будет исполняться параллельно (одновременно) с основным потоком программы.

Многопоточные программы хлынули в массы с появлением процессоров с поддержкой Hyper-Threading (Pentium-IV), когда одно ядро представляется в виде двух логических ядер. Эта технология основана на том, что к одному исполнительному ядру Core подходит не один, а два конвейера, которые снабжают ядро инструкциями. Это позволило исполнять одновременно сразу два программных потока, вместо одного.

ООО-ядро четвёртого пенька было настолько удачным, что при полной загрузке конвейера, 70% исполнительных возможностей ядра тупо простаивало, т.е. ядро могло исполнять инструкции намного быстрее, чем они поступали из-вне. Поэтому инженеры добавили второй конвейер, и проц стал Hyper-Threading, или двупоточным.

Ясно, что поскольку ядро у HT на самом деле одно, то это эмуляция многопоточности, т.к. исполнение двух потоков просто чередуется планировщиком на высокой частоте, загружая ядро по самые помидоры. Реальная параллельность возможна только на процессорах с двумя физическими ядрами. Кстати, процессоры INTEL отличаются от AMD тем, что если INTEL говорит "8-ядерный процессор", то это 4-ядерный проц с поддержкой HT. У AMD, 8-ядер означают реальные 8 ядра, без технологии HT (хотя встречаются и исключения).

Таким образом программист получил возможность создавать в своих программах дополнительные потоки, которые на многоядерных процессорах будут исполняться параллельно с основным потоком. Созданный функцией CreateThread() программный поток - это физически отдельная нить программного кода. Как-правило, доп.поток ждёт от основного потока сигнала к действию, для чего применяют объекты синхронизации типа Event (событие) или Semaphore (семафор).

Например в текстовых редакторах основной поток может принимать от пользователя ввод с клавиатуры, а доп.поток будет сохранять этот ввод в файл. Тогда основной поток не будет отвлекаться от пользователя. Когда юзер нажмёт комбинацию Ctrl+S (save) и продолжит печатать дальше, основной поток как и прежде будет обрабатывать ввод, а доп.поток в фоне сохранит данные в файл.
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Нет, call не порождает потоков

Точно.. вспомнил интервью Гайджинов.. те, что бы загружать другие ядра кидали на них всякие красивости.
Нужно будет пересмотреть с новыми знаниями :)
Кому тоже интересно 21:40
 
Последнее редактирование:
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
4.1.4.4. Простое многопоточное приложение

Небольшой пример дву/поточного приложения..
Основной поток подаёт сигнал к действию доп.потоку посредством объекта-синхронизации 'Event' (событие). Значит сначала нужно создать этот объект функцией CreateEvent() с таким прототипом:

C-подобный:
HANDLE CreateEvent (
    lpEventAttributes    // (0) - атрибут безопасности 
    bManualReset         // (0) - авто/переключение в предыдущее состояние
    bInitialState        // (0) - начальное состояние объекта (выкл)
    lpName               // (0) - указатель на имя объекта (у нас безымянный).
   );
Эта fn. вернёт дескриптор Handle данного объекта-события.
Включать/выключать событие позволяют функции SetEvent() и ResetEvent() соответственно, но поскольку в аргументах мы задали авто/переключение в предыдущее состояние, нужно будет только включить его по Set. После того-как тред отработает, он сам обратно выключит объект.

Везде, где есть объекты-синхронизации, должна быть функция WaitForSingleObject(), которая будет наблюдать за этим объектом, и в зависимости от его состояния - замораживать или пробуждать доп.поток, для исполнения своего кода. Если нужно наблюдать сразу за несколькими объектами, то вместо Single используем WaitForMultipleObject(). Эти функции удобны тем, что в момент ожидания не занимают процессорное время. Прототип у Single такой:

C-подобный:
DWORD WaitForSingleObject (
    hHandle,              // Handle объекта, за которым нужно наблюдать
    dwMilliseconds        // сколько времени наблюдать. -1 = 0xFFFFFFFF = вечно (INFINITE)
   );
Это - что касается объекта.. Теперь сам поток..
Создаётся он функцией CreateThread() с нижеследующим прототипом (в скобках мои значения):

C-подобный:
HANDLE CreateThread (
    lpThreadAttributes,   // (0) - атрибут безопасности (в дефолте)
    dwStackSize,          // (0) - размер стека для потока (в дефолте)
    lpStartAddress,       // указатель на исполняемую функцию потока
    lpParameter,          // (0) - можно передать потоку 1 аргумент
    dwCreationFlags,      // (0) - после создания, сразу запустить поток на исполнение
    lpThreadId            // (0) - указатель для сохранения Thread-ID (нам TID не нужен)
   );
Функция возвращает Handle созданного потока, через который можно к нему обращаться.
Среди функций работы с потоками выделяются следующие:

  • SuspendThread (handle) - усыпить поток;
  • ResumeThread (handle) - пробудить поток;
  • ExitThread (0) - выгрузить поток из памяти.
Консольная программа ниже демонстрирует, как применять эти функции на практике. Здесь, в основном потоке пользователю предлагается ввести текст, а доп.поток сохранит этот текст в файл. Командой для начала работы потока является SetEvent(). Дескриптором вывода STDOUT (монитор) является константа(7), а дескриптором ввода STDIN (клавиатура) - константа(3). Их будем подставлять в fn. Read/WriteConsole().

Стандартные функции Win32 для работы с файлами типа CreateFile() - слишком громоздки, поэтому задействуем их аналоги из Win2000, которые для обратной совместимости поддерживаются всеми Kernel32/64.dll. Просмотреть список функций-экспорта из системных библиотек можно как-минимум прямо из TotalCommander, нажав в нём комбинацию Ctrl+Q и перейдя на вкладку Импорт/Экспорт. Для их исследования, я скопировал основные файлы системы в отдельную папку, и теперь могу свободно трассировать их в отладчике (иначе система не даст этого сделать):


thread.png


Ну и собственно многопоточный код:

C-подобный:
format PE console
include 'win32ax.inc'
.data
fName   db  'backup.txt',0
capt    db   'Type text for save to file',13,10
        db   '--------------------------',13,10,0
cLen    dd   $ - capt
final   db   '--------------------------',13,10
        db   'Save to backup.txt - OK!  ',0
fLen    dd   $ - final
buff    db   512 dup(0)    ; буфер для ввода
rSize   dd   0             ; реальная длина ввода.
tHndl   dd   0             ; хэндлы: - потока  Thread
fHndl   dd   0             ;         - файла   File
eHndl   dd   0             ;         - события Event
;-------
.code
start:
;// Создаём событие и доп.поток
        invoke   CreateEvent,0,0,0,0
        mov      [eHndl],eax
        invoke   CreateThread,0,0,threadFunc,0,0,0
        mov      [tHndl],eax

;// Выводим заголовок программы в консоль
;// и принимаем от юзера ввод
        invoke   WriteConsoleA,7,capt,[cLen],0,0
        invoke   ReadConsoleA,3,buff,512,rSize,0

;// Включаем объект события для доп.потока
        invoke   SetEvent,[eHndl]

;// Выводим финальную мессагу в основном потоке
        invoke   WriteConsoleA,7,final,[fLen],0,0
        invoke   ExitProcess,0

;//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
;//--- Функция дополнительного потока ----
proc    threadFunc
        invoke   WaitForSingleObject,[eHndl],-1   ; ждём события..
        invoke  _lcreat,fName,0                   ; создать файл R/W
        invoke  _lwrite,eax,buff,[rSize]          ; запись в него из буфера!
        invoke  _lclose,[fHndl]                   ; закрыть файл
        invoke   ExitThread,0                     ;   ..и выгрузить поток.
        ret
endp
.end start
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Годный линк подогнали ребята с Васма
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
4.2. Вторжение в пространство чужого процесса

Как упоминалось выше, у каждого процесса своё адресное пространство и если мы хотим произвести мод стороннего процесса, сначала должны получить его координаты в памяти. Сделать это проще, чем может показаться на первый взгляд, хотя Microsoft утверждает обратное. Во-первых, нужно получить PID, по которому сможем запросить у системы Handle процесса-жертвы через OpenProcess(). Если удастся выудить PID и Handle, то процесс будет полностью в нашем распоряжении.

Способов нахождения PID довольно много, среди которых пользуются спросом функции: ZwQuerySystemInformation(), EnumProcess() и CreateToolhelp32Snapshot(). Первая – это шагающий экскаватор, который может перемолоть всё на своём пути. Помимо Pid, ZwQuery возвращает ещё кучу информации, причём за один вызов сразу обо всех активных процессах, что требует резерва памяти под выхлоп. Её мы будем использовать для скрытия своей тушки в следующем разделе этой части. Вторая EnumProcess() живёт в сторонней библиотеке Psapi.dll, поэтому пока оставим её в покое.

Тогда вероятным кандидатом для наших целей остаётся CreateToolhelp32Snapshot(), услугами которой и воспользуемся. Общую информацию по функциям ToolHelp можно
. Snapshot – это снимок объектов, доступный только для чтения. В окопах CreateToolhelp32Snapshot() лежит аргумент, в котором нужно указать один из следующих флагов, чтобы уточнить запрос:
C-подобный:
TH32CS_SNAPHEAPLIST = 1      ; снимок куч
TH32CS_SNAPPROCESS  = 2      ; снимок процессов
TH32CS_SNAPTHREAD   = 4      ; снимок тредов
TH32CS_SNAPMODULE   = 8      ; снимок модулей DLL
TH32CS_SNAPALL      = 15     ; всё перечисленное
Из этой коллекции нам нужен флаг(2), что заставит функцию выдать информацию только о процессах. Она вернёт нам Handle на снапшот, который нужно передать в руки родственным функциям Process32First() и Process32Next(). Результатом их работы будет заполненная данными структура PROCESS_ENTRY32 следующего содержания:
C-подобный:
struct  ProcessEntry32          ;// cтруктура для fn. Process32First/Next()
  size           dd  300          ; размер данной структуры (установить до вызова)
  usage          dd  0            ; ссылок на процесс
  processID      dd  0            ; PID процесса (наш клиент)
  defaultHeapID  dd  0            ; ID кучи процесса по-умолчанию
  moduleID       dd  0            ; MID
  threads        dd  0            ; кол-во тредов у процесса
  parentPID      dd  0            ; PID родительского процесса
  priClassBase   dd  0            ; приоритет процесса (см.в Диспетчере задач)
  flags          dd  0            ; ..(резерв)
  processName    db  256 dup(0)   ; ASCII-имя процесса
ends
Третий член этой структуры есть никто-иной как идентификатор процесса PID, и это радует. Только данная структура описывает всего один процесс, значит нужно продолжить сбор информации в цикле, пока Process32Next() не вернёт ошибку. Так мы просканируем все процессы в памяти и сможем вывести некоторые (или все) поля структуры на экран. Код ниже демонстрирует такой алгоритм:
C-подобный:
include 'win32ax.inc'
.data
capt           db  'Task v0.1',0
text           db  'Активные процессы ',13,10
               db  '------------------',13,10
buff           db  2048 dup(0)
frmt           db  'Pid: %04x     Процесс: %s',13,10,0
count          db  '------------------',13,10
               db  'Всего процессов: %d',0
pHndl          dd  0

align   16                      ; выравнивание памяти на 16-байтный параграф
struct  ProcessEntry32    ;//<--- cтруктура для fn. Process32First/Next()
size           dd  300          ; размер данной структуры (установить до вызова)
usage          dd  0            ;
processID      dd  0            ; PID процесса
defaultHeapID  dd  0            ;
moduleID       dd  0            ;
threads        dd  0            ; кол-во тредов в процессе
parentPID      dd  0            ;
priClassBase   dd  0            ;
flags          dd  0            ;
processName    db  256 dup(0)   ; имя процесса
ends
pEntry         ProcessEntry32   ; определяем (#define) укороченное имя структуры
;//------------
.code
start: invoke  CreateToolhelp32Snapshot,2,0       ; аргумент = 2. процессы
       mov     [pHndl],eax                        ; запомнить хэндл снапшота

       xor     ebx,ebx                            ; EBX=0, будет счётчиком найденых
       mov     edi,buff                           ; адресуем буфер через EDI
       invoke  Process32First,[pHndl],pEntry      ; первый пошёл! (он заполнит структуру)
@scan: inc     ebx                                ; счётчик найденых +1
       mov     esi,edi                            ; ESI = указатель на текущий буфер

      cinvoke  wsprintf,esi,frmt,[pEntry.processID],pEntry.processName  ;// сбрасываем инфу в буфер

;// здесь нужно очистить поле с именем в структуре, от предыдущего
       mov     edi,pEntry.processName             ; EDI = указатель на него
       xor     al,al                              ; забивать будем нулями, AL=0
       mov     ecx,256                            ; длина поля в байтах
       rep     stosb                              ; запись из AL в EDI, 256 раз

;// чтобы не затереть предыдущую инфу в буфере,
;// нужно сместить указатель ЗА строку, тогда получим форматированный вывод.
;// т.к. весь буфф был забит нулями, значит нужно искать первый/попавшийся нуль:
       mov     edi,buff                           ; ставим указатель на начало
       xor     al,al                              ; искать будем нуль, AL=0
       mov     ecx,-1                             ; счётчик на макс! ECX=FFFFFFFFh
       repne   scasb                              ; ищем AL в EDI
       dec     edi                                ; коррекция указателя.

;// теперь можно перейти к поиску следующего процесса в памяти
       invoke  Process32Next,[pHndl],pEntry       ; в аргументах хэндл снапшота, и адрес структуры
       or      eax,eax                            ; ошибка???
       jne     @scan                              ; нет - повторить цикл..
       invoke  CloseHandle,[pHndl]                ; иначе: прибьём снапшот

      cinvoke  wsprintf,edi,count,ebx             ; добавим в буфер счётчик найденых

       invoke  MessageBox,0,text,capt,0           ; вывод всей инфы на экран!
       invoke  ExitProcess,0                      ;
.end start
toolHelp.png


Угу.. значит ToolHelp исправно робит и в награду мы получили валиндый PID каждого из присутствующих в памяти процессов. Теперь настала очередь их дескрипторов. Функция OpenProcess() как-раз этим и занимается. В аргументах она требует PID, по которому возвращает Handle окутанного тайной процесса.

C-подобный:
Handle OpenProcess (
     DWORD dwDesiredAccess,    // флаги доступа, ставим ALL_ACCESS
     BOOL  bInheritHandle,     // флаг наследования, ставим нуль
     DWORD dwProcessId         // PID, чей хэндл нужно получить
    );
Если функция завершается ошибкой, возвращаемое значение NULL, иначе – дескриптор указанного процесса. Например, функция завершается неудачно для процессов Idle и csrss.exe, поскольку их атрибуты доступа не позволяют открывать свои тушки на уровне юзера.

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

C-подобный:
BOOL WriteProcessMemory (
    hProcess,               // Handle процесса, в чью память хотим проникнуть
    lpBaseAddress,          // адрес для записи в его пространстве
    lpBuffer,               // указатель на буфер-источник в своём пространстве
    nSize,                  // кол-во байт для записи
    lpNumberOfBytesWritten  // фактическое кол-во записанных байт
   );
Эта функция BOOL, так-что нуль = ошибка, иначе = ОК!
Судя по её описанию, критическим условием является доступ на запись в регион.
Значит перед тем-как воспользоваться WriteProcessMemory(), нужно функцией VirtualAllocEx() выделить память в пространстве жертвы, задав ей атрибуты R/W. Эта функция возвращает адрес выделенного блока памяти в пространстве жертвы, или нуль в случае ошибки:

C-подобный:
LPVOID VirtualAllocEx (
    hProcess,            // Handle процесса, чью память хотим поиметь
    lpAddress,           // базовый адрес выделения, ставим нуль и fn.сама найдёт свободный участок
    dwSize,              // размер в байтах области выделения
    flAllocationType,    // тип размещения, ставим MEM_COMMIT – выделение (не резервация)
    flProtect            // тип защиты доступа, ставим EXECUTE_READWRITE – исполняемая для шелла.
   );
Теперь потренируемся ‘на кошках’..
Напишем два приложения: одно будет жертвой, а другое – атакующим.
Если наша цель раньше была коммунизм, то сейчас – запись строки в область памяти подопытного процесса Hello.exe. Алгоритм будет такой:
  1. CreateToolhelp32Snapshot() – найти процесс Hello.exe в памяти, и получить его PID;
  2. OpenProcess() – открыть данный процесс по PID и получить его Handle;
  3. VirtualAllocEx() – передав Handle, выделить память в процессе Hello.exe с доступом на запись/исполнение;
  4. WriteProcessMemory() – записать данные в подопытный процесс.
C-подобный:
include 'win32ax.inc'
.data
capt           db  'Attach v0.1',0
frmt           db  'Шелл-код удачно записан в удалённый процесс!',13,10
               db  'Базовый адрес расположения: 0x%08X',0
text           db   128 dup(0)
accessError    db  'Процесс найден, но к нему нет доступа.',0
noProcess      db  'Указанный процесс не найден.',0
name           db  'hello.exe',0    ; имя процесса для поиска (в нижнем регистре)
nameLen        =   ($ - name)-1     ; его длина без учёта нуля
snapHndl       dd   0               ; будет хэндл снапшота
processHndl    dd   0               ; под хэндл процесса

align   16                        ; выравнивание структуры на параграф
struct  ProcessEntry32            ;// cтруктура для fn.Process32First/Next()
  size         dd  300            ;
  usage        dd  0              ;
  processID    dd  0              ;// PID процесса
  HeapID       dd  6 dup(0)       ; резерв
  processName  db  256 dup(0)     ;// имя процесса
ends
pEntry         ProcessEntry32
;//-------
.code
start: invoke  CreateToolhelp32Snapshot,2,0       ; запрашиваем инфу о процессах
       mov     [snapHndl],eax                     ; запомнить хэндл снапшота
       invoke  Process32First,[snapHndl],pEntry   ; начинаем поиск процесса-жертвы

;// здесь нужно перевести имя в структуре, в нижний регистр (прописные),
;// иначе поиск может не дать результата
@scan: mov     edi,pEntry.processName   ; EDI = указатель на него
       mov     ecx,nameLen              ; длина строки в байтах
       push    edi ecx                  ; запомнить для сл.операции!
@01:   or      byte[edi],00100000b      ; взводим бит(5) в имени (символ в ниж.регистр)
       inc     edi                      ; сл.байт в поле 'Имя' структуры
       loop    @01                      ; повторить ECX-раз..

;// сканируем поле с именем на 'hello.exe'
       pop     ecx edi                  ; восстанавливаем указатель и длину имени
       mov     esi,name                 ; ESI = указатель на строку поиска
       repe    cmpsb                    ; сравниваем байты ESI с байтами EDI
       or      ecx,ecx                  ; всю строку проверили?
       je      @ok                      ; если совпало!

       invoke  Process32Next,[snapHndl],pEntry  ; иначе: продолжить поиск процесса
       or      eax,eax                          ; ошибка???
       jne     @scan                            ; нет - повторить цикл..
       invoke  MessageBox,0,noProcess,0,0       ; все процессы проверили - нету Hello.exe
       jmp     @exit                            ; на выход!

;// нашли процесс!
;// берём его PID из структуры и получаем Handle
@ok:   mov     eax,[pEntry.processID]
       invoke  OpenProcess,PROCESS_ALL_ACCESS,0,eax    ; ставим доступ ALL
       or      eax,eax                                 ; ошибка???
       jnz     @next                                   ; если нет..
@err:  invoke  MessageBox,0,accessError,0,0            ; иначе: нет доступа!
       jmp     @exit                                   ; и на выход.

;// теперь у нас есть PID и Handle процесса 'hello.exe'
;// выделяем в нём память размером в страницу и атрибутами Ex/R/W
@next: mov     [processHndl],eax
       invoke  VirtualAllocEx,eax,0,1000h,MEM_COMMIT,PAGE_EXECUTE_READWRITE
       or      eax,eax                                 ;
       jz      @err                                    ; если ошибка, уходим выше.
       push    eax                                     ; запомнить выделенную базу
      cinvoke  wsprintf,text,frmt,eax                  ; преобразовать её в текст

;// Осталось записать в процесс шелл-код (пока просто строку)
       pop     eax                                     ; снимаем базу
       invoke  WriteProcessMemory,[processHndl],eax,name,nameLen,0
       or      eax,eax                                 ;
       jz      @err                                    ; если ошибка, уходим выше.

;// Значит запись в сторонний процесс прошла успешно
       invoke  MessageBox,0,text,capt,0                ; вывод инфы на экран!

@exit: invoke  CloseHandle,[snapHndl]      ; прибиваем снапшот
       invoke  ExitProcess,0
.end start

shell.png


Тут непонятно одно – куда смотрят аверы, т.к. на не один из них даже не хрюкнул. Расположившись в чужом пространстве, можно остановить поток жертвы по SuspendThread() и вместо него самим выполнять любые задачи, тем-самым уничтожив против себя все улики – мол это не мой процесс, и вообще моя хата с краю. Во-вторых, можно перехватывать все адресованные жертве пакеты хоть на уровне сокетов, хоть на уровне Web-приложений (например проникнув в Хром). Вообщем поле деятельности настолько обширно, что становится грустно, ведь это-же могут проделать и с нами.
 
Z

zigun

@Marylin я так понимаю проблема ведь не только записать что то в чужой процесс, но и передать управление на этот кусок, т.е. в этом процессе должна быть уязвимость переполнения
 
  • Нравится
Реакции: Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
т.е. в этом процессе должна быть уязвимость переполнения
..всё-правильно.
Программа должна записать шелл в чужое пространство, и запустить его функцией CreateRemoteThread(). Беспрепятственное выделение памяти в пространстве жертвы с доступом на R/W и исполнение - это уже уязвимость, и жертва от этого никак не застрахована.

Microsoft разрекламировала фишку под названием DEP 'Data-Execute-Protect' (защита от исполнения в секции-данных), но по-большому счёту она была введена только для защиты от переполнения стека, и просто делает стек неисполняемым. Если-уж ввели DEP, тогда нужно было убрать такие функции как VirtualProtectEx() и VirtualAllocEx(), которые с лёгкостью переназначают атрибуты регионов памяти. Но у Microsoft всё через пятую точку..
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
Я закончил, жадно хочу есче знаний :)
Остались не понятны эти манипуляции %04x, 0x%08X и тд.. но это к синтаксису я так понимаю. Де переменные де метки уже ясно.
invoke это макрос который живет тут win32ax.inc, а вот всякие OpenProcess это.. мм.. это тоже из win32ax.inc или это winAPI ?
 
Последнее редактирование:

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
Остались не понятны эти манипуляции %04x, 0x%08X
это спецификаторы для функции wsprintf(), которая сохраняет в буфер переданные ей аргументы в различных представлениях. Список наиболее часто применяемых спецификаторов выглядит так:
C-подобный:
   %f - плавающая точка fpu,
   %o - OCT восьмеричное число,
   %u - DEC беззнаковое целое,
   %d - DEC знаковое целое,
   %x - hex в нижнем регистре  5fe10ab8,
   %X - HEX в верхнем регистре 5FE10AB8,
   %s - строка (аргумент должен быть адресом строки).

//-- Ограничение разрядов в числе на примере 5fe10ab8 --
   %04x - четыре младших разрядов = 0ab8
   %08X - восьми/разрядное число  = 5FE10AB8
 
//-- В одной строке может быть несколько спецификаторов --
//-- причём началом спецификатора является знак процента (%),
//-- и до него может быть строка, например:
.data
frmt     db  'HEX = %08x, DEC = %u',0
buff     db  64 dup(0)
;------
.code
start:   mov     eax,0x5fe10ab8
        cinvoke  wsprintf, buff, frmt, eax, eax    ; 2-аргумента EAX (не считая первых/двух)
         invoke  MessageBox,0,buff,0,0

//-- Результат на экране --
         HEX = 5fe10ab8, DEC = 1608583864
Макрос invoke превращает стандартный вызов API-функций в приглядный вид. В ассемблере нет инструкции invoke, а есть только call, аргументы которому передаются через стек. Если посмотреть в отладчике, то бокс мессаги имеет в нём такой вид, который является фактическим вызовом функции в ассемблере:
C-подобный:
push  0           ; аргумент(4) - стиль окна,
push  caption     ; аргумент(3) - заголовок окна,
push  text        ; аргумент(2) - указатель на строку,
push  0           ; аргумент(1) - окно не модальное (вызывается не другим окном),
call  MessageBox  ; вызов Win32-API
Но в исходнике такой формат неудобен, и проще выстроить все аргументы в одной строке, что собственно и делает макрос invoke.

Чтобы изучить компилятор, можно полазить по фасмовским папкам и просмотреть содержимое его файлов. Как-правило там только инклуды с текстовым содержимым, поэтому можно использовать просмотрщик Тотала по Ctrl+Q. По функциональному назначению они выстроены так:
  • fasm\include - макросы, для технических нужд компилятора;
  • fasm\include\api - перечень API-функций из системных библиотек;
  • fasm\include\equates - список структур для API-функций;
  • fasm\include\pcount - число параметров/аргументов у API-функций;
  • fasm\tools - готовые к компиляции исходники, для создания доп.утилит.
Из этого списка, лично я часто обращаюсь к содержимому папки equates, чтобы найти формат той-или-иной структуры. Их там много, и открывать каждую порой напрягает. Поэтому я написал батник в который подставляю маску для поиска, и он мне выдаёт полный путь, где прописана данная структура. Основой батника является консольная команда findstr, способная рекурсивно обходить все папки и файлы, в поисках указанной строки:
Bash:
@echo off
echo.
echo  Поиск по маске "WSADATA" начался.
echo  Это займёт пару минут. Пожалуйста ждите...
echo.
echo  Слово найдено в следующих файлах:
echo  ---------------------------------
findstr /s /i /m "\<wsadata\>" *.*
echo  ---------------------------------
echo  Поиск окончен!!!
pause
Чтобы консоль отображала кирилицу, нужно сохранить этот сценарий как *.bat, в кодировке OEM-866. Сама маска указывается в аргументах findstr. Теперь ложим батник в корневую папку фасма и запускаем его на исполнение.

OpenProcess() и прочие - это виндовые функции Win32-API, которые экспортируют для нас системные библиотеки типа Ntdll.dll, Kernel32.dll, User32.dll и т.д. Какой из библиотек принадлежит конкретная функция можно узнать из директории fasm\include\api. Чтобы не искать вручную, можно как-раз воспользоваться этим батником, изменив маску "\<wsadata\>" на маску "\<CreateProcess\>".
 

CKAP

Green Team
10.06.2019
68
39
BIT
0
31384


В целом. Пока нет от вас статей, штудирую Масм и многое становится более понятно. Получается даже как то взаимно обоюдно, что то тут почерпну, что там.. и одно другому не мешает.
 
  • Нравится
Реакции: EdmonDantesDuma и Marylin

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
ассемблер - и в Африке ассемблер, а зоопарк его компиляторов отличается только синтаксисом. Например у MASM и TASM он на 99% схож, а разраб FASM'а попытался убрать из их синтаксиса всё лишнее, например ту-же директиву offset.

Классическим ассемблером для х86 является TASM, только под винду на нём не прогают. MASM выделяет на фоне других шикарной коллекцией инклуд и макросов на все случаи жизни (он так и называется Macro-Asm). Зато исходник на фасме получается более читабельным, да и сам компилятор более шустрый и простой из-за своего IDE.

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

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
4.3. Перехват API-функций

Win-API или Application Programming Interface предоставляет программистам готовые функций для решения широкого круга задач. Все имеющиеся в системе API отсортированы по назначению и хранятся в соответствующих библиотеках DLL. На английский манер библиотеки называются Library, а в роли консперативной их квартиры выступает системная директория %Windir%\System32\.. Внутри либ, функции расположены вплотную друг-к-другу и каждая из них имеет свою точку-входа. Для осуществления наших замыслов, на данном этапе понадобится
– хакерское оружие ближнего боя, способное творить чудеса. Вот как он отображает содержимое библиотек на примере Kernel32.dll:

api_0.png


При вызове функций из наших программ система проделывает огромную работу прежде чем вернём нам результат, и в силу некоторых обстоятельств этот результат может быть не только положительным, но и отрицательным. Например передали мы функции неверный аргумент – встречаем ошибку. Большинство (если не все) API возвращают информацию о проделанной работе, в регистре EAX. Обработчики некоторых функций могут выполнятся в пределах юзерского пространства, однако львиная их доля бурится в ядро по такой схеме:

sysenter.png


Здесь видно, что вся нагрузка выпадает на долю библиотеки Ntdll.dll, которая через ассемблерную инструкцию SYSENTER переходит из юзерского пространства, в кернел. Если мы вызываем API для создания всяких побрекушек типа иконки или курсоры, то подключать к этому делу ядро совсем не обязательно, и можно сделать это локально, в пределах юзерского пространства (пунктирные линии на схеме). Однако если API создаёт объекты ядра типа файл/устройство и прочее (см.утилиту WinObj), то оська должна зарегистрировать эти объекты у себя. Переход из юзера в ядро занимает много времени, поэтому практичней создавать все ядерные объекты на начальном этапе программы, а не внутри полезного кода. Тогда наша программа пробуксует один раз при старте, а дальше - будет работать как часы.


4.3.0. Получаем адрес точки-входа в функцию

Таким образом, чтобы перехватить некоторую функцию Win32-API, нам нужно получить Entry-Point перехватываемой функции внутри данной библиотеки. Сделать это можно несколькими способами в зависимости от того, кто будет осуществлять перехват. Например если это шелл-код, то на него накладывается куча ограничений, в числе которых отказ от прямого использования системных сервисов. Shell должен делать всё самостоятельно, рассчитывая только на себя. Причиной тому – компиляция его в виде бинарного файла с базой нуль. Попав в тело жертвы, посредством вычисления т.н. ‘дельта-смещения’, шелл получает базу той области памяти в пространстве жертвы, куда его катапультировала программа-матка. Запомнив эту базу в регистре EBP (base-pointer), шелл должен будет опираться только на него, осуществляя исключительно косвенные переходы, типа EBP+4.

Эти сложные механизмы мы оставим на закуску, а пока вычислим адрес точки-входа легальными путями, для чего система имеет твикс-парочку GetModuleHandle() и GetProcAddress(). Первая возвращает в EAX базу указанной библиотеки в памяти, а вторая – адрес функции внутри этой библиотеки. После того-как в наших руках окажется точка-входа, мы можем вставить туда свой JMP, что позволит получить управление при вызовы системой данной функции. Это собственно и есть один из способов перехвата API, который назвали ‘сплайсинг’ от буржуйского Splicing. Код выглядит так:

C-подобный:
include 'win32ax.inc'
.data
capt     db   'Win32-API',0
text     db    128 dup(0)
frmt     db   '%08X = База User32.dll в памяти',13,10
         db   '%08X = Точка входа в MessageBox()',0

libName  db   'user32.dll',0
fName    db   'MessageBoxA',0
;-------
.code
start:   invoke  GetModuleHandle,libName        ;// EAX = база DLL в памяти
         mov     ebx,eax                        ;// (запомнить в EBX)
         invoke  GetProcAddress,eax,fName       ;// EAX = база функции внутри DLL
        cinvoke  wsprintf,text,frmt,ebx,eax     ;// (числа в символы)

         invoke  MessageBox,0,text,capt,0
         invoke  ExitProcess, 0
.end start
ep.png


Попробуем сверить результаты с выхлопами HIEW’a и отладчика WinDbg..
Значит запускаем Hiew и открыв в нём User32.dll пару раз жмём Enter. Клавиша Enter переключает режимы отображения в Hiew на Text/Hex/Decode – нам нужен любой режим, кроме Text. В каждом из режимов своя строка-меню и соответственно свои опции. Теперь в режиме Hex жмём на F8 и получаем содержимое РЕ-заголовка, с технической информацией о дисковом образе файла. Меню F8 открывает вход в закрома библиотеки, где можно ознакомится со-списком импорта(F7) , или экспорта(F9) – выбираем экспорт и мотаем список, пока не уткнёмся в функцию MessageBoxA().

Первый стоблец в окне – это ‘ординал функции’, т.е. её порядковый номер внутри библиотеки. Hiew предлагает нам отсортивать список клавишами F2-3-4 по трём критериям – ординалам, адресу или имени. После выбора функции по Enter, мы попадаем в аккурат на дизассемблерный код выбранной функции.

Чтобы проделать аналогичную операцию в WinDbg, достаточно открыть скомпилированную программу ‘HelloWorld’ комбинацией клавиш Ctrl+E, и дать запрос на дизассемблирование функции командой ‘u MessageBoxA’ (unassemble). У меня получилось так:

mBox.png



4.3.1. Подготовительные операции

Поскольку мы хотим произвести запись в часть нашего пространства где тусуются системные библиотеки (адрес выше 0х70000000), для начала посмотрим, доступен-ли вообще данный регион для записи, для чего затребуем карту MemoryMap в отладчике OllyDbg.

noWrite.png


Упс.. система выставила им/всем атрибуты ‘только-чтение’, что и неудивительно – должна-же она предпринимать хоть какие-то попытки, пусть и сугубо ламерские. В наших штанинах есть функция VirtualProtect(), которая сможет переназначить доступ, добавив к ‘R’ буковку ‘W’. Этикет программистов предписывает после записи восстанавливать предыдущие атрибуты, и если мы входим в их сообщество, должны придерживаться этих правил. VirtualProtect() служит для переназначения атрибутов страниц текущего процесса, она-же с окончанием(Ex) манипулирует с защитой страниц чужих процессов. Вот её прототип:

C-подобный:
VirtualProtect (
    lpvAddress,         // адрес защищаемого блока
    dwSize,             // размер защищаемого блока
    fdwNewProtect,      // новые флаги защиты
    fdwOldProtect  );   // переменная, в которую записываются старые флаги

Константы флагов защиты
-----------------------------------
#define PAGE_NOACCESS          0x01     // страница без атрибутов (зарезервирована)
#define PAGE_READONLY          0x02
#define PAGE_READWRITE         0x04
#define PAGE_WRITECOPY         0x08
#define PAGE_EXECUTE           0x10
#define PAGE_EXECUTE_READ      0x20
#define PAGE_EXECUTE_READWRITE 0x40
#define PAGE_EXECUTE_WRITECOPY 0x80
#define PAGE_GUARD             0x100    // сторожевая страница стека
#define PAGE_NOCACHE           0x200    // страницу нельзя выгружать в файл-подкачки.
Если функция терпит неудачу, то возвращаемое значение EAX=0.
Аргументы lpvAddress и dwSize служат для указания диапазона адресов защищаемой памяти. Поскольку функция работает только с 4К-байтными страницами, то и начальный адрес должен быть выровнен на границу 0х1000 байт. Для выравнивания границ адресов как-нельзя лучше подходит ассемблерная инструкция AND. Вторым операндом она требует маску, по которой сбрасывает в двоичном числе те биты, которые в маске установлены в нуль.

Например из предыдущего скрина мы уже знаем, что адрес MessageBox() в библиотеке User32.dll равен 0x7E3A07EA, и чтобы выровнить его на 4К-байтную границу, нужно сбросить три/младших разряда в нуль. Значит нужна маска FFFFF000, что равнозначно числу -1000h. Тогда получим адрес 0х7E3A0000, в диапазоне которого и будет находится точка-входа в MessageBox().



4.3.2. Перехват API методом сплайсинга, в своём пространстве.

Будем считать, что пусть и не до блеска, но чуть заточили своё оружие, и теперь можно осуществить практический перехват системной API. В конечном итоге алгорит будет такой:
  1. GetProcAddress() – найти адрес точки-входа в перехватываемую функцию;
  2. Инструкция AND – выровнить этот адрес на 4К-байтную границу;
  3. VirtualProtect() – переназначить атрибуты данного региона памяти, с доступом на запись;
  4. Инструкция REP MOVSB – сохранить первые 5-байт из точки-входа в функцию, в свой буфер;
  5. Инструкция REP STOSB – записать на их место, JMP-переход на свою/фиктивную функцию.
Суть перехвата API в том, чтобы при вызове функции системой, изменить ход событий в свою пользу, вставив в начало обработчика свой дальний переход JMP far. Её опкодом является всего 1-байт со-значением EАh и если добавить к нему 4-байтный адрес нашей процедуры, то в сумме получим как-раз 5 байт, которые можем вставить в пролог оригинальной функции Win-API.

В демонстрационном примере, я буду перехватывать тот-же MessageBox(). Моя подставная функция будет просто расшифровывать выводимую на экран строку, после чего вернёт управление опять оригинальной функции, чтобы она вывела её на экран. Замечу, что метод шифрования тут не важен, главное понять сам принцип перехвата системных функций:

C-подобный:
include  'win32ax.inc'
.data
capt       db  'Splicing',0
text       db  'Пример перехвата API-функций',13,10
           db  'Эта строка была дешифрована!',0
mesLen      =   $-text
libName    db  'user32.dll',0
fName      db  'MessageBoxA',0
oldByte    db   7 dup(0)               ;// под оригинальные 7-байт функции
ePoint     dd   0                      ;// под оригинальный адрес MessageBox()
oldFlags   dd   0                      ;// прежние флаги защиты для VirtualProtect()
;-------
.code
start:
;// Получаем адрес функции
         invoke  GetModuleHandle,libName
         invoke  GetProcAddress,eax,fName
         mov     [ePoint],eax              ;// запомнить

;// Выравниваем адрес на 4К страницу,
;// и выставляем этой странице атрибуты R/W
         and     eax,-1000h
         invoke  VirtualProtect,eax,1000h,PAGE_READWRITE,oldFlags

;// Копируем оригинальные 7-байт из функции, в свой буфер
         mov     esi,[ePoint]
         mov     edi,oldByte
         mov     ecx,7
         push    esi                 ;// запомнить точку-входа в функцию..
         rep     movsb

;// Перехват API-функции!
;// вставляем на место 7-ми байт, переход на свою процедуру
         pop     edi                 ;// точка-входа в функцию..
         mov     al,0xEA             ;// опкод инструкции 'JMP_FAR'
         stosb                       ;// вставить первым байтом! (EDI сам сдвинулся)
         mov     eax,@crypt          ;// EAX = адрес нашей функции
         stosd                       ;// вставить как DWORD!
         mov     ax,cs               ;// АХ = селектор сегмента-кода
         stosw                       ;// вставить как WORD! Итого 7-байт.

         invoke  MessageBox,0,text,capt,0
         invoke  ExitProcess, 0

;//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
;//-- Подставная функция MessageBox() ------
;//-- дешифрует указанную строку в секции-данных
@crypt:  push    esi edi ecx         ;// запомнить модифицируемые регистры
         mov     ecx,mesLen          ;// длина строки,
         mov     esi,text            ;// ..и её адрес
@001:    xor     byte[esi],7Eh       ;// дешифруем байтики ключом 0х7Е
         inc     esi                 ;// сл.байт..
         loop    @001                ;// повторить ЕСХ-раз!

;// Восстанавливаем перехваченную функцию!
         mov     esi,oldByte         ;// источник = оригинальные 7-байт
         mov     edi,[ePoint]        ;// приёмник = точка-входа в функцию
         mov     ecx,7               ;// длина участка кода
         rep     movsb               ;// скопировать из ESI в EDI.

         pop     ecx edi esi         ;// восстанавливаем регистры
         push    [ePoint]            ;// засылаем в стек точку-входа в MessageBox()
         ret                         ;// передаём на неё управление!
.end start
Теперь запускаем это приложение и видим такую форточку..
Вместо указанной строки получили абракадабру, а это означает, что наш код исправно сработал, и функция MessageBox() перехвачена! Если-бы мы увидели что-то другое, тогда нужно было-бы искать ошибки в отладчике. Как говорят в народе: - ‘Если отладка это поиск ошибок, то программирование – их создание’:


mBox_0.png


Такое окно предстало нашему взору потому, что функция @crypt в нашем коде не расшифровывает строку, а наоборот шифрует её по XOR с ключом 0x7Eh. Учитывая тот факт, что операция XOR обратима (т.е. повторный XOR таким-же ключом восстанавливает прежнее значение), сейчас мы должны зашифровать данную строку, тогда программа исправно её дешифрует и мы увидим долгожданный текст.


4.3.3. Ручное шифрование строк в HIEW’e

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

data_0.png


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

На данном этапе, нам нужно проксорить эту строку ключом 7Eh в образе программы на диске. Такое шифрование назвали офф-лайн шифрованием, а в качестве инструмента в самый цвет подходит хек-редактор HIEW. Откроем в нём свою прожку, после чего нажмём такую последовательность клавиш: Enter -> F8 -> F6 -> (.data) -> Enter. Так мы уткнёмся в начало секции данных, но к сожалению Hiew не понимает кириллицу и придётся ориентироваться на терминальный нуль строки, которым она заканчивается:


data_1.png


Теперь у нас есть начало строки и можно приступить непосредственно к шифрованию. Обязательным условием является одинаковый ключ ксора 0х7Е, иначе дешифрация потерпит крах. Шифровать будем весь выделенный блок, включая терминальный нуль, т.к. процедура дешифрации учитывает и его. Значит установив стрелками указатель на начало блока с байтом CFh, жмём последовательность F3 (edit) -> F8 (xor) и в поле ‘Hex’ вводим значение ключа 7Е. Теперь подтверждаем намерения по Enter, и той-же клавишей F8 шифруем весь блок. Каждый зашифрованный байт выделяется жёлтым цветом. По окончании, применяем изменения клавишей F9 (update) и получаем такую картину:

data_2.png


data_3.png


Если после всех телодвижений теперь запустить нашу прожку, то функция @crypt в программе начнёт дешифрацию текста тем-же ключом, которым мы и шифровали. В результате текст начнёт мутировать в оригинальную строку, которую и сбоксит в окошко оригинальная MessageBox(). За процессом дешифровки можно понаблюдать в отладчике:

mBox_1.png


Здесь мы рассмотрели только один метод перехвата API, хотя есть и другие – например модификация секции-импорта. Но для этого метода нужно подробно изучить формат и структуру заголовка РЕ-файла, чем мы займёмся как-нибудь в другой раз.
 
Последнее редактирование:

Сергей Попов

Кодебай
30.12.2015
4 691
6 584
BIT
291
4.3. Перехват API-функций
Комментарии не индексируются поисковиками. Ваши комментарии тянут на полноценную статью. Было бы здорово, если бы Вы прислушались к моему мнению, и начали публиковать подобные комментарии отдельными статьями. Это даст море целевого трафика на Ваши статьи.
 
  • Нравится
Реакции: Vertigo и Marylin

CKAP

Green Team
10.06.2019
68
39
BIT
0
Короче, не чего не смог найти в сети по импорту и экспорту либов. Ну как бы по названиям понятно о чем речь но картина в целом не ясна.

>В реальном софте строки несут в себе большую смысловую нагрузку, по которым можно выстроить общую картину работы программы

Вот как её выстраивать, мм.. назовем условно дорожной картой. Ну скажем есть exe, он по любому грузит в себя либ кернел, юзер в .data , что то я так понимаю инициализирует перед Энтри Поинт.. геты я вижу всякие курсора, иконок..

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

Благодарю.
 

Marylin

Mod.Assembler
Red Team
05.06.2019
295
1 310
BIT
89
Короче, не чего не смог найти в сети по импорту и экспорту либов.
Классикой являются следующие доки, которые должны быть у каждого разработчика:
  • Джеффри Рихтер - Windows для профессионалов (издание 4)
  • Марк Руссинович - Внутреннее устройство Windows (издание 6)
  • Эндрю Танненбаум - Современные операционные системы (издание 4)
Вот как её выстраивать, мм.. назовем условно дорожной картой.
думаю это придёт с опытом, поэтому нужно больше практиковаться

что бы видеть что с чем общается.
Есть признанный в котором имеются перекрёстные ссылки, т.е. он связывает стрелками переходы. Попробуйте использовать его, хотя лично я им редко пользуюсь, т.к. этот-же функционал есть и у HIEW'a. Но это дело вкуса..
 
E

EdmonDantesDuma

Спасибо Вам за труд. Очень познавательно, доходчиво и интересно. Очень будем ждать продолжения. Спасибо.
 
  • Нравится
Реакции: Marylin

SearcherSlava

Red Team
10.06.2017
943
1 259
BIT
109
Классикой являются следующие доки, которые должны быть у каждого разработчика:
  • Джеффри Рихтер - Windows для профессионалов (издание 4)
  • Марк Руссинович - Внутреннее устройство Windows (издание 6)
  • Эндрю Танненбаум - Современные операционные системы (издание 4)

думаю это придёт с опытом, поэтому нужно больше практиковаться


Есть признанный в котором имеются перекрёстные ссылки, т.е. он связывает стрелками переходы. Попробуйте использовать его, хотя лично я им редко пользуюсь, т.к. этот-же функционал есть и у HIEW'a. Но это дело вкуса..

Здрав будь!

Можно & нужно почитать по IDA-PRO:

Chris Eagle.THE IDA PRO BOOK. The Unofficial Guide to the World’s Most Popular Disassembler. 1 & 2nd Edition
Chris Eagle. Reverse Engineering with Ida Pro
Pearce W. Reverse Engineering Code with IDA Pro
Brdarevic Omega. Hacker Tools Crack With Disassembling
Wong Reginald. Mastering Reverse Engineering
Dennis Yurichev. Reverse Engineering for Beginners
Крис Касперски. Техника отладки программ без исходных текстов
Крис Касперски. Образ мышления - дизассемблер IDA PRO
Крис Касперски. Фундаментальные основы хакерства. Искусство дизассемблирования
Крис Касперски. Искусство дизассемблирования
Крис Касперски. Тонкости дизассемблирования
Крис Касперски. Техника и философия хакерских атак. Записки мыщ'а
Pirogov Vlad. Disassembling Code IDA Pro and SoftICE
Пирогов В.Ю. Ассемблер и дизассемблирование
 
Последнее редактирование:
  • Нравится
Реакции: bin1101d и Marylin

Hardreversengineer

Green Team
20.08.2019
48
26
BIT
3
4.1.4.4. Простое многопоточное приложение

У меня код из примера не работает. Запускается и сразу исчезает (проверено на разных ПК), я прописал invoke Sleep,1000 перед Invoke Exitprocess, окно остаётся, сообщение не прописывается (пустое консольное окно), создаётся файл backup спустя доли секунды(при чём до слипа не создавался), он тоже пустой. Как поправлять ?
 
  • Нравится
Реакции: Marylin
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!