对于注册表的REG_MULTI_SZ类型的数据个人感觉一直比较蛋疼,一个是因为在Delphi下竟然没有相关的函数,第二个是因为对于这类型的数据在使用ssdt hook修改数据的时候出现了很多问题。
为了明白到底是怎么处理的,于是就把相关的调用流程跟踪了一遍。至于是什么程序调用的这里就不提了,直接贴代码吧。
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 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 iTunes_1.10FD8F08 ; ASCII "SystemBiosVersion"
103B2A24 51 push ecx
103B2A25 FFD7 call edi
这里是应用层的第一次调用,在调用的时候第一次并没有分配相应的缓冲区来存放数据。这次调用的根本目的是为了获取注册表中对应的数据的大小。
跟入call之后可以看到如下的代码:
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中。
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
再次跟入之后就到了应用层的最底层了。
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会通过返回值来重新请求长度,相关代码如下:
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的源代码
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,而不是成功。而这个返回码却会导致应用程序不会第二次获取注册表的内容。虽然得到的数据长度是正确的,但是由于返回的错误码导致程序不再读取,代码如下:
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就在这个地方卡壳了,既要让程序能够获取到真正的长度还要让状态为成功。
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;
}
这段代码修改的方式是按照正常的读取方式修改的,但是却并没有达到想要的效果,如果谁知道为什么还望不吝赐教。