首页
论坛
课程
招聘
[原创] 精确逆向windows未公开函数 RtlpGetAssemblyStorageMapRootLocation
2022-6-7 15:34 9294

[原创] 精确逆向windows未公开函数 RtlpGetAssemblyStorageMapRootLocation

2022-6-7 15:34
9294

介绍

精确逆向未公开的windows内部函数RtlpGetAssemblyStorageMapRootLocation的源代码。截止2022年5月30日,该函数没有在网络上找到任何有效的公开信息。

使用工具

  • IDA Pro
  • OllyDbg
  • dumpbin
  • certutil

文件信息

  • 文件名: ntdll.dll
  • 机器平台: x86 32bit
  • 编译日期: Sat Nov 20 20:08:56 2010
  • 操作系统版本: 6.01
  • 链接器版本: 9.00
  • SHA256: ea 1e 16 24 7c 84 8c 8c 17 1c 4c d1 fa 17 bc 5a 01 8a 1f cb 0c 0d ac 25 00 90 66 b6 66 7b 8e ef
  • 文件下载:https://gitee.com/asminlife/rtlp-get-assembly-storage-map-root-location-reversing/blob/master/ntdll.dll

与目标相关的函数

  • RtlpGetAssemblyStorageMapRootLocation
  • RtlGetAssemblyStorageRoot
  • RtlpResolveAssemblyStorageMapEntry
  • RtlpInitializeAssemblyStorageMap
  • RtlpInsertAssemblyStorageMapEntry
  • RtlpAssemblyStorageMapResolutionDefaultCallback
  • RtlpProbeAssemblyStorageRootForAssembly
  • RtlpUninitializeAssemblyStorageMap
  • RtlpQueryAssemblyInformationActivationContextDetailedInformation
  • RtlpQueryFilesInAssemblyInformationActivationContextDetailedInformation

逆向目标函数基本信息

  • 名称:RtlpGetAssemblyStorageMapRootLocation
  • 堆栈格式:标准堆栈帧
  • 调用规范:__stdcall
  • 参数个数:3
  • 返回值:成功/错误代码

初步分析

从函数名称推测,函数的功能是获取程序集存储映射的根位置。 借助IDA Pro,分析该函数的参数、局部变量和相关调用。详细IDA Pro反汇编代码请访问 https://gitee.com/asminlife/rtlp-get-assembly-storage-map-root-location-reversing/blob/master/%E5%8F%8D%E6%B1%87%E7%BC%96%E4%BB%A3%E7%A0%81.asm

1
2
3
4
5
6
7
8
9
10
11
12
ObjectAttributes= _OBJECT_ATTRIBUTES ptr -240h
ResultLength= dword ptr -228h
var_224= dword ptr -224h
KeyHandle= dword ptr -220h
KeyValueInformation= byte ptr -21Ch
var_218= dword ptr -218h
Size= dword ptr -214h
Src= byte ptr -210h
var_4= dword ptr -4
arg_0= dword ptr  8
arg_4= dword ptr  0Ch
arg_8= dword ptr  10h

函数的相关调用

1
2
3
4
5
6
7
8
9
.text:7DF1C529    call    _ZwOpenKey@12; ZwOpenKey(x,x,x)
.text:7DF1C563    call    _ZwQueryValueKey@24; ZwQueryValueKey(x,x,x,x,x,x)
.text:7DF1C57E    call    _DbgPrintEx
.text:7DF1C5AD    call    _DbgPrintEx
.text:7DF1C5DA    call    _DbgPrintEx
.text:7DF1C5F1    call    ds:_RtlAllocateStringRoutine; RtlpAllocateEnvBlock(x)
.text:7DF1C616    call    _memcpy
.text:7DF1C63F    call    _NtClose@4; NtClose(x)
.text:7DF1C64E    call    @__security_check_cookie@4; __security_check_cookie(x)

调用函数分析

  • ZwOpenKey 打开一个现有的注册表项,推测可能使用局部变量KeyHandle接收注册表键句柄。
  • ZwQueryValueKey 函数返回注册表项的键值,推测可能使用局部变量Src和- - ResultLength接收键值信息和结果长度。
  • DbgPrintEx 函数将向内核调试器发送一个字符串,表明这个函数为内核调试器提供服务,字符串信息可以为我们提供重要参考。
  • RtlpAllocateEnvBlock 函数没有公开的信息,这里通过名称和相关资料提示,它可能是分配一个调试用的线程环境块(typedef struct _TEB)。
  • NtClose 函数关闭指定的句柄。
  • memcpy 拷贝字节数据。

字符串分析

1
2
3
4
5
'SXS: Unable to open storage root subkey %wZ; Status = 0x%08lx',0Ah
'SXS: Unabel to query location from storage root subkey %wZ; Statu'    //微软工程师把Unable写成了Unabel
'SXS: Assembly storage root location value type is not REG_SZ',0Ah
'SXS: Assembly storage root location for %wZ does not fit in a UNICODE STRING'
'SXS: Assembly storage root location value has non-even size',0Ah,0

这些错误信息表明,函数可能通过查询注册表键值来获得程序集的位置信息。

参数分析

1
2
.text:7DF1C4AF                 mov     eax, [ebp+arg_0]       
.text:7DF1C4E7                 mov     [ebp+ObjectAttributes.RootDirectory], eax

第一个参数先被放在eax中,随后 arg_0 的值给了RootDirectory ,根据_OBJECT_ATTRIBUTES结构的定义,可以确定arg_0是HANDLE类型,它是一个根对象目录句柄。

