前言
__declspec 属性扩展关键字,微软特供
语法:
__declspec ( extended-decl-modifier-seq )
属性类型(extended-decl-modifier-seq)有很多,常见的可能有dllimport(导入声明),dllexport(导出声明),naked(裸函数)等
今天讨论msvc的__declspec(code_seg(...)) 属性及对应的 gcc 的 __attribute__ ((section (...)))
还有 __declspec(dllimport) 对于编译器的优化在汇编代码中的具体体现
正文
.text$
MSVC的 __declspec(code_seg(...) 对标 GCC的__attribute__ ((section (...))) 在现在很多知名项目中都有使用,比如开源C2框架Havoc,CobaltStrike UDRL(User Defined Reflective Loader ) AceLdr...
__declspec(code_seg(...) 可以将代码块放到自定义节中,同时无需提前使用pragma section和 __declspec(allocate) 来定义节和分配节
例如下面代码将sub函数放到名为 jsec 的节中:
cl /MD main.cpp -o main.exe
__declspec(code_seg("jsec"))
int sub(int a, int b)
{
return a - b;
}
int add(int a, int b)
{
return a + b;
}
int main()
{
add(1, sub(3, 2));
return 0;
}
当我尝试将jsec节设置为可读可写可执行时发现并不可行
#pragma section("jsec",read,write,execute)
有没有办法呢?
这里CALLBACK一下之前文章中提到的 RWX注入方法[1],我们可以通过给链接器传递指定参数指定jsec节的属性为RWX
cl /MD main.cpp -o main.exe /link /SECTION:jsec,RWE
除了自定义的节以外,我们可能也会看到这样的写法:
__declspec(code_sec(.text$A))
__declspec(code_sec(.text$B))
...
在微软关于PE和COFF文件的描述中提到:关于$符号有特殊的解释,如果链接过程中能过确定对象(obj)的最终归属,则链接器忽略紧随$后面的字符串内容,即某个对象文件包含.text$A节,则此节最终会被放到PE文件的.text节中
但是后面的字符会影响节中内容的排序,所有具有相同节名的内容在PE文件中被连续分配,同时具有.text$X声明的内容会被放到.text$W之后,.text$Y之前。
The “$” character (dollar sign) has a special interpretation in section names in object files. When determining the image section that will contain the contents of an object section, the linker discards the “$” and all characters following it. Thus, an object section named .text$X will actually contribute to the .text section in the image. However, the characters following the “$” determine the ordering of the contributions to the image section. All contributions with the same object-section name will be allocated contiguously in the image, and the blocks of contributions will be sorted in lexical order by object-section name. Therefore, everything in object files with section name .text$X will end up together, after the .text$W contributions and before the .text$Y contributions. The section name in an image file will never contain a “$” character.
示例如下:
__declspec(code_seg(".text$B"))
int sub(int a, int b)
{
return a - b;
}
__declspec(code_seg(".text$A"))
int add(int a, int b)
{
return a + b;
}
综上所述,我们可以通过无论是msvc的__declspec(code_seg(...))还是gcc的__attribute__( ( section(...) ) )将代码放入指定节中,甚至可以更改自定义节的属性。
就现在安全领域工具开发中,通常这种声明多用于使用GCC编译的x64ShellCode项目中,通过如下一段汇编代码将堆栈先行对齐之后,再进行ShellCode的调用。
[SECTION .text$A]
Start:
push rsi
mov rsi, rsp
and rsp, 0FFFFFFFFFFFFFFF0h
sub rsp, 020h
call Ace
mov rsp, rsi
pop rsi
ret
通常为了使Start函数在ShellCode之前执行,还会使用一个链接器脚本来控制生成顺序。如下:
SECTIONS
{
.text :
{
*( .text$A )
*( .text$B )
*( .text$C )
*( .text$D )
*( .text$E )
*( .rdata* )
*( .text$F )
}
}
关于堆栈对齐这最初可能是由Matt Graeber在 Writing Optimized Windows Shellcode in C[2] 中指出,这是由于使用128位XMM寄存器需要堆栈按照16字节对齐的原因(也许后面文章会针对这点进行探讨,如果我足够勤快的话:P)
对于使用MSVC编写x64ShellCode而言,确实很少看到使用这样的声明,一般都是通过指定入口点函数,比如在入口函数中编写堆栈对齐的代码,为了使入口函数在ShellCode中最先执行,需要使用 /ORDER 的编译选项来控制生成顺序。(我在使用一些ShellCode VS编译模板时没有碰到过入口函数生成在中间位置,但是Matt Graeber在文章中有提到)
function_link_order64.txt
Begin
GetProcAddressWithHash
ExecutePayload
上述截图和function_link_order64.txt来自项目PIC_Bindshell[3]
dllimport
下面代码调用了两个函数,一个是位于主模块内部的TestFunc,一个是从User32中导入的MessageBoxA(隐式链接)
#include <Windows.h>
int TestFunc(int a,int b)
{
return a+b;
}
int main() {
TestFunc(1,2);
MessageBoxA(0, "hello", "world", 0);
return 0;
}
下面是对应的汇编代码部分:
调用TestFunc的汇编形式如下:
CALL XXXXXXXX
而调用MessageBoxA则是这种形式:
CALL [XXXXXXXX]
我们都知道MessageBoxA的地址位于主模块的导入表中(IAT)
不同的调用形式是因为编译器识别出函数类型的不同,即区分普通函数(TestFunc)和导入函数(MessageBoxA),这个是如何做到的?
实际上通过 __declspec(dllimport) 函数修饰符告诉编译器该函数驻留在另一个 DLL 中,并且编译器应当生成 Call [xxxxxxxx] 指令,此外,编译器还生成信息,告诉链接器将指令的函数指针部分解析为一个名为 __imp_functionname 的符号,这一点我们可以在中间文件看到
这个 __imp_functionname最终被解析为导入表中的某个条目地址。
通过查看MessageBoxA声明,我们可以看到WINUSERAPI的前置声明符
WINUSERAPI
int
WINAPI
MessageBoxA(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
WINUSERAPI实际上就是 __declspec(dllimport)
如果我把WINUSERAPI给注释掉了,阁下该如何应对呢?
还是能够编译成功的,只不过对于MessageBoxA的调用变成了普通函数的调用形式,也就是CALL XXXXXXXX
那0x7FF6E13B1052位置处的代码是什么?
其实是一个 JMP的调用存根Stub,所以通过声明导入函数避免了多余的五个字节指令,提高代码的运行效率,达到编译器优化的目的。
引用
[1]:https://mp.weixin.qq.com/s/aIrBVUF-WRpkbIqc5HaUEg
[2]:Writing Optimized Windows Shellcode in C:https://web.archive.org/web/20210305190309/http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html
[3]:https://github.com/mattifestation/PIC_Bindshell
https://learn.microsoft.com/en-us/cpp/cpp/code-seg-declspec?view=msvc-170
原文始发于微信公众号(无名之):__declspec两种属性类型探讨
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论