从RegQueryValueExA到ZwQueryValueKey

对于注册表的REG_MULTI_SZ类型的数据个人感觉一直比较蛋疼,一个是因为在Delphi下竟然没有相关的函数,第二个是因为对于这类型的数据在使用ssdt hook修改数据的时候出现了很多问题。

为了明白到底是怎么处理的,于是就把相关的调用流程跟踪了一遍。至于是什么程序调用的这里就不提了,直接贴代码吧。

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
103B29D8    8B4C24 18       mov ecx,dword ptr ss:[esp+0x18]
103B29DC    8B3D 34F0EE10   mov edi,dword ptr ds:[<&ADVAPI32.RegQuer>; ADVAPI32.RegQueryValueExA
103B29E2    894424 14       mov dword ptr ss:[esp+0x14],eax
103B29E6    8D4424 14       lea eax,dword ptr ss:[esp+0x14]
103B29EA    50              push eax
103B29EB    6A 00           push 0x0
103B29ED    6A 00           push 0x0
103B29EF    6A 00           push 0x0
103B29F1    68 088FFD10     push iTunes_1.10FD8F08                   ; ASCII "SystemBiosVersion"
103B29F6    51              push ecx
103B29F7    FFD7            call edi103B29F9    85C0            test eax,eax
103B29FB    75 48           jnz XiTunes_1.103B2A45
103B29FD    8B5424 14       mov edx,dword ptr ss:[esp+0x14]
103B2A01    52              push edx
103B2A02    50              push eax
103B2A03    E8 7820CDFF     call iTunes_1.10084A80
103B2A08    50              push eax
103B2A09    FF15 84FEEE10   call dword ptr ds:[<&KERNEL32.HeapAlloc>>; ntdll.RtlAllocateHeap
103B2A0F    8B4C24 18       mov ecx,dword ptr ss:[esp+0x18]
103B2A13    8BF0            mov esi,eax
103B2A15    8D4424 14       lea eax,dword ptr ss:[esp+0x14]
103B2A19    50              push eax
103B2A1A    56              push esi
103B2A1B    6A 00           push 0x0
103B2A1D    6A 00           push 0x0
103B2A1F    68 088FFD10     push iTunes_1.10FD8F08                   ; ASCII "SystemBiosVersion"
103B2A24    51              push ecx
103B2A25    FFD7            call edi

这里是应用层的第一次调用,在调用的时候第一次并没有分配相应的缓冲区来存放数据。这次调用的根本目的是为了获取注册表中对应的数据的大小。

跟入call之后可以看到如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
77DA6ECC            .  8D8D 6CFFFFFF lea ecx,dword ptr ss:[ebp-0x94]
77DA6ED2            .  898D 58FFFFFF mov dword ptr ss:[ebp-0xA8],ecx
77DA6ED8            .  0F85 66730000 jnz ADVAPI32.77DAE244
77DA6EDE            >  8B35 2814DA77 mov esi,dword ptr ds:[<&ntdll.NtQueryVal>;  ntdll.ZwQueryValueKey
77DA6EE4            .  8D85 5CFFFFFF lea eax,dword ptr ss:[ebp-0xA4]
77DA6EEA            .  50            push eax
77DA6EEB            .  BF 90000000   mov edi,0x90
77DA6EF0            .  57            push edi
77DA6EF1            .  8D85 6CFFFFFF lea eax,dword ptr ss:[ebp-0x94]
77DA6EF7            .  50            push eax
77DA6EF8            .  6A 02         push 0x2
77DA6EFA            .  53            push ebx
77DA6EFB            .  FFB5 50FFFFFF push dword ptr ss:[ebp-0xB0]
77DA6F01            >  FFD6          call esi

上面的代码依旧处于advapi32.dll中,而经过这次调用就到了ntdll.dll中。

1
2
3
4
7C92D950 ntdll.ZwQueryValueKey          B8 B1000000     mov eax,0xB1
7C92D955                                BA 0003FE7F     mov edx,0x7FFE0300
7C92D95A                                FF12            call dword ptr ds:[edx]
7C92D95C                                C2 1800         retn 0x18

再次跟入之后就到了应用层的最底层了。

1
2
3
7C92E4F0 ntdll.KiFastSystemCall             8BD4            mov edx,esp
7C92E4F2                                    0F34            sysenter
7C92E4F4 ntdll.KiFastSystemCallRet          C3              retn

调用sysenter进入到ring 0继续运行,到这里应用层就无法跟踪下去了。但是在内核中通过ssdt hook得到数据的时候数据已经经过了几次调用了,所以看到的并不是直接从应用层传下来的数据。

而应用层在调用的过程中ntdll.dll第一次请求的数据长度为0x90,所以刚开始每次在调试驱动的时候看到数据都是0x90感觉有点奇怪。

为了使上层分配的空间能够容纳要返回的数据,所以此时应该修正调用ZwQueryValueKey得到的resultlength的长度。并且返回STATUS_BUFFER_OVERFLOW。之所以返回这个是由ntdll.dll来决定的,执行完之后ntdll.dll会通过返回值来重新请求长度,相关代码如下:

1
2
3
4
5
6
7
77DA6F01                                   > /FFD6          call esi
77DA6F03                                   . |83BD 64FFFFFF>cmp dword ptr ss:[ebp-0x9C],0x0
77DA6F0A                                   . |8985 68FFFFFF mov dword ptr ss:[ebp-0x98],eax
77DA6F10                                   . |8B85 60FFFFFF mov eax,dword ptr ss:[ebp-0xA0]
77DA6F16                                   . |0F85 63B40100 jnz ADVAPI32.77DC237F
77DA6F1C                                   > |81BD 68FFFFFF>cmp dword ptr ss:[ebp-0x98],0x80000005
77DA6F26                                   . |0F84 EB0A0000 je ADVAPI32.77DA7A17

0x80000005对应的就是STATUS_BUFFER_OVERFLOW,那么此时ntdll.dll会尝试再次获取。

直到获取成功,而这时在应用层得到的错误码为EA,表示还有更多的数据。但是在没有hook的时候返回的状态却是成功的。至于为什么刚开始感觉比较困惑,后来查看了一下reactos的源代码

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
Status = NtQueryValueKey( hKey,
lpValueName,
KeyValueInformationClass,
KeyValueInformation,
BufferLength,
&ResultLength
);
//
// A return value of STATUS_BUFFER_TOO_SMALL would mean that there
// was not enough room for even the known (i.e. fixed length portion)
// of the structure.
//
ASSERT( Status != STATUS_BUFFER_TOO_SMALL );
if( ( Status == STATUS_BUFFER_OVERFLOW ) &&
( !ARGUMENT_PRESENT( lpData ) ) ) {
//
 
//  STATUS_BUFFER_OVERFLOW means that the API returned all the
//  information in the fixed portion of the structure
//  KEY_VALUE_BASIC_INFORMATION or KEY_VALUE_PARTIAL_INFORMATION,
//  but not the value name or the value data.
//
//  If KeyValueInformationClass is equal to KeyValueBasicInformation
//  then the API would return the value name. But since we are not
//  interested in the value name (it was supplied by the client), we
//  can assume that the API succeeded.
//
//  If KeyValueInformationClass is equal to KeyValuePartialInformation
//  then the API would return the value data. But lpData == NULL
//  means that the client is not interested on the value data, but
//  just on its size. For this reason, we can also assume that the
//  API succeeded.
//
Status = STATUS_SUCCESS;
}

虽然缓冲区不够,但是此时用户并不关心数据。所以此时只返回大小就可以了。然而程序在实际的调用过程中使用的类型为KeyValuePartialInformation。所以按照通常的情况在调用的时候返回STATUS_BUFFER_OVERFLOW,但是实际的情况并不是预想的那样。在应用层程序调用完成之后会返回0xEA,而不是成功。而这个返回码却会导致应用程序不会第二次获取注册表的内容。虽然得到的数据长度是正确的,但是由于返回的错误码导致程序不再读取,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
103B29F7                                    FFD7            call edi
103B29F9                                    85C0            test eax,eax
103B29FB                                    75 48           jnz XiTunes_1.103B2A45
103B29FD                                    8B5424 14       mov edx,dword ptr ss:[esp+0x14]
103B2A01                                    52              push edx
103B2A02                                    50              push eax
103B2A03                                    E8 7820CDFF     call iTunes_1.10084A80
103B2A08                                    50              push eax
103B2A09                                    FF15 84FEEE10   call dword ptr ds:[<&KERNEL32.HeapAlloc>]          ; ntdll.RtlAllocateHeap
103B2A0F                                    8B4C24 18       mov ecx,dword ptr ss:[esp+0x18]
103B2A13                                    8BF0            mov esi,eax
103B2A15                                    8D4424 14       lea eax,dword ptr ss:[esp+0x14]
103B2A19                                    50              push eax
103B2A1A                                    56              push esi
103B2A1B                                    6A 00           push 0x0
103B2A1D                                    6A 00           push 0x0
103B2A1F                                    68 088FFD10     push Tunes_1.10FD8F08                             ; ASCII "SystemBiosVersion"
103B2A24                                    51              push ecx
103B2A25                                    FFD7            call edi

检测的Eax中的数值,如果不是0(STATUS_SUCESS)那么就不再尝试读取了,所以现在要想让他读取就只能改掉这个跳转。但是这并不是我想采用的方式。于是hook就在这个地方卡壳了,既要让程序能够获取到真正的长度还要让状态为成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (RtlCompareUnicodeString(ValueName,&MySystemBiosVersion,TRUE) ==0)
					{
						if (Length < ((pFackSystemBiosVersion->DataLength) + 0x0C))
						{
							//*ResultLength = pFackSystemBiosVersion->DataLength;							memcpy(valueInfoP->Data,pFackSystemBiosVersion->Data,Length-0x0C);
							valueInfoP->TitleIndex = pFackSystemBiosVersion->TitleIndex;							valueInfoP->Type = pFackSystemBiosVersion->Type;							valueInfoP->DataLength = Length-0x0C;
							*ResultLength = ((pFackSystemBiosVersion->DataLength) + 0x0C);
							status = STATUS_BUFFER_OVERFLOW;
						} else{
 
						KdPrint(("REG Changed:: SystemBiosVersion Befor: %ws , After: %ws \n",
							valueInfoP->Data,
							pFackSystemBiosVersion->Data));
						memcpy(valueInfoP->Data,pFackSystemBiosVersion->Data,pFackSystemBiosVersion->DataLength);
						valueInfoP->TitleIndex = pFackSystemBiosVersion->TitleIndex;
						valueInfoP->Type = pFackSystemBiosVersion->Type;
						valueInfoP->DataLength = pFackSystemBiosVersion->DataLength;
						//valueInfoP->Data[1] = pFackSystemBiosVersion->Data[1];				
						//(PKEY_VALUE_PARTIAL_INFORMATION)KeyValueInformation = valueInfoP;
						*ResultLength = ((pFackSystemBiosVersion->DataLength) + 0x0C);
						status = STATUS_SUCCESS;
						}

这段代码修改的方式是按照正常的读取方式修改的,但是却并没有达到想要的效果,如果谁知道为什么还望不吝赐教。

 

原创文章,转载请注明: 转载自 obaby@mars

本文标题: 《从RegQueryValueExA到ZwQueryValueKey》

本文链接地址: http://h4ck.org.cn/2011/09/from-regqueryvalueexa-to-zwqueryvaluekey/

You may also like

发表评论

电子邮件地址不会被公开。 必填项已用*标注