0day安全阅读笔记[1]
程序员文章站
2024-03-11 22:16:01
...
第一个问题:
首先来看一下那个HASH算法,这个HASH算法有一点懵住我了,一开始看那一段代码始终没看出个所以然:
digest = ((digest << 25) | (digest >> 7));
一直在想的是|或位运算符是从左往右的顺序,那digest << 25生成的结果,后面的digest >> 7会使用吗? 还是全部用原始的? 结果证明是不会影响的:
转成反汇编后就看到了, 把digest的值。一次放eax, 一次放ecx, 分开来进行左右移的运算,最终或起来后加到一起。
第二个问题:
这段shellcode的详细注释已经写上,在看不明白那基本上很难明白了
__asm {
cld // 把DF清零
push 0x1e380a6a // MessageBoxA的HASH值
push 0x4fd18963 // ExitProcess的HASH值
push 0x0c917432 // LoadLibraryA的HASH值
mov esi, esp // 保存LoadLibraryA的HASH首地址到esi中
lea edi, [esi - 0xC] // 留出可容纳3个API的地址的空间
xor ebx, ebx
mov bh, 0x04
sub esp, ebx // 抬高栈顶0x400的大小, 防止破坏shellcode
mov ebx, 0x3233
push ebx
push 0x72657375 // 把user32字符串压入堆栈
push esp // 把"user32"字符串首地址压入堆栈
xor edx, edx
mov ebx, fs:[edx + 0x30] // 拿到PEB指针
mov ecx, [ebx + 0x0c] // 拿到PEB_LDR_DATA指针
mov ecx, [ecx + 0x1c] // 拿到指向模块初始化链表的头指针, 其指向了NTDLL.dll
mov ecx, [ecx] // 拿到kernel32.dll
mov ebp, [ecx + 0x08] // kernel32.dll的BaseImage
find_lib_functions:
lodsd // 从ds:[esi]中拿到了LoadLibraryA的HASH,esi增加4
cmp eax, 0x1e380a6a // 这个是MessageBoxA的HASH值,第一次进入肯定不可能,因为在栈顶的是LoadLibrary,所以必然跳转, 第三次才会不跳
jne find_functions // 如果第一次跳到这,直接看find_functions
xchg eax, ebp // 因为eax在下面的LoadLibraryA的调用中会被覆盖,所以保存一下eax即MessageBox的Hash值
call [edi - 0x8] // 这里调用的是LoadLibraryA
xchg eax, ebp // 这里成功加载了user32.dll并获取了其模块句柄。这时ebp装的是MessageBoxA的HASH而eax装的是user32.dll的句柄
find_functions:
pushad // 以EAX,ECX,EDX,EBX,ESP,EBP,ESI, EDI顺序依次压栈, 注意,这里最后一个是EDI即栈顶
mov eax, [ebp + 0x3C] // Kernel32.dll的ImageBase即PE结构的DOS头开始+3C就是PE头的开始, 实际上就是e_lfanew的值
mov ecx, [ebp + eax + 0x79] // 这里是PE头在加上0x79是IMAGE_DATA_DIRECTORY结构中导出表的VirtualAddress
add ecx, ebp // VirtualAddress是RVA, 加上ImageBase后就变成VA了
mov ebx, [ecx + 0x20] // AddressOfNames的RVA
add ebx, ebp // AddressOfNames的VA
xor edi, edi
next_function_loop:
inc edi
mov esi, [ebx + edi * 4] // 这个是在名称地址表中遍历各个API名称, 因为名称地址表中存储的都是4字节为单位的函数名称的RVA地址
add esi, ebp // 同样求出每个API名称的VA
cdq // 这个指令把eax扩展成edx:eax, 并且edx内每一位的值都是eax的符号位, 实际上就是都是0了, 因为
hash_loop: // 这段代码实际上就是GetHash函数的汇编形式
movsx eax, byte ptr [esi] // 实际上这里玩了个技巧,可能一些人一开始会看懵(比如我)
cmp al, ah // 他这里想做的事是把每个API名称字符与0比较,看字符串比较结束了没,结束了就代表HASH算完了,movsx的出现是为了让后面全部为0,方便用ah(0)与al字符比较
jz compare_hash // 如果相等代表字符串结束了, 不然就是按照算法按计算HASH
ror edx, 7 // 循环右移, 多出来的位补最左边
add edx, eax // edx 相当于之前的digest变量
inc esi
jmp hash_loop
compare_hash:
cmp edx, [esp + 0x1C] // esp就是LoadLibraryA字符串的首地址
jnz next_function_loop // 不相等则跳回去继续把LoadLibraryA和下一个位于地址名称表中的API名字对比
mov ebx, [ecx + 0x24] // 相等拿到AddressOfNameOrdinals的RVA
add ebx, ebp // 拿到AddressOfNameOrdinals的VA
mov di, [ebx + 2 * edi] // 因为ordinal是word为单位所以是乘2
mov ebx, [ecx + 0x1C] // AddressOfFunctions的RVA
add ebx, ebp // AddressOfFunctions的VA
add ebp, [ebx + 4 * edi] // 对应名称的函数VA地址
xchg eax, ebp // 把函数VA地址放入eax中
pop edi // edi是pushad最后一个压入栈的所以位于栈顶,pop出来即可
stosd // 将函数地址其保存到ds:[edi]中, 往上回看一下会发现,lea edi, [esi - 0xC]即是这个
push edi // push回去,不然popad会出问题
popad
cmp eax, 0x1e380a6a // 与最后一个MessageBoxA对比,如果是MessageBoxA代表已经到了尽头。
jne find_lib_functions // 不等则继续找
function_call:
xor ebx, ebx
push ebx
push 0x74736577
push 0x6C696166
mov eax, esp
push ebx
push eax
push eax
push ebx
call[edi - 0x04] // MessageBox
push ebx
call [edi - 0x08] // ExitProcess
nop
nop
nop
nop
}
这段shellcode写的真的很好。一切都算的刚刚好
(完)
上一篇: 浅谈Java中强制类型转换的问题