Немножко контекста проблемы:
Решил я пописать несколько программок под win16, написал потестил - на 32битных Windows запускаются, на 64битных естественно (а так ли это естественно) нет - падают с ошибкой, мол они валидные, но для другой архитектуры(машины).
В общем натолкнулся на исполнимый файл формата NE-executable для подсистемы win16, самое удивительное в отличие от всех других NE файлов в 64битном окружении он запускался.
Этот экзотический файл acmboot.exe был взят из embedded Visual C++ 4.0. (Это просто файл загрузчик, он наверняка есть и в других дистрибутевах MS от того времени)
73кб - не самый удобный размер, поэтому было решено пойти по следующему алгоритму:
1. минимизировать логику программы с сохранением возможности запуска в 64разрядном окружении, а для этого
1.а. исключить из исполнения ЯВУ инициализации (их 3 последовательных вызова cinit, setargv, setenvp)
1.б. оставить в качестве полезной логики только вывод сообщения (благо такой кусочек кода, и при том очень удобный есть, он конечно использует глобальные переменные, но есть другой кусок где эти переменные загружаются из ресурса таблицы строк, и то и другое не требует ничего что было отсечено на шаге 1.а.)
2. залить неиспользуемые участки кода значением -1 (в случае PE файлов можно 0 заливать, но в случае NE формата релокации формируются в цепочки, а маркер конечного звена таких цепочек -1) - это будет гарантировать, что код который мы явно подразумеваем как явно не используемый не будет как-нибудь использован неявно - иначе после такого патча программа вылетит и это будет свидетельствовать о том что проблему нужно копать глубже
3. убрать ненужные релокации туда где все залито минус единицами
4. Сжать файл сократив размеры секций за счет неиспользуемого кода на этапах 2 и за счет сокращения размеров релокаций этапа 3, естественно сжимать не бездумно - править содержимое NE структуры, так чтоб ссылки на начала секций указывали на их новые начала, править их размеры, все прочие ссылки на начала других таблиц также править.
Алгоритм показался простым - планировалось сжатие подопытного до 3-10кб (я причислял возможность запускаться в 64битной среде к "правильному" содержимому NE заголовка и, возможно, какому то особенному содержимому других вспомогательных таблиц, на которые этот заголовок ссылается.
В общем как патчил
сказвно сделано(хекс редактор и...):
__stubmain не делает ничего полезного, только вызывает WINMAIN, параметры самого __stubmain никак не используются, поэтому нет ничего страшного, что убит код где они инициализируются.
конечно и у __stubmain и у WINMAIN по 5 параметров и все они берутся из секции данных можно было бы перебить релоки и параметров и вызова так чтоб вместо __stubmain сразу вызывался WINMAIN. Но проще использовать пока как есть вызывать__stubmain, а он вызывает WINMAIN.
перед WINMAIN есть вкусный участок кода:
но Text и Caption не инициализированы,
зато в самой WINMAIN есть код их инициализации, почти сразу же после входа в функцию
итак патчим:
в 32битных системах патч привел к тому, что отображается MessageBox с заголовком "Setup initialization error" и текстом "Insufficent memory" эти строки как раз содержимое ресурса таблиц строк под номерами 22 и 23.
НО!!!!!!!!!!!
в 64битных системах логика программы осталась неизменной выводится MessageBox с заголовком "Setup initialization error" и текстом "Setup cannot access the required initialization file '...\ACMBOOT.LST'." (потому что идет проверка есть ли данный файл - НО МЫ УБИЛИ ВСЮ ЛОГИКУ ПРОВЕРКИ, она была в WINMAIN но мы ее изрешетили jmp`ами так, чтоб никаких проверок, а сразу MessageBox), более того если создадим ACMBOOT.LST с произвольным содержимым, то выводится MessageBox с заголовком "Setup initialization error" и текстом "This Setup program is not intended to be used with your version of Windows." , и даже если перебъем ресурс таблицы строк другим текстом, текст отображается именно этот.
Пробовал патченный файл запускать в 64битном окружении новой совершенно чистой свежепоставленной виртуальной машиной - то же самое - функционал откуда то подхватывается.
Вот теперь сам вопрос знатокам, acmboot.exe как то эмулировано прямо в NTVDM на 64битных системах? Откуда система берет код в 64битном окружении? спасибо.
П.С. где достать этот acmboot.exe я уже написал (можно в официальном источнике, и пропатчить так как было изложено выше), а для ленивых(и смелых) пропатченный экзешник и IDAPro`шные *.idb к нему и к оригиналу - в приложенном архиве. до winXP включительно просто запускается, на поздних windows wowexec требует админских привелегий. (Этот патченный экзешник содержит только 16разрядный код, который в 32разрядном окружении ведет себя как задумано патчем, а в 64разрядном так как предусмотрела микрософт независимо от патча (у него нет никакого кода ни для win32 ни для win64))
Решил я пописать несколько программок под win16, написал потестил - на 32битных Windows запускаются, на 64битных естественно (а так ли это естественно) нет - падают с ошибкой, мол они валидные, но для другой архитектуры(машины).
В общем натолкнулся на исполнимый файл формата NE-executable для подсистемы win16, самое удивительное в отличие от всех других NE файлов в 64битном окружении он запускался.
Этот экзотический файл acmboot.exe был взят из embedded Visual C++ 4.0. (Это просто файл загрузчик, он наверняка есть и в других дистрибутевах MS от того времени)
73кб - не самый удобный размер, поэтому было решено пойти по следующему алгоритму:
1. минимизировать логику программы с сохранением возможности запуска в 64разрядном окружении, а для этого
1.а. исключить из исполнения ЯВУ инициализации (их 3 последовательных вызова cinit, setargv, setenvp)
1.б. оставить в качестве полезной логики только вывод сообщения (благо такой кусочек кода, и при том очень удобный есть, он конечно использует глобальные переменные, но есть другой кусок где эти переменные загружаются из ресурса таблицы строк, и то и другое не требует ничего что было отсечено на шаге 1.а.)
2. залить неиспользуемые участки кода значением -1 (в случае PE файлов можно 0 заливать, но в случае NE формата релокации формируются в цепочки, а маркер конечного звена таких цепочек -1) - это будет гарантировать, что код который мы явно подразумеваем как явно не используемый не будет как-нибудь использован неявно - иначе после такого патча программа вылетит и это будет свидетельствовать о том что проблему нужно копать глубже
3. убрать ненужные релокации туда где все залито минус единицами
4. Сжать файл сократив размеры секций за счет неиспользуемого кода на этапах 2 и за счет сокращения размеров релокаций этапа 3, естественно сжимать не бездумно - править содержимое NE структуры, так чтоб ссылки на начала секций указывали на их новые начала, править их размеры, все прочие ссылки на начала других таблиц также править.
Алгоритм показался простым - планировалось сжатие подопытного до 3-10кб (я причислял возможность запускаться в 64битной среде к "правильному" содержимому NE заголовка и, возможно, какому то особенному содержимому других вспомогательных таблиц, на которые этот заголовок ссылается.
В общем как патчил
Код:
cseg01:001A start proc near
cseg01:001A
cseg01:001A ; FUNCTION CHUNK AT cseg01:0012 SIZE 00000008 BYTES
cseg01:001A ; FUNCTION CHUNK AT cseg01:1BC8 SIZE 00000039 BYTES
cseg01:001A
cseg01:001A xor bp, bp
cseg01:001C push bp
cseg01:001D call INITTASK
cseg01:0022 or ax, ax
cseg01:0024 jz short _error
cseg01:0026 mov word_CA58, es
cseg01:002A add cx, 256
cseg01:002E jb short _error
cseg01:0030 mov StackSize, cx
cseg01:0034 mov hPrevInstance, si
cseg01:0038 mov hInstance_0, di
cseg01:003C mov word ptr lpszCmd, bx
cseg01:0040 mov word ptr lpszCmd+2, es
cseg01:0044 mov cmdShow, dx
cseg01:0048 mov ax, -1
cseg01:004B push ax ; UINT
cseg01:004C call LOCKSEGMENT
cseg01:0051 call GETVERSION
cseg01:0056 xchg al, ah
cseg01:0058 mov version, ax
cseg01:005B
cseg01:005B loc_5B:
cseg01:005B mov ah, DOS_GET_DOS_VERSION
cseg01:005D
cseg01:005D loc_5D:
cseg01:005D test cs:lpWinFlags, WF_PMODE
cseg01:0064 jz short loc_6D
cseg01:0066 call DOS3CALL
cseg01:006B jmp short loc_6F
cseg01:006D ; ---------------------------------------------------------------------------
cseg01:006D
cseg01:006D loc_6D: ; CODE XREF: start+4Aj
cseg01:006D int 21h ; DOS -
cseg01:006F
cseg01:006F loc_6F: ; CODE XREF: start+51j
cseg01:006F mov DOSversion, ax
cseg01:0072 xchg al, ah
cseg01:0074 mov DOSversionInverted, ax
cseg01:0077 test cs:lpWinFlags, WF_PMODE
cseg01:007E jnz short loc_85
cseg01:0080 mov al, FALSE
cseg01:0082 mov PROTECTED_MODE, al
cseg01:0085
cseg01:0085 loc_85: ; CODE XREF: start+64j
cseg01:0085 xor ax, ax
cseg01:0087 push ax
cseg01:0088 call WAITEVENT
cseg01:008D push hInstance_0
cseg01:0091 call INITAPP
cseg01:0096 or ax, ax
cseg01:0098 jnz short loc_9D ; идеальное место для патча надо прыжок не в loc_9D, а в loc_AC
cseg01:009A jmp _error
cseg01:009D ; ---------------------------------------------------------------------------
cseg01:009D
cseg01:009D loc_9D: ; CODE XREF: start+7Ej
cseg01:009D call __cinit
cseg01:00A2 call far ptr __setargv
cseg01:00A7 call __setenvp
cseg01:00AC nop
cseg01:00AD nop
cseg01:00AE nop
cseg01:00AF push word_CA9A ; int
cseg01:00B3 push word_CA98 ; int
cseg01:00B7 push word_CA96 ; int
cseg01:00BB push word_CA94 ; int
cseg01:00BF push word_CA92 ; int
cseg01:00C3 call __stubmain
cseg01:00C8 add sp, 0Ah
cseg01:00CB push ax ; int
cseg01:00CC call _exit
cseg01:00CC start endp ; sp-analysis failed
Код:
cseg01:0098 jnz short loc_AC
cseg01:009A jmp loc_12
cseg01:009A ; ---------------------------------------------------------------------------
cseg01:009D db 0Dh dup(0FFh) ;блок кода вызывающий ЯВУ инициализации сразу затер минус единицами FF
cseg01:00AA dw seg cseg01 ; на место одной затертости села релокация, но это не сделало файл невалидным -1 то это окончание цепи релоков
cseg01:00AC ; ---------------------------------------------------------------------------
cseg01:00AC
cseg01:00AC loc_AC: ; CODE XREF: start+7Ej
__stubmain не делает ничего полезного, только вызывает WINMAIN, параметры самого __stubmain никак не используются, поэтому нет ничего страшного, что убит код где они инициализируются.
конечно и у __stubmain и у WINMAIN по 5 параметров и все они берутся из секции данных можно было бы перебить релоки и параметров и вызова так чтоб вместо __stubmain сразу вызывался WINMAIN. Но проще использовать пока как есть вызывать__stubmain, а он вызывает WINMAIN.
перед WINMAIN есть вкусный участок кода:
Код:
cseg01:8B31 ; ---------------------------------------------------------------------------
cseg01:8B31
cseg01:8B31 loc_8B31: ; CODE XREF: ShowErrorMessage+34j
cseg01:8B31 ; ShowErrorMessage+51j
cseg01:8B31 push hwnd ; hWnd
cseg01:8B35 push ds
cseg01:8B36 push offset Text ; lpText
cseg01:8B39 push ds
cseg01:8B3A push offset Caption ; lpCaption
cseg01:8B3D push MB_ICONHAND ; uType
cseg01:8B3F call MESSAGEBOX
cseg01:8B44 xor ax, ax
зато в самой WINMAIN есть код их инициализации, почти сразу же после входа в функцию
Код:
cseg01:8B91 mov cx, [bp+hInstance]
cseg01:8B94 mov hInstance, cx
cseg01:8B98 push cx ; hInstance
cseg01:8B99 push 22 ; uID
cseg01:8B9B push ds
cseg01:8B9C push offset Text ; lpBuffer
cseg01:8B9F push _MAX_PATH ; cchBufferMax
cseg01:8BA2 call LOADSTRING
cseg01:8BA7 or ax, ax
cseg01:8BA9 jz short loc_8BD3
cseg01:8BAB push hInstance ; hInstance
cseg01:8BAF push 23 ; uID
cseg01:8BB1 push ds
cseg01:8BB2 push offset Caption ; lpBuffer
cseg01:8BB5 push _MAX_PATH ; cchBufferMax
cseg01:8BB8 call LOADSTRING
cseg01:8BBD or ax, ax
cseg01:8BBF jz short loc_8BD3
cseg01:8BC1 push hInstance ; hInstance
cseg01:8BC5 push 24 ; uID
cseg01:8BC7 push ds
cseg01:8BC8 push offset byte_C7DC ; lpBuffer
cseg01:8BCB push _MAX_PATH ; cchBufferMax
cseg01:8BCE call LOADSTRING
Код:
cseg01:8B31 ; ---------------------------------------------------------------------------
cseg01:8B31 ; START OF FUNCTION CHUNK FOR WINMAIN
cseg01:8B31
cseg01:8B31 loc_8B31: ; CODE XREF: cseg01:89F2j
cseg01:8B31 ; cseg01:8A0Fj ...
cseg01:8B31 push word_C5D2 ; HWND
cseg01:8B35 push ds
cseg01:8B36 push offset byte_C5D4 ; LPCSTR
cseg01:8B39 push ds
cseg01:8B3A push offset byte_C6D8 ; LPCSTR
cseg01:8B3D push 10h ; UINT
cseg01:8B3F call MESSAGEBOX
cseg01:8B44 xor ax, ax
cseg01:8B46
cseg01:8B46 loc_8B46: ; CODE XREF: cseg01:89D7j
cseg01:8B46 ; cseg01:8B2Fj
cseg01:8B46 mov sp, bp ; сюда дописываем выход из процедуры
cseg01:8B48 pop bp
cseg01:8B49 retf
cseg01:8B49 ; END OF FUNCTION CHUNK FOR WINMAIN
cseg01:8B49 ; ---------------------------------------------------------------------------
cseg01:8B4A db 4 dup(0)
Код:
cseg01:8B4E ; =============== S U B R O U T I N E =======================================
cseg01:8B4E
cseg01:8B4E ; Attributes: bp-based frame
cseg01:8B4E
cseg01:8B4E ; int __pascal __far WINMAIN(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPCSTR lpCmdLine, int nShowCmd)
cseg01:8B4E WINMAIN proc far ; CODE XREF: __stubmain+1EP
cseg01:8B4E
cseg01:8B4E nShowCmd = word ptr 6
cseg01:8B4E lpCmdLine = dword ptr 8
cseg01:8B4E hPrevInstance = word ptr 0Ch
cseg01:8B4E hInstance = word ptr 0Eh
cseg01:8B4E
cseg01:8B4E ; FUNCTION CHUNK AT cseg01:8B31 SIZE 00000019 BYTES
cseg01:8B4E
cseg01:8B4E push bp ; а здесь был сложный вход в процедуру с выделением места под локальные переменные, но нам нужно максимально просто
cseg01:8B4F mov bp, sp
cseg01:8B51 jmp short loc_8B91 ; наши столь нужные переходы
cseg01:8B51 ; ---------------------------------------------------------------------------
cseg01:8B53 db 2 dup(90h)
cseg01:8B55 ; ---------------------------------------------------------------------------
cseg01:8B55
cseg01:8B55 loc_8B55: ; CODE XREF: WINMAIN:loc_8BD3j
cseg01:8B55 jmp short loc_8B31 ; наши столь нужные переходы
cseg01:8B55 ; ---------------------------------------------------------------------------
cseg01:8B57 db 0D8h,81h,0ECh,88h,6,57h,56h,8Eh,6,0Ah,0Bh,26h,0A1h
cseg01:8B57 db 0E2h,0B6h,26h,8Bh,16h,0E4h,0B6h,89h,86h,36h,0FFh,89h
cseg01:8B57 db 96h,38h,0FFh,33h,0C0h,0B9h,4Eh,0,8Dh,0BEh,3Ah,0FFh
cseg01:8B57 db 16h,7,0F3h,0ABh,2Bh,0C9h,89h,4Eh,0ECh,89h,4Eh,0EAh
cseg01:8B57 db 89h,46h,0E6h,89h,46h,0E8h,0A2h,56h,4Ah
cseg01:8B91 ; ---------------------------------------------------------------------------
cseg01:8B91
cseg01:8B91 loc_8B91: ; CODE XREF: WINMAIN+3j
cseg01:8B91 mov cx, [bp+hInstance]
cseg01:8B94 mov hInstance, cx
cseg01:8B98 push cx ; hInstance
cseg01:8B99 push 22 ; uID
cseg01:8B9B push ds
cseg01:8B9C push offset Text ; lpBuffer
cseg01:8B9F push _MAX_PATH ; cchBufferMax
cseg01:8BA2 call LOADSTRING
cseg01:8BA7 or ax, ax
cseg01:8BA9 jz short loc_8BD3
cseg01:8BAB push hInstance ; hInstance
cseg01:8BAF push 23 ; uID
cseg01:8BB1 push ds
cseg01:8BB2 push offset Caption ; lpBuffer
cseg01:8BB5 push _MAX_PATH ; cchBufferMax
cseg01:8BB8 call LOADSTRING
cseg01:8BBD or ax, ax
cseg01:8BBF jz short loc_8BD3
cseg01:8BC1 push hInstance ; hInstance
cseg01:8BC5 push 24 ; uID
cseg01:8BC7 push ds
cseg01:8BC8 push offset byte_C7DC ; lpBuffer
cseg01:8BCB push _MAX_PATH ; cchBufferMax
cseg01:8BCE call LOADSTRING
cseg01:8BD3
cseg01:8BD3 loc_8BD3: ; CODE XREF: WINMAIN+5Bj
cseg01:8BD3 ; WINMAIN+71j
cseg01:8BD3 jmp short loc_8B55 ; наши столь нужные переходы
cseg01:8BD3 WINMAIN endp
в 32битных системах патч привел к тому, что отображается MessageBox с заголовком "Setup initialization error" и текстом "Insufficent memory" эти строки как раз содержимое ресурса таблиц строк под номерами 22 и 23.
НО!!!!!!!!!!!
в 64битных системах логика программы осталась неизменной выводится MessageBox с заголовком "Setup initialization error" и текстом "Setup cannot access the required initialization file '...\ACMBOOT.LST'." (потому что идет проверка есть ли данный файл - НО МЫ УБИЛИ ВСЮ ЛОГИКУ ПРОВЕРКИ, она была в WINMAIN но мы ее изрешетили jmp`ами так, чтоб никаких проверок, а сразу MessageBox), более того если создадим ACMBOOT.LST с произвольным содержимым, то выводится MessageBox с заголовком "Setup initialization error" и текстом "This Setup program is not intended to be used with your version of Windows." , и даже если перебъем ресурс таблицы строк другим текстом, текст отображается именно этот.
Пробовал патченный файл запускать в 64битном окружении новой совершенно чистой свежепоставленной виртуальной машиной - то же самое - функционал откуда то подхватывается.
Вот теперь сам вопрос знатокам, acmboot.exe как то эмулировано прямо в NTVDM на 64битных системах? Откуда система берет код в 64битном окружении? спасибо.
П.С. где достать этот acmboot.exe я уже написал (можно в официальном источнике, и пропатчить так как было изложено выше), а для ленивых(и смелых) пропатченный экзешник и IDAPro`шные *.idb к нему и к оригиналу - в приложенном архиве. до winXP включительно просто запускается, на поздних windows wowexec требует админских привелегий. (Этот патченный экзешник содержит только 16разрядный код, который в 32разрядном окружении ведет себя как задумано патчем, а в 64разрядном так как предусмотрела микрософт независимо от патча (у него нет никакого кода ни для win32 ни для win64))
Вложения
Последнее редактирование: