__declspec两种属性类型探讨

admin 2023年12月19日12:28:25评论33 views字数 4428阅读14分45秒阅读模式

前言

__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)

__declspec两种属性类型探讨


有没有办法呢?


这里CALLBACK一下之前文章中提到的 RWX注入方法[1],我们可以通过给链接器传递指定参数指定jsec节的属性为RWX

cl /MD main.cpp -o main.exe /link /SECTION:jsec,RWE

__declspec两种属性类型探讨


除了自定义的节以外,我们可能也会看到这样的写法:

__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;
}


__declspec两种属性类型探讨


综上所述,我们可以通过无论是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在文章中有提到)

__declspec两种属性类型探讨

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;
}

下面是对应的汇编代码部分:

__declspec两种属性类型探讨

调用TestFunc的汇编形式如下:

CALL XXXXXXXX

而调用MessageBoxA则是这种形式:

CALL [XXXXXXXX]

我们都知道MessageBoxA的地址位于主模块的导入表中(IAT)


不同的调用形式是因为编译器识别出函数类型的不同,即区分普通函数(TestFunc)和导入函数(MessageBoxA),这个是如何做到的?


实际上通过 __declspec(dllimport) 函数修饰符告诉编译器该函数驻留在另一个 DLL 中,并且编译器应当生成 Call [xxxxxxxx] 指令,此外,编译器还生成信息,告诉链接器将指令的函数指针部分解析为一个名为 __imp_functionname 的符号,这一点我们可以在中间文件看到

__declspec两种属性类型探讨


这个 __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

__declspec两种属性类型探讨


那0x7FF6E13B1052位置处的代码是什么?


其实是一个 JMP的调用存根Stub,所以通过声明导入函数避免了多余的五个字节指令,提高代码的运行效率,达到编译器优化的目的。

__declspec两种属性类型探讨


引用

[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两种属性类型探讨

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月19日12:28:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   __declspec两种属性类型探讨http://cn-sec.com/archives/2316007.html

发表评论

匿名网友 填写信息