Windows shellcode编写和提取细节
liebian365 2024-10-20 10:00 27 浏览 0 评论
原创: EDI-VOID 合天智汇
0x00 shellcode编写
首先shellcode的编写可以用纯汇编也可以用c++,其两者难易程度可想而知,还是抱住VS的大腿,不过这其中要注意一些代码格式和编译选项,以确保生成的shellcode是地址无关的
比如像如下代码都会被编译器优化,编译器会把字符串放在常量区,以下代码在vc6能通过,vs2017这些新版本的不能通过,新版本的语意更严谨。
char*arr = "test";
实际上是constchar*arr = "test";
Vc6中反汇编代码如下
可以看到字符串指针是一个固定的地址,在前面加上const关键字后会发现生成的代码是一样的,可见旧版本的编译器很善解人意,把你不严谨的代码改了
但是如果以以下的形式写的话,就成了地址无关的代码
charcmd[] = { 'c','a','l','c','\x00'};
这样写的需要我们在数组末尾手动添加截断字符
字符串的绝对地址引用问题解决了,下一个就是函数的调用,是dll的加载基址相关的,不同的操作系统某个dll的加载基址不一样,就需要动态定位,需要得到别的api的地址的话,首先我们需要得到dll的基址,可以通过进程PEB结构中的成员来遍历,遍历到kernel32.dll以后再遍历到GetProcAddress函数的地址和LoadLibrary的地址就万事大吉了,别的函数只需要这两个函数来获取了
首先获取PEB结构可以直接调用winternl.h里的一个宏
_PEB*peb = NtCurrentTeb()->ProcessEnvironmentBlock;
查看其宏定义看似调用了函数,查看反汇编代码发现实则是地址无关的代码
mov eax,dword ptr fs:[00000018h]
mov ecx,dword ptr [eax+30h]
也可以参照teb的结构自己来实现一个GetPeb函数让指令使用更少的字节
fs:[0x18]处是一个指向TEB自身的指针,TEB结构0x30处是PEB指针
得到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; }
运行一下成功弹出了calc
下面我们一起来看看这个都脚本做了什么
首先我们先总结一下我们exe的现状,关键逻辑的部分没有绝对地址的引用,都是地址无关的代码,我们的func函数被放到了线性地址的开头,在所有其他的函数之前,_main函数在我们定义的所有的函数的末尾
如图所示
_main函数在最后一个,那么他的起始地址就是我们shellcode的长度了,即前面我们从func开始所有的函数的集合的长度
第一步就是获取这个长度
获取.text节中这个长度的shellcode,然后给shellcode添加loader,接着修复重定位,查找shellcode里不包含的byte,范围0x00到0xff,找到shellcode里不存在的byte就可以进行异或加密,从而剔除\x00截断字符了,最后再添加解密代码进去,如果没有不存在的字符的话,如果shellcode涉及的字符范围很全面,我们就没有可以挑选来进行异或的操作数了,这样可以把shellcode适当分成两部分或者多部分,分别用不同的操作数进行异或操作
其他的不用说,我们主要看看两个loader处用到的技巧
q # call here # here: # ... # shellcode_start: # <shellcode> # relocs: # off1|off2|... # str1|str2|... code= [ 0xE8,0x00, 0x00, 0x00, 0x00, # CALL here #here: 0x5E, # POP ESI 0x8B,0xFE, # MOV EDI, ESI 0x81,0xC6, x[0], x[1], x[2], x[3], # ADD ESI, shellcode_start +len(shellcode) - here 0xB9,y[0], y[1], y[2], y[3], # MOV ECX, len(relocs) 0xFC, # CLD #again: 0xAD, # LODSD 0x01,0x3C, 0x07, # ADD [EDI+EAX], EDI 0xE2,0xFA # LOOP again #shellcode_start: ]
这里第一句的call,因为我们不能直接操作指令指针寄存器,我们可以通过call下一条指令来压入下一条指令的地址,然后通过popesi放到esi寄存器里,然后参照后面的relocs修正相对偏移
然后异或加密剔除\x00的部分
code= [ 0xE8,0xFF, 0xFF, 0xFF, 0xFF, # CALL $ + 4 #here: 0xC0, # (FF)C0 = INC EAX 0x5F, # POP EDI 0xB9,xor1[0], xor1[1], xor1[2], xor1[3], # MOV ECX, <xorvalue 1 for shellcode len> 0x81,0xF1, xor2[0], xor2[1], xor2[2], xor2[3], # XOR ECX, <xorvalue 2 for shellcode len> 0x83,0xC7, 29, # ADD EDI,shellcode_begin - here 0x33,0xF6, # XOR ESI, ESI 0xFC, # CLD #loop1: 0x8A,0x07, # MOV AL, BYTE PTR[EDI] 0x3C,missing_byte, # CMP AL, <missingbyte> 0x0F,0x44, 0xC6, # CMOVE EAX, ESI 0xAA, # STOSB 0xE2,0xF6 # LOOP loop1 #shellcode_begin: ]
获取当前eip的地址方式与上面一样,只不过是这次的loader本身也不能包含空字节,指令的解码方式是,E8是call指令,后面的地址要加上当前指令的长度才是真正的偏移,E8FF FF FF FF FF 是跳转到-1+5的位置即最后一个FF处,这样就又组成了一句FFC0汇编指令,巧妙地避开了0x00空字节
然后处理了shellcode长度中可能出现的空字节,与之前的找missingbyte用的同样的方法,然后对shellcode进行异或解密,最后运行shellcode
相关实验
1. shellcode编写:
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014050817262500001(了解shellcode编写规则)
2. shellcode编写练习:http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014071816144300001(掌握shellcode的编写)
最后偷偷给自己博客和和合天实验室打个广告
博客http://sayhi2urmom.top/
合天实验室http://www.hetianlab.com/里面好多优质学习资源的呦
本文为合天原创,未经允许,严禁转载。
相关推荐
- C++零基础入门学习指南(中篇)
-
目标:像拼装乐高一样理解程序模块,掌握内存管理核心技能...
- Linux下跨语言调用C++实践
-
不同的开发语言适合不同的领域,例如Python适合做数据分析,C++适合做系统的底层开发,假如它们需要用到相同功能的基础组件,组件使用多种语言分别开发的话,不仅增加了开发和维护成本,而且不能确保多种语...
- 输入格式控制:C++程序中的数据接收与处理技巧
-
在C++编程中,输入输出是非常基本且重要的操作。尤其是输入部分,程序员通常需要从用户那里获取数据,并根据不同的输入格式进行处理。然而,用户的输入往往是多样化的,如何有效地控制输入格式,确保程序正确接收...
- 常见读写excel文件的库/类
-
在C++语言中读写EXCEL表格,有这几种方法:COM方式、ODBC方式、OLE方式、纯底层格式分析方式。Basicexcel使用方法:https://www.cnblogs.com/paullam/...
- C++文档识别接口如何实现 高效办公
-
数字化信息爆炸时代,办公效率的提升成为企业和个人的迫切需求。人工智能技术的飞速发展,为我们带来了前所未有的便利,翔云文档识别接口便是其中之一。 与传统的人工手动录入相比,文档识别接口优势显著。人...
- 超实用C++学习指南:语法要点、经典书籍、实战案例全汇总!
-
以下是为您整理的C++学习指南,综合了语法要点、资源推荐及实战方向,结合搜索结果和经典知识体系,帮助您系统学习:一、C++基础语法学习指南1.核心概念oC++是静态类型、编译式语言,支持面向对象和...
- 掌握C++文件读写,让代码更灵动!
-
文章改写指令通常涉及对原有文本进行调整、重组或重新表达,以保持或增强信息的准确性和可读性,同时可能改变风格、语气或目标受众。以下是一些具体的文章改写指令示例:·2.简化语言:→指令:将文章中的复杂词汇...
- 闲置宽带能换钱?P2P CDN、无线宝、赚钱宝到底靠不靠谱
-
无线宝类产品其实由来已久,无线宝类产品即与支付宝、余额宝、余利宝等货币基金毫无干系,与区块链“挖币”更存在本质的不同,而是一种利用家庭中的闲置宽带,通过流量来换取佣金的产品。无线宝类产品其实在过去几年...
- 攻略什么?闲置宽带还可以赚钱?
-
现在很多朋友在使用10Mbps、50Mbps甚至100Mbps的高速宽带,不过普通用户并不是长时间都需要这么高速的宽带。比如对于100Mbps的宽带用户,在日常浏览网页时,基本上2Mbps左右的带宽即...
- 明日学业水平考试开始报名 详细步骤都在这里
-
点击上面蓝字关注我们哦~日前,山东省教育考试院发布了《山东省2019年夏季学业水平考试报名考生操作说明》(点击文末阅读原文查看),明天就到了报名的时候了,详细的报名步骤、网上缴费流程、追加报考科目等...
- 瞄准用户上传带宽:HiWiFi 极路由 联合 迅雷 推出 “极赚钱”套餐
-
上次总理谈到宽带降价问题时,很多网友除了吐槽网速慢费用贵,还反映宽带网络的上下行速度不对等。比如说以前ADSL2M的宽带只有512Kbps的上行速度,现在升级到光纤网络之后,按理说技术上实现上下行...
- 揭秘P2P平台刷数据:交易额从100万到1200万
-
(作者:峰岭、刘珺、周娜)从默默无闻到万众瞩目,从“零数据”到“大数据”,从小众投资到大众理财,从个人借贷到企业借款,从个人信用到车、房、资产抵押……近两年来P2P行业以迅雷之速快速爆发,P2P平台也...
- 运营商让我签这个宽带违规使用告知函,我懵逼了
-
特么的是爱奇艺迅雷自己上传的p2p数据,btpt也会上传,直播也会上传,监控也会,传文件也会,到底他么的运营商你要干个啥啊,我不仅没捞着一分好处,夹在中间两头受气!真特么晦气这特么是谁弄的函?完全没搞...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)