1
2
3
4
.text:7DF1C4B2                 mov     ecx, [ebp+arg_4]
.text:7DF1C4BD                 mov     [ebp+var_224], ecx
.text:7DF1C4ED                 lea     eax, [ebp+var_224]
.text:7DF1C4F3                 mov     [ebp+ObjectAttributes.ObjectName], eax

第二个参数移动到ecx,随后它被保存在局部变量var_224,最后var_224的地址传递给了ObjectAttributes.ObjectName所以第二个参数是对象名称,根据 _OBJECT_ATTRIBUTES结构的定义,它是PUNICODE_STRING类型

1
2
3
4
.text:7DF1C4B9                 mov     esi, [ebp+arg_8]
.text:7DF1C5BC                 movzx   ecx, word ptr [esi+2]
.text:7DF1C5E9                 mov     [esi+2], ax
.text:7DF1C5F7                 mov     [esi+4], eax

观察这些指令对第三个参数arg_8的操作,表明它是一个32位指针,暂时不明确类型。

参数信息汇总

1
2
3
HANDLE    arg_0,           //根对象目录句柄
PUNICODE_STRING    arg_4,  //对象名称
ptr_32    arg_8;          //未知指针

初步分析总结

初步判断,函数应该是通过传递根对象目录句柄、对象名称和一个未知指针参数来获取程序集的位置。

详细的逆向过程

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
.text:7DF1C49A ; __stdcall RtlpGetAssemblyStorageMapRootLocation(x, x, x)
.text:7DF1C49A _RtlpGetAssemblyStorageMapRootLocation@12 proc near
.text:7DF1C49A                                         ; CODE XREF: RtlpAssemblyStorageMapResolutionDefaultCallback(x,x,x)+2D255↑p
.text:7DF1C49A
.text:7DF1C49A ObjectAttributes= _OBJECT_ATTRIBUTES ptr -240h
.text:7DF1C49A ResultLength    = dword ptr -228h
.text:7DF1C49A var_224           = dword ptr -224h
.text:7DF1C49A KeyHandle       = dword ptr -220h
.text:7DF1C49A KeyValueInformation= byte ptr -21Ch
.text:7DF1C49A var_218         = dword ptr -218h
.text:7DF1C49A Size            = dword ptr -214h
.text:7DF1C49A Src             = byte ptr -210h
.text:7DF1C49A var_4           = dword ptr -4
.text:7DF1C49A arg_0           = dword ptr  8
.text:7DF1C49A arg_4           = dword ptr  0Ch
.text:7DF1C49A arg_8           = dword ptr  10h
.text:7DF1C49A
.text:7DF1C49A                 mov     edi, edi
.text:7DF1C49C                 push    ebp
.text:7DF1C49D                 mov     ebp, esp
.text:7DF1C49F                 sub     esp, 240h
.text:7DF1C4A5                 mov     eax, ___security_cookie
.text:7DF1C4AA                 xor     eax, ebp
.text:7DF1C4AC                 mov     [ebp+security_cookie], eax    ;var_4
.text:7DF1C4AF                 mov     eax, [ebp+arg_0] ; eax = arg_0 , HANDLE
.text:7DF1C4B2                 mov     ecx, [ebp+arg_4] ; ecx = arg_4 ,对象名称
.text:7DF1C4B5                 push    ebx
.text:7DF1C4B6                 xor     ebx, ebx        ; ebx = 0
.text:7DF1C4B8                 push    esi
.text:7DF1C4B9                 mov     esi, [ebp+arg_8] ; esi = arg_8 ,指针
.text:7DF1C4BC                 push    edi
.text:7DF1C4BD                 mov     [ebp+var_224], ecx
.text:7DF1C4C3                 mov     [ebp+KeyHandle], ebx
.text:7DF1C4C9                 mov     [ebp+ResultLength], ebx
.text:7DF1C4CF                 cmp     eax, ebx
.text:7DF1C4D1                 jz      loc_7DF1C62C
.text:7DF1C4D7                 cmp     ecx, ebx
.text:7DF1C4D9                 jz      loc_7DF1C62C
.text:7DF1C4DF                 cmp     esi, ebx
.text:7DF1C4E1                 jz      loc_7DF1C62C

这段代码主要是初始化一些局部变量,把对象名称存储到var_224,然后开始检查参数。 任意一个参数为0,则函数退出,并返回一个错误代码。以下是代码还原:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern dword security_cookie = 0BB40E64Eh;                        //var_4,在外部定义的未知类型变量,大小为dword
 
// 返回值是某种错误代码类型,大小为dword
dword RtlpGetAssemblyStorageMapRootLocation(HANDLE arg_0, PUNICODE_STRING arg_4, ptr_32 arg_8)
{
    PUNICODE_STRING    temp_arg_4    = arg_4;       
    HANDLE            KeyHandle    = 0;
    PULONG            ResultLength    = 0;
 
    if (arg_0 == 0 || arg_4 == 0 || arg_8 == 0) {                // 任意参数为空则函数退出,并跳转到loc_7DF1C62C
        if(KeyHandle != 0)                       
            NtClose(KeyHandle);
        __security_check_cookie(security_cookie);           
        return 0C000000Dh;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:7DF1C4E7                 mov     [ebp+ObjectAttributes.RootDirectory], eax
.text:7DF1C4ED                 lea     eax, [ebp+var_224]
.text:7DF1C4F3                 mov     [ebp+ObjectAttributes.ObjectName], eax
.text:7DF1C4F9                 lea     eax, [ebp+ObjectAttributes]
.text:7DF1C4FF                 push    eax             ; ObjectAttributes
.text:7DF1C500                 push    1               ; DesiredAccess
.text:7DF1C502                 lea     eax, [ebp+KeyHandle]
.text:7DF1C508                 push    eax             ; KeyHandle
.text:7DF1C509                 mov     [ebp+ObjectAttributes.Length], 18h
.text:7DF1C513                 mov     [ebp+ObjectAttributes.Attributes], 40h ; '@'
.text:7DF1C51D                 mov     [ebp+ObjectAttributes.SecurityDescriptor], ebx
.text:7DF1C523                 mov     [ebp+ObjectAttributes.SecurityQualityOfService], ebx
.text:7DF1C529                 call    _ZwOpenKey@12   ; ZwOpenKey(x,x,x)
.text:7DF1C52E                 mov     edi, eax
.text:7DF1C530                 cmp     edi, ebx
.text:7DF1C532                 jge     short loc_7DF1C543

这段代码主要是对ObjectAttributes结构的成员进行初始化,用第一个参数作为对象目录句柄,第二个参数作为对象名称。随后调用ZwOpenKey函数打开对象arg_4的注册表项,以下是代码还原:

1
2
3
4
5
6
7
8
9
10
11
OBJECT_ATTRIBUTES ObjectAttributes;
ObjectAttributes.RootDirectory                = arg_0;            //根对象目录句柄
ObjectAttributes.ObjectName                    = &temp_arg_4;        //对象名称,arg_4
ObjectAttributes.Length                        = 0x18;
ObjectAttributes.Attributes                    = 0x40;                //使用不区分大小写的方式比较对象名称
ObjectAttributes.SecurityDescriptor            = 0;
ObjectAttributes.SecurityQualityOfService    = 0;
 
if (ZwOpenKey(&KeyHandle, 1, ObjectAttributes) >= 0) {            //打开注册表项
    //跳转到loc_7DF1C543
}

loc_7DF1C543

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:7DF1C543 loc_7DF1C543: 
.text:7DF1C543                 lea     eax, [ebp+ResultLength]
.text:7DF1C549                 push    eax             ; ResultLength
.text:7DF1C54A                 push    218h            ; Length
.text:7DF1C54F                 lea     eax, [ebp+KeyValueInformation]
.text:7DF1C555                 push    eax             ; KeyValueInformation
.text:7DF1C556                 push    2               ; KeyValueInformationClass
.text:7DF1C558                 push    offset stru_7DF1C658 ; ValueName
.text:7DF1C55D                 push    [ebp+KeyHandle] ; KeyHandle
.text:7DF1C563                 call    _ZwQueryValueKey@24 ; ZwQueryValueKey(x,x,x,x,x,x)
.text:7DF1C568                 mov     edi, eax
.text:7DF1C56A                 cmp     edi, ebx
.text:7DF1C56C                 jge     short loc_7DF1C58B

这段代码调用ZwQueryValueKey函数,以下是MSDN上的函数说明

1
2
3
4
5
6
7
8
NTSYSAPI NTSTATUS ZwQueryValueKey(
  [in]            HANDLE                      KeyHandle,                //键句柄
  [in]            PUNICODE_STRING             ValueName,                //键值名称
  [in]            KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,    //确定 KeyValueInformation 缓冲区中返回的信息的类型
  [out, optional] PVOID                       KeyValueInformation,        //接收请求信息的指针
  [in]            ULONG                       Length,                    //指定 KeyValueInformation 缓冲区的大小(以字节为单位)
  [out]           PULONG                      ResultLength
);

调用ZwQueryValueKey函数时,第三个参数是一个枚举变量,它在KEY_VALUE_INFORMATION_CLASS中定义。它确定了第四个参数 KeyValueInformation 缓冲区中返回的信息的类型。 调用时向KeyValueInformationClass传递的实际参数为数值2,所以我们可以确定第四个参数KeyValueInformation指向一个KEY_VALUE_PARTIAL_INFORMATION结构体变量。 此外,stru_7DF1C658被发现是一个在函数外部定义的LSA_UNICODE_STRING结构体变量,里面保存的是注册表的键"Location", 现在我们可以确认之前的猜测,函数确实是通过查询注册表键值来获得程序集的位置信息。 根据ZwQueryValueKey的最后一个参数信息,修改局部变量ResultLength的类型为ULONG。 以下是代码还原:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <lsalookup.h>                            //LSA_UNICODE_STRING结构体头文件
 
extern LSA_UNICODE_STRING stru_7DF1C658 = { 0x10, 0x12, L"Location" };
 
 
    PUNICODE_STRING        temp_arg_4 = arg_4;            //保存对象名称
    HANDLE                KeyHandle = 0;
    ULONG                ResultLength = 0;
    PVOID                KeyValueInformation;       
 
        //返回注册表键Location的值
        //KeyValueInformation返回一个KEY_VALUE_PARTIAL_INFORMATION结构的指针,就是location信息
 
        if (ZwQueryValueKey(KeyHandle, stru_7DF1C658, 2, &KeyValueInformation, 0x218, &ResultLength) >= 0) {
            //跳转到loc_7DF1C58B
        }

错误处理

1
2
3
4
5
.text:7DF1C534                 push    edi
.text:7DF1C535                 lea     eax, [ebp+var_224]
.text:7DF1C53B                 push    eax
.text:7DF1C53C                 push    offset aSxsUnableToOpe ; "SXS: Unable to open storage root subkey"...
.text:7DF1C541                 jmp     short loc_7DF1C57B
1
2
3
4
.text:7DF1C56E                 push    edi
.text:7DF1C56F                 lea     eax, [ebp+var_224]
.text:7DF1C575                 push    eax
.text:7DF1C576                 push    offset aSxsUnabelToQue ; "SXS: Unabel to query location from stor"...
1
2
3
4
5
6
.text:7DF1C57B loc_7DF1C57B:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+A7↑j
.text:7DF1C57B                 push    ebx             ; Level
.text:7DF1C57C                 push    33h ; '3'       ; ComponentId
.text:7DF1C57E                 call    _DbgPrintEx
.text:7DF1C583                 add     esp, 14h
.text:7DF1C586                 jmp     loc_7DF1C631

如果调用ZwOpenKey失败,则跳转到.text:7DF1C534 ,如果调用ZwQueryValueKey失败则跳转到.text:7DF1C56E,两个代码段处理的目的都是为了打印错误消息,并跳转到程序结尾loc_7DF1C631。这里列出代码详细逻辑:

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
#include <ntdef.h>                                            //OBJECT_ATTRIBUTES结构体头文件
#include <lsalookup.h>                                        //LSA_UNICODE_STRING结构体头文件
#include <Wdm.h>                                            //DbgPrintEx函数头文件
 
extern dword security_cookie            = 0xBB40E64E;        // var_4,值来自在外部定义的未知类型变量___security_cookie
extern LSA_UNICODE_STRING stru_7DF1C658 = { 0x10, 0x12, L"Location" };
extern const char aSxsUnableToOpe[]        = "SXS: Unable to open storage root subkey %wZ; Status = 0x%08lx\n";
extern const char aSxsUnabelToQue[]        = "SXS: Unabel to query location from storage root subkey %wZ; Statu";
 
// 返回值是某种错误代码类型,大小为dword
dword RtlpGetAssemblyStorageMapRootLocation(HANDLE arg_0,            //根对象目录句柄
                                            PUNICODE_STRING arg_4,    //对象名称
                                            ptr_32 arg_8)            //未知指针
{
    dword                return_code                = 0;                // 函数返回代码
    PUNICODE_STRING        temp_arg_4                = arg_4;            // 对象名称
    HANDLE                KeyHandle                = 0;
    ULONG                ResultLength            = 0;
    PVOID                KeyValueInformation        = NULL;                // 指向注册表键值信息
    OBJECT_ATTRIBUTES    ObjectAttributes;
    ObjectAttributes.RootDirectory                = arg_0;            // 根对象目录句柄
    ObjectAttributes.ObjectName                    = &temp_arg_4;        // 对象名称,arg_4
    ObjectAttributes.Length                        = 0x18;
    ObjectAttributes.Attributes                    = 0x40;                // 使用不区分大小写的方式比较对象名称
    ObjectAttributes.SecurityDescriptor            = 0;
    ObjectAttributes.SecurityQualityOfService    = 0;
 
 
    if (arg_0 != 0 && arg_4 != 0 && arg_8 != 0) {                                // 参数检查
 
        return_code = ZwOpenKey(&KeyHandle, 1, ObjectAttributes);                // 打开注册表项
        if (return_code < 0)
            DbgPrintEx(0x33, 0, aSxsUnableToOpe, &temp_arg_4, return_code);        // 打开注册表项失败,打印错误消息
        else {                                                                    // 成功打开,获取注册表键Location的值
 
            return_code = ZwQueryValueKey(KeyHandle, stru_7DF1C658, 2, &KeyValueInformation, 0x218, &ResultLength);
            if (return_code < 0)
                DbgPrintEx(0x33, 0, aSxsUnabelToQue, &temp_arg_4, return_code);    //查询键值失败,打印错误消息
            else {
                //跳转到loc_7DF1C58B
            }
        }
    }
    else
        return_code = 0xC000000D;
 
    if (KeyHandle != 0)                                                            // 程序退出,loc_7DF1C631
        NtClose(KeyHandle);
    __security_check_cookie(security_cookie);
    return return_code;
}

loc_7DF1C58B和loc_7DF1C5AA

1
2
3
4
5
6
7
8
9
10
11
12
.text:7DF1C58B loc_7DF1C58B:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+D2↑j
.text:7DF1C58B                 cmp     [ebp+var_218], 1
.text:7DF1C592                 jz      short loc_7DF1C59B
.text:7DF1C594                 push    offset aSxsAssemblySto_1 ; "SXS: Assembly storage root location val"...
.text:7DF1C599                 jmp     short loc_7DF1C5AA
.text:7DF1C5AA loc_7DF1C5AA:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+FF↑j
.text:7DF1C5AA                 push    ebx             ; Level
.text:7DF1C5AB                 push    33h ; '3'       ; ComponentId
.text:7DF1C5AD                 call    _DbgPrintEx
.text:7DF1C5B2                 add     esp, 0Ch
.text:7DF1C5B5                 mov     edi, 0C000003Ah
.text:7DF1C5BA                 jmp     short loc_7DF1C631

在前面调用ZwQueryValueKey的第三个参数(KeyValueInformationClass)时我们看到,它指出了KeyValueInformation的类型信息, KeyValueInformationClass的实际参数的值为2。根据函数ZwQueryValueKey的说明,这个值是枚举类型KEY_VALUE_INFORMATION_CLASS定义的, 参考定义我们得出KEY_VALUE_INFORMATION_CLASS的值2对应的项为为KEY_VALUE_PARTIAL_INFORMATION。也就是说,KeyValueInformation指向一个 KEY_VALUE_PARTIAL_INFORMATION结构体变量。到这里,我们已经可以完全的推断出KeyValueInformation附近几个局部变量的数据类型和含义了。

 

回顾IDA Pro给出的局部变量分析:

1
2
3
4
5
6
7
8
9
10
11
12
.text:7DF1C49A ObjectAttributes= _OBJECT_ATTRIBUTES ptr -240h
.text:7DF1C49A ResultLength    = dword ptr -228h
.text:7DF1C49A var_224         = dword ptr -224h
.text:7DF1C49A KeyHandle       = dword ptr -220h
.text:7DF1C49A KeyValueInformation= byte ptr -21Ch
.text:7DF1C49A var_218         = dword ptr -218h
.text:7DF1C49A Size            = dword ptr -214h
.text:7DF1C49A Src             = byte ptr -210h
.text:7DF1C49A var_4           = dword ptr -4
.text:7DF1C49A arg_0           = dword ptr  8
.text:7DF1C49A arg_4           = dword ptr  0Ch
.text:7DF1C49A arg_8           = dword ptr  10h

结合KEY_VALUE_PARTIAL_INFORMATION结构体的定义,我们可以得出

1
2
3
4
5
6
typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
  ULONG TitleIndex;                    ; KeyValueInformation
  ULONG Type;                        ; var_218,注册表键值的类型
  ULONG DataLength;                    ; Size,键值长度,以字节为单位
  UCHAR Data[1];                    ; Src,键值
}KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;

KeyValueInformation中保存着键值和相关信息,是本函数的关键数据结构。

1
.text:7DF1C58B cmp[ebp + var_218], 1

指令比较了KeyValueInformation->Type值和1是否相等,根据winnt.h中对KeyValueInformation->Type值的宏定义, 指令的意图在于测试获得的键值是否为unicode空终止字符串。如果不是,则调用DbgPrintEx输出错误信息。(错误信息aSxsAssemblySto_1的内容也印证了这个推断) 至此,代码还原如下:

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
#include <ntdef.h>                                            //OBJECT_ATTRIBUTES结构体头文件
#include <lsalookup.h>                                        //LSA_UNICODE_STRING结构体头文件
#include <Wdm.h>                                            //DbgPrintEx函数头文件
#include <Winnt.h>                                            //注册表数据类型定义头文件
 
extern dword security_cookie            = 0xBB40E64E;        //var_4,一个在外部定义的变量未知类型
extern LSA_UNICODE_STRING stru_7DF1C658 = { 0x10, 0x12, L"Location" };
extern const char aSxsUnableToOpe[]        = "SXS: Unable to open storage root subkey %wZ; Status = 0x%08lx\n";
extern const char aSxsUnabelToQue[]        = "SXS: Unabel to query location from storage root subkey %wZ; Statu";
extern const char aSxsAssemblySto_1[]    = "SXS: Assembly storage root location value type is not REG_SZ\n";
 
// 返回值是某种错误代码类型,大小为dword
dword RtlpGetAssemblyStorageMapRootLocation(HANDLE arg_0,            //根对象目录句柄
                                            PUNICODE_STRING arg_4,    //对象名称
                                            ptr_32 arg_8)            //未知指针
{
    dword                return_code                = 0;                // 函数返回代码
    PUNICODE_STRING        temp_arg_4                = arg_4;            // 对象名称
    HANDLE                KeyHandle                = 0;
    ULONG                ResultLength            = 0;
    KEY_VALUE_PARTIAL_INFORMATION*    KeyValueInformation    = NULL;        // 指向注册表键值信息
    OBJECT_ATTRIBUTES    ObjectAttributes;
    ObjectAttributes.RootDirectory                = arg_0;            // 根对象目录句柄
    ObjectAttributes.ObjectName                    = &temp_arg_4;        // 对象名称,arg_4
    ObjectAttributes.Length                        = 0x18;
    ObjectAttributes.Attributes                    = 0x40;                // 使用不区分大小写的方式比较对象名称
    ObjectAttributes.SecurityDescriptor            = 0;
    ObjectAttributes.SecurityQualityOfService    = 0;
 
 
    if (arg_0 != 0 && arg_4 != 0 && arg_8 != 0) {                                // 参数检查
        return_code = ZwOpenKey(&KeyHandle, 1, ObjectAttributes);                // 打开注册表项
        if (return_code < 0)
            DbgPrintEx(0x33, 0, aSxsUnableToOpe, &temp_arg_4, return_code);        // 打开注册表项失败,打印错误消息
        else {                                                                    // 获取注册表键Location的值
            return_code = ZwQueryValueKey(KeyHandle, stru_7DF1C658, 2, &KeyValueInformation, 0x218, &ResultLength);
            if (return_code < 0)
                DbgPrintEx(0x33, 0, aSxsUnabelToQue, &temp_arg_4, return_code);    // 查询键值失败,打印错误消息
            else {
                if(KeyValueInformation->Type != 1){                                // 测试键值是否为unicode空终止字符串,loc_7DF1C58B
                    DbgPrintEx(0x33, 0, aSxsAssemblySto_1);                        // 打印错误消息
                    return_code = 0xC000003A;
                }
                else    //跳转到loc_7DF1C59B
            }
        }
    }
    else
        return_code = 0xC000000D;
 
    if (KeyHandle != 0)                                                            // 程序退出,loc_7DF1C631
        NtClose(KeyHandle);
    __security_check_cookie(security_cookie);
    return return_code;
}

loc_7DF1C59B

1
2
3
4
5
6
7
8
9
10
11
12
13
.text:7DF1C59B loc_7DF1C59B:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+F8↑j
.text:7DF1C59B                 mov     eax, [ebp+Size]
.text:7DF1C5A1                 test    al, 1
.text:7DF1C5A3                 jz      short loc_7DF1C5BC
.text:7DF1C5A5                 push    offset aSxsAssemblySto_3 ; "SXS: Assembly storage root location val"...
.text:7DF1C5AA
.text:7DF1C5AA loc_7DF1C5AA:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+FF↑j
.text:7DF1C5AA                 push    ebx             ; Level
.text:7DF1C5AB                 push    33h ; '3'       ; ComponentId
.text:7DF1C5AD                 call    _DbgPrintEx
.text:7DF1C5B2                 add     esp, 0Ch
.text:7DF1C5B5                 mov     edi, 0C000003Ah
.text:7DF1C5BA                 jmp     short loc_7DF1C631

如果键值是unicode空终止字符串,则程序跳转到loc_7DF1C59B。这段代码检查了Size也就是KeyValueInformation->DataLength的值,DataLength描述的是以字节为单位的数据长度。指令使用了al和1进行测试, 实际上是对比了DataLength的最低位是否为1,如果为1,则表明它是一个单数。而微软保存unicode字符的类型是一个16位utf-16的类型。 也就是说,无论保存的键值长度是多少,它的字节数都为偶数。在这里测试最低位是否为单数,再根据错误消息aSxsAssemblySto_3[] = "SXS: Assembly storage root location value has non-even size"的定义,编写者似乎在确认键值的unicode规范性。 代码还原如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
if(KeyValueInformation->Type != 1){                                // 测试键值是否为unicode空终止字符串,loc_7DF1C58B
    DbgPrintEx(0x33, 0, aSxsAssemblySto_1);                        // 打印错误消息
    return_code = 0xC000003A;
}
else {                                                            // loc_7DF1C59B
    if (KeyValueInformation.DataLength % 2) {                    // 指令test al, 1
        DbgPrintEx(0x33, 0, aSxsAssemblySto_3);                    // 键值不规范,打印错误消息
        return_code = 0xC000003A;
    }
    else {
        //跳转到loc_7DF1C5BC
    }
}

loc_7DF1C5BC

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
.text:7DF1C5BC loc_7DF1C5BC:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+109↑j
.text:7DF1C5BC                 movzx   ecx, word ptr [esi+2]
.text:7DF1C5C0                 cmp     eax, ecx
.text:7DF1C5C2                 jbe     short loc_7DF1C60B
.text:7DF1C5C4                 cmp     eax, 0FFFEh
.text:7DF1C5C9                 jbe     short loc_7DF1C5E9
.text:7DF1C5CB                 lea     eax, [ebp+var_224]
.text:7DF1C5D1                 push    eax
.text:7DF1C5D2                 push    offset aSxsAssemblySto_0 ; "SXS: Assembly storage root location for"...
.text:7DF1C5D7                 push    ebx             ; Level
.text:7DF1C5D8                 push    33h ; '3'       ; ComponentId
.text:7DF1C5DA                 call    _DbgPrintEx
.text:7DF1C5DF                 add     esp, 10h
.text:7DF1C5E2                 mov     edi, 0C0000106h
.text:7DF1C5E7                 jmp     short loc_7DF1C631
.text:7DF1C5E9 ; ---------------------------------------------------------------------------
.text:7DF1C5E9
.text:7DF1C5E9 loc_7DF1C5E9:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+12F↑j
.text:7DF1C5E9                 mov     [esi+2], ax
.text:7DF1C5ED                 movzx   eax, ax
.text:7DF1C5F0                 push    eax             ; Size
.text:7DF1C5F1                 call    ds:_RtlAllocateStringRoutine ; RtlpAllocateEnvBlock(x)
.text:7DF1C5F7                 mov     [esi+4], eax
.text:7DF1C5FA                 cmp     eax, ebx
.text:7DF1C5FC                 jnz     short loc_7DF1C605
.text:7DF1C5FE                 mov     edi, 0C0000017h
.text:7DF1C603                 jmp     short loc_7DF1C631
.text:7DF1C605 ; ---------------------------------------------------------------------------
.text:7DF1C605
.text:7DF1C605 loc_7DF1C605:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+162↑j
.text:7DF1C605                 mov     eax, [ebp+Size]
.text:7DF1C60B
.text:7DF1C60B loc_7DF1C60B:                           ; CODE XREF: RtlpGetAssemblyStorageMapRootLocation(x,x,x)+128↑j
.text:7DF1C60B                 push    eax             ; Size
.text:7DF1C60C                 lea     eax, [ebp+Src]
.text:7DF1C612                 push    eax             ; Src
.text:7DF1C613                 push    dword ptr [esi+4] ; Dst
.text:7DF1C616                 call    _memcpy
.text:7DF1C61B                 mov     ax, word ptr [ebp+Size]
.text:7DF1C622                 add     esp, 0Ch
.text:7DF1C625                 mov     [esi], ax
.text:7DF1C628                 xor     edi, edi
.text:7DF1C62A                 jmp     short loc_7DF1C631

如果键值的unicode规范性OK,则程序跳转到loc_7DF1C5BC。这里首先分析数据的类型,因为这段代码的指令对数据的操作让人感到很疑惑。

1
.text:7DF1C625 mov [esi], ax

指令明确的告诉我们,arg_8(ESI)指向的是一个2字节的数据,考虑到之后的比较指令 是jbe,则arg_8指向的可能是一个2字节的unsigned short int变量,因为被比较的数是ULONG类型。

1
.text:7DF1C5E9 mov [esi+2], ax

指令告诉我们,arg_8+2指向的也是一个2字节的,它也可能是unsigned short int的变量。

 

再分析这段代码的逻辑结构:

1
.text:7DF1C5BC movzx ecx, word ptr[esi + 2]

这里的esi是参数arg_8,代码把arg_8+2处的2个字节扩展并传送到ecx 接下来的一条指令

1
.text:7DF1C5C0 cmp eax, ecx

,这里的eax是KeyValueInformation.DataLength(ULONG类型) 代码编写者试图对比两个变量的大小。如果DataLength更大,则将DataLength和0FFFEh进行对比,如果它仍然大于0FFFEh,则打印错误消息 并返回错误代码。考虑到windows径的最大长度为MAX_PATH,定义为 260 个字符,除去驱动器标识x:\和空结尾,则最大长度为0xFFFE,代码应该 在测试路径是否越过最大的长度。根据错误消息aSxsAssemblySto_0的提示,也可以确认这一点。

 

如果DataLength比arg_8+2更小或相等,则程序会跳转到loc_7DF1C60B,调用memcpy函数把键值拷贝 到arg_8+4指向的指针(arg_8指向的是一个重要的数据结构),随后代码将DataLength的低16位拷贝到 arg_8, 并返回0表示函数成功。

 

如果DataLength比arg_8+2更大,但小于等于0xFFFE,则程序跳转到loc_7DF1C5E9,将DataLength的低16位拷贝到arg_8+2。 随后调用未公开的函数RtlpAllocateEnvBlock 分配DataLength低16位的数量的线程环境块TEB,由于RtlpAllocateEnvBlock没有公开信息,目前只是对函数功能的推测。最后,代码 检查RtlpAllocateEnvBlock是否调用成功,如果成功则程序跳转到loc_7DF1C60B,调用memcpy函数把键值拷贝 到arg_8+4指向的指针(进一步确认了arg_8指向的是一个重要数据结构),随后代码将DataLength的低16位拷贝到 arg_8, 并返回0表示函数成功。

 

至此,整个函数逆向分析工作已经全部完成,函数通过打开对象的注册表项,查询注册表键Location的值来获取程序集的路径。在获取路径之后,对路径字符串进行了一系列的unicode检查。如果路径字符串合规,则将键值DataLength和参数arg_8指向的前4个字节进行一系列对比,并根据对比结果进行相关数据操作。最终,程序集的路径字符串被拷贝到arg_8+4指向的地址,函数返回0。其中,RtlpAllocateEnvBlock函数的功能仍然是一个迷,arg_8指向的详细数据结构也仍然是一个迷。但是,目前不再深究这些谜题的答案。完整的还原代码请访问https://gitee.com/asminlife/rtlp-get-assembly-storage-map-root-location-reversing/blob/master/%E8%BF%98%E5%8E%9F%E4%BB%A3%E7%A0%81.c

总结

  • esi也就是arg_8所指向的变量和它的类型一直成为本次逆向的难点和焦点,这个指针所指向的具体数据结构只有通过逆向更多相关函数才能知晓。也正是因为这一点,如果存在逆向与本函数相关的函数需要,以本次逆向为基础,esi所指向的数据结构能够成为一条重要的线索。
  • 本次逆向中,DbgPrintEx函数输出的错误消息为逆向过程中的证据互相印证和确认作者意图提供了重要的帮助。
  • 这是本人第一次完整的精确还原函数源代码,最大的体会是程序的跳转和逻辑结构在逆向中消耗了很多的时间。
  • 数据类型和数据结构是逆向工程的核心要素。现代计算机仍然基于图灵模型的思想,所以它的所有操作永远围绕着数据。那么这些被操作的数据,就是我们在逆向过程中需要着重关注的重点。

[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~

最后于 2022-6-7 18:59 被mb_uklfpprl编辑 ,原因: 修正参数检查的逻辑错误
收藏
点赞3
打赏
分享
最新回复 (8)
雪    币: 8669
活跃值: 活跃值 (1155)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
cvcvxk 活跃值 10 2022-6-11 13:15
2
0

NTSTATUS
RtlpGetAssemblyStorageMapRootLocation(
    HANDLE KeyHandle,
    PCUNICODE_STRING SubKeyName,
    PUNICODE_STRING Root
    );

NTSTATUS
RtlpGetAssemblyStorageMapRootLocation(
    HANDLE KeyHandle,
    PCUNICODE_STRING SubKeyName,
    PUNICODE_STRING Root
    )
{
    NTSTATUS Status = STATUS_SUCCESS;
    OBJECT_ATTRIBUTES Obja;
    HANDLE SubKeyHandle = NULL;
    ULONG ResultLength = 0;

    struct {
        KEY_VALUE_PARTIAL_INFORMATION kvpi;
        WCHAR Buffer[DOS_MAX_PATH_LENGTH];
    } ValueData;

    static const WCHAR ValueNameBuffer[] = L"Location";
    static const UNICODE_STRING ValueName = { sizeof(ValueNameBuffer) - sizeof(WCHAR), sizeof(ValueNameBuffer), (PWSTR) ValueNameBuffer };

    if ((KeyHandle == NULL) ||
        (SubKeyName == NULL) ||
        (Root == NULL)) {
        Status = STATUS_INVALID_PARAMETER;
        goto Exit;
    }

    InitializeObjectAttributes(
        &Obja,
        (PUNICODE_STRING) &SubKeyName,
        OBJ_CASE_INSENSITIVE,
        KeyHandle,
        NULL);

    Status = NtOpenKey(&SubKeyHandle, KEY_QUERY_VALUE, &Obja);
    if (!NT_SUCCESS(Status)) {
        DbgPrintEx(
            DPFLTR_SXS_ID,
            DPFLTR_ERROR_LEVEL,
            "SXS: Unable to open storage root subkey %wZ; Status = 0x%08lx\n", &SubKeyName, Status);

        goto Exit;
    }

    Status = NtQueryValueKey(
        SubKeyHandle,
        (PUNICODE_STRING) &ValueName,
        KeyValuePartialInformation,
        &ValueData,
        sizeof(ValueData),
        &ResultLength);
    if (!NT_SUCCESS(Status)) {
        DbgPrintEx(
            DPFLTR_SXS_ID,
            DPFLTR_ERROR_LEVEL,
            "SXS: Unabel to query location from storage root subkey %wZ; Status = 0x%08lx\n", &SubKeyName, Status);

        goto Exit;
    }

    if (ValueData.kvpi.Type != REG_SZ) {
        DbgPrintEx(
            DPFLTR_SXS_ID,
            DPFLTR_ERROR_LEVEL,
            "SXS: Assembly storage root location value type is not REG_SZ\n");
        Status = STATUS_OBJECT_PATH_NOT_FOUND;
        goto Exit;
    }

    if ((ValueData.kvpi.DataLength % 2) != 0) {
        DbgPrintEx(
            DPFLTR_SXS_ID,
            DPFLTR_ERROR_LEVEL,
            "SXS: Assembly storage root location value has non-even size\n");
        Status = STATUS_OBJECT_PATH_NOT_FOUND;
        goto Exit;
    }

    if (ValueData.kvpi.DataLength > Root->MaximumLength) {
        if (ValueData.kvpi.DataLength > UNICODE_STRING_MAX_BYTES) {
            DbgPrintEx(
                DPFLTR_SXS_ID,
                DPFLTR_ERROR_LEVEL,
                "SXS: Assembly storage root location for %wZ does not fit in a UNICODE STRING\n", &SubKeyName);

            Status = STATUS_NAME_TOO_LONG;
            goto Exit;
        }

        Root->MaximumLength = (USHORT) ValueData.kvpi.DataLength;
        Root->Buffer = (PWSTR)(RtlAllocateStringRoutine)(Root->MaximumLength);
        if (Root->Buffer == NULL) {
            Status = STATUS_NO_MEMORY;
            goto Exit;
        }
    }

    RtlCopyMemory(
        Root->Buffer,
        ValueData.kvpi.Data,
        ValueData.kvpi.DataLength);

    Root->Length = (USHORT) ValueData.kvpi.DataLength;

    Status = STATUS_SUCCESS;

Exit:
    if (SubKeyHandle != NULL) {
        RTL_SOFT_VERIFY(NT_SUCCESS(NtClose(SubKeyHandle)));
    }

    return Status;
}


最后于 2022-6-11 13:17 被cvcvxk编辑 ,原因:
雪    币: 722
活跃值: 活跃值 (226)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_uklfpprl 活跃值 2022-6-11 14:55
3
0

仔细阅读了了@cvcvxk 兄改进后的代码,对原文代码的改进之处
- 对第三个参数的类型做出了准确的判断
- 使用了更文档化的SMB错误类别和代码
- 更优美的代码结构

学习了,感谢 :-)

最后于 2022-6-11 16:04 被mb_uklfpprl编辑 ,原因:
雪    币: 15
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
我skyddr 活跃值 2022-6-12 16:38
4
0
学习了,非常感谢楼主分析的资料        ·············感谢 :-)

雪    币: 601
活跃值: 活跃值 (1480)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2022-6-12 18:38
5
0
谷歌大法好
http://218.94.103.156:8090/download/developer/xpsource/Win2K3/NT/base/ntdll/sxsstorage.c
雪    币: 722
活跃值: 活跃值 (226)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_uklfpprl 活跃值 2022-6-12 19:55
6
0
kakasasa 谷歌大法好 http://218.94.103.156:8090/download/developer/xpsource/Win2K3/NT/base/ntdll/sxsstorage.c
真没想到,竟然有公开的微软源代码。。。谢谢分享。
雪    币: 100
活跃值: 活跃值 (139)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhanglKX 活跃值 2022-6-13 10:37
7
0
为何不来个x64的
雪    币: 15
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
我skyddr 活跃值 2022-6-15 10:02
8
0
楼主忙活了一个月,
搞了半天源代码早就有了,白忙活了。。。哈哈
雪    币: 377
活跃值: 活跃值 (164)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cpking 活跃值 2022-7-2 11:49
9
0
win xp 和  server 2003 代码网上早就有泄漏了

https://github.com/selfrender/Windows-Server-2003
游客
登录 | 注册 方可回帖
返回