学习来源:0day安全一书
shellcode
即一段植入进程的代码。
在实际漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧很有可能会产生”位移“,即shellcode在内存中的地址是动态变化的。因此我们需要找到一个跳板,使得程序的执行流程总是能找到我们的shellcode。
在上一节中verify_password函数返回后栈中的情况如下所示:
0x0012FAF0
,函数返回时,这个地址被弹入EIP
寄存器中,处理器按照EIP
寄存器中的地址取指令,最后栈中的数据被处理器当成指令执行。图一 栈帧位移示意图
图二 溢出发生时栈、寄存器与代码之间的关系
一般情况下,ESP
寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP
所指的位置恰好是我们所淹没的返回地址的下一个位置,如图三。
图三 使用"跳板"的溢出利用流程
由于ESP
寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们就可以使用图三所示的这种定位shellcode的方法来进行动态定位。
jmp esp
指令的地址覆盖函数的返回地址,而不是用原来的手工查询出的shellcode起始地址直接覆盖。jmp esp
指令,而不是直接开始执行shellcode。ESP
在函数返回时仍指向栈区(函数返回地址之后),jmp esp
指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行。jmp esp
指令执行过后会恰好跳进shellcode。这种定位shellcode的方法使用进程空间里的一条 jmp esp
指令作为"跳板",无论栈帧怎么"位移",都能精确地跳回栈区,从而适应程序运行中shellcode内存地址的动态变化。
当然这只是一种定位shellcode的方式,还有其他许多种定位shellcode的方式
1 2 |
|
通过手工查出来的API地址会在其他计算机上失效,在shellcode中使用静态函数地址来调用API会使exploit的额通用性收到很大的限制,所以,实际中使用的shellcode必须还要能动态地获得自身所需的API的函数地址。
Windows的API是通过动态链接库中的导出函数来实现的,例如,内存操作等函数在kernel32.dll
中实现;大量的图形界面相关的API则在user32.dll
中实现。Win32平台下的shellcode使用最广泛的方法,就是通过从进程环境块中找到动态链接库的导出表,并搜索出所需的API地址,然后逐一调用。
几乎所有Win32程序都会加载ntdll.dll
和kernel32.dll
这两个基础的动态链接库。如果想要在win_32平台下定位kernel32.dll
中的API地址,可以采取如下办法。
64位系统
GS
在内存中找到当前存放着指向当前线程环境块TEB
。在GS
中存储的是TEB
在GDT(Global Descriptor Table)
中的序号,通过GDT
获取TEB
的基址。0x60
的地方存放着指向进程环境块PEB
的指针(即GS[0x30]
)。0x18
的地方存放着指向PEB_LDR_DATA
结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。PEB_LDR_DATA
结构体偏移位置为0x20
的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList
。InInitializationOrderModuleList
中按顺序存放着 PE
装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll
,第二个链表结点就是 kernelbase.dll
,第三个节点才是kernel32.dll
。kernel32.dll
的结点后,在其基础上再偏移 0x20
就是 kernel32.dll
在内存中的加载基地址。kernel32.dll
的加载基址算起,偏移0x3C
的地方就是其PE
头。PE
头偏移 0x88
的地方存放着指向函数导出表的指针。32位系统
FS
在内存中找到当前存放着指向当前线程环境块TEB
。在FS
中存储的是TEB
在GDT(Global Descriptor Table)
中的序号,通过GDT
获取TEB
的基址。0x30
的地方存放着指向进程环境块PEB
的指针(即FS[0x30]
)。0x0C
的地方存放着指向PEB_LDR_DATA
结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。PEB_LDR_DATA
结构体偏移位置为0x1C
的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList
。InInitializationOrderModuleList
中按顺序存放着 PE
装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll
,第二个链表结点就是 kernel32.dll
。kernel32.dll
的结点后,在其基础上再偏移 0x08
就是 kernel32.dll
在内存中的加载基地址。kernel32.dll
的加载基址算起,偏移0x3C
的地方就是其PE
头。PE
头偏移 0x78
的地方存放着指向函数导出表的指针。图四 在shellcode中动态定位API的原理
x64 弹出计算器shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
|
x86 弹出计算器shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
|
release
版本中的一些信息。如下属性
-> C/C++
-> 代码生成
-> 安全检查
->禁用安全检查 (/GS-)
属性
-> 链接器
-> 清单文件
-> 生成清单
-> 否 (/MANIFEST:NO)
属性
-> 链接器
-> 调试
-> 生成调试信息
-> 否
如下所示:
通常情况下,我们会对所需的API
函数名进行hash
运算,在搜索导出表时对当前遇到的函数名也进行同样的hash
,这样只要比较hash
所得的摘要(digest
)就能判定是不是我们所需的API
了。虽然这种搜索方法需要引入额外的hash
算法,但是可以节省出存储函数名字符串的代码。
注意:
1 |
|
这里提供一段简单的 "hash" 算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
|
编译成功之后,程序正常运行
将程序拖入OD
,按F9
进入程序模块,如图所示:
然后从第一行开始,下拉到空白区选中 右键 复制 二进制复制。然后再在010editor中粘贴自 从十六进制文本粘贴。如下图:
选中然后复制为c代码
这样就得到了我们的shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
在程序中运行我们的shellcode。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
成功运行,如下所示:
漏洞研究小白,目前正在跟着书学习。希望自己能持之以恒。大家一起共勉