名思義我們今天討論的是應(yīng)用層軟件的調(diào)試,且是64位的軟件,當(dāng)一個軟件發(fā)生錯誤或者異常時(不管是無意的還是刻意的)首先這個錯誤信息會先在內(nèi)核中處理,如果是驅(qū)動程序產(chǎn)生的異常或者錯誤,能處理就處理不能處理就直接藍屏,如果是應(yīng)用程序的話,在內(nèi)核中分配一些諸如ExceptionRecord、ContextRecord的結(jié)構(gòu)體后就返回到用戶層面繼續(xù)處理了。
內(nèi)核函數(shù) KiExceptionDispatch在檢測到異常或錯誤來自用戶空間時,就會調(diào)用用戶層的KiUserExceptionDispatcher,這個函數(shù)是用戶層面的異常錯誤總?cè)肟冢挥贜TDLL.DLL中,我們編寫軟件時用到的SEH異常處理,都是由這個函數(shù)來實現(xiàn)的。
上圖是win10的目前最新的版本
我們看下它的導(dǎo)出函數(shù)KiUserExceptionDispatcher,其中RtlDispatchException就是核心的異常處理過程,RtlGuardRestoreContext 函數(shù)是調(diào)用我們提供給SEH的安全返回地址。
現(xiàn)在市面上的商業(yè)軟件不管32位還是64位,都有防跟蹤、防逆向的指令在保護自身代碼,很多調(diào)試器也有相應(yīng)的插件提供過保護,32位系統(tǒng)下,基本上OD可以過所有保護。但64位系統(tǒng)下就不行了,其中win64對內(nèi)核有更加嚴(yán)格的保護,不像32位下你可以隨時接管中斷接口、異常接口、SSDT 等,64位系統(tǒng)下你這樣做分分鐘藍屏,PatchGuard了解一下。
基于這個原因,其實我們可以從ntdll的RtlDispatchException函數(shù)入手,我們掛鉤RtlDispatchException,接管用戶層面的所有異常,然后過濾出我們感興趣的程序即可。
我們增加一個新的節(jié)區(qū)(fix)調(diào)整節(jié)區(qū)屬性為可讀、可寫、可執(zhí)行、可共享(應(yīng)用程序加載NTDLL時不會為這個節(jié)區(qū)額外分配空間,直接映射我們分配的空間)
手動修改call的跳轉(zhuǎn)地址。然后自己編寫過濾代碼,將編好的代碼用ue寫入我們新建的節(jié)區(qū)就可以了。假如我們要跟蹤一個license 授權(quán)文件的解密流程,那就需要掛鉤ZwCreateFile函數(shù),獲取到文件句柄后,掛鉤ZwReadFile函數(shù)。方法和RtlDispatchException一樣,直接把指令call到我們提前定義的空間地址上。整個過程就是在真實的環(huán)境中運行,軟件自身的反調(diào)試功能統(tǒng)統(tǒng)失效,因為我們就沒用市面上的任何調(diào)試器,我們直接通過和NTDLL的自定義節(jié)區(qū)(fix)交換數(shù)據(jù)來獲取調(diào)試數(shù)據(jù)。
很多軟件喜歡用VMP來加殼,我們就針對VMP來簡單說下調(diào)試過程吧。VMP殼軟件在啟動過程中自身會調(diào)用一些函數(shù)來檢測電腦的運行環(huán)境,如果電腦處在調(diào)試模式,或者檢測到調(diào)試端口、事件之類,就會異常退出,我們沒用調(diào)試器所以就不用關(guān)心這些事情,唯一要做的是VMP對自陷指令的處理,比如:int1 、int 3。我們不是接管了RtlDispatchException嘛,所以VMP 產(chǎn)生的異常指令,都會被我們過濾到,把這些過濾到的異常指令全部保存下來,將來在動態(tài)跟蹤時,遇到這些指令就放行。
pushfq
or byte ptr [rsp+1],1 ;單步
popfq
持續(xù)產(chǎn)生int1 中斷,就可以把整個代碼流程給抓取出來。用正確的授權(quán)走一遍,再用錯誤的走一遍,比對一下不同點很快就可以找到關(guān)鍵 jz jnz 指令點。
ALPC這玩意挺復(fù)雜的,現(xiàn)在的安全軟件應(yīng)該都是在r3下hook services來實現(xiàn)的,比如
@FaEry
https://bbs.pediy.com/thread-251158.htm寫的
但是r3下掛會遇到PPL問題
https://bbs.pediy.com/thread-250446.htm
所以才有了今天的文章
直接內(nèi)聯(lián)hook -PG
找指針改地址,做DKOM -安全軟件不應(yīng)該這樣做
由于ALPC_PORT是object回調(diào),因此掛object 的情況下,win7可以,win8以上直接PG并且只能攔得到創(chuàng)建但是不能攔得到ALPC交換的過程
IO掛鉤 -本文的方法
最開始我是自己逆向ALPC時候偶然發(fā)現(xiàn)的,后來搜了一下外網(wǎng),老外
http://www.zer0mem.sk/?p=542
也找到了一個一樣的方法,不過他的研究比我深入一點
在
NtAlpcSetInformation->AlpcpInitializeCompletionList->AlpcpAllocateCompletionPacketLookaside
中可以看到
這是一個IO回調(diào),可以通過設(shè)置微軟允許的IOComplateCallback來做一些操作
首先我們要知道內(nèi)核里面有個全局的雙向鏈表是叫做nt!alpclist 根據(jù)zeromem的說法是
雖然這玩意不公開,但是有個公開結(jié)構(gòu)AlpcPortObjectType是跟他在一起的,定位到AlpcPortObjectType再定位到它就行
請注意這是一個坑,因為這個方法win7好使,僅限于win8之前,win8后,這玩意就成這樣的布局了:
因此我們再也無法通過公開的AlpcPortObjectType再拿到信息了,只能找特征碼:
\x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\x48\xCC\xCC\xCC\xCC\xCC\xCC\xCC自己找,別抄.....
那個alpclistlock也是一樣,特征碼處理
我們需要干什么:
1. 遍歷alpclist
2. 找到這個ALPC是否是我們的需要掛鉤的進程(比如services.exe)
3. 設(shè)置IO回調(diào)掛鉤
首先記得鎖一下:
if (_interlockedbittestandset64((__int64*)&gAlpcpPortListLock, 0))
ExfAcquirePushLockShared((PULONG_PTR)&gAlpcpPortListLock);
然后是遍歷這個雙向鏈表
PLIST_ENTRY pLink=NULL;
for (pLink=gAlpcpPortList->Flink; pLink !=(PLIST_ENTRY)&gAlpcpPortList->Flink; pLink=pLink->Flink)
{
_ALPC_PORT* alpcPort=CONTAINING_RECORD(pLink, _ALPC_PORT, PortListEntry);
其中ALPC_PORT結(jié)構(gòu)每個版本都在變
struct _ALPC_PORT
{
struct _LIST_ENTRY PortListEntry; //0x0
struct _ALPC_COMMUNICATION_INFO* CommunicationInfo; //0x10
struct _EPROCESS* OwnerProcess; //0x18
VOID* CompletionPort; //0x20
VOID* CompletionKey; //0x28
struct _ALPC_COMPLETION_PACKET_LOOKASIDE* CompletionPacketLookaside; //0x30
VOID* PortContext; //0x38
struct _SECURITY_CLIENT_CONTEXT StaticSecurity; //0x40
struct _LIST_ENTRY MainQueue; //0x88
struct _LIST_ENTRY PendingQueue; //0x98
struct _LIST_ENTRY LargeMessageQueue; //0xa8
struct _LIST_ENTRY WaitQueue; //0xb8
union
{
struct _KSEMAPHORE* Semaphore; //0xc8
struct _KEVENT* DummyEvent; //0xc8
};
struct _ALPC_PORT_ATTRIBUTES PortAttributes; //0xd0
struct _EX_PUSH_LOCK Lock; //0x118
struct _EX_PUSH_LOCK ResourceListLock; //0x120
struct _LIST_ENTRY ResourceListHead; //0x128
struct _ALPC_COMPLETION_LIST* CompletionList; //0x138
struct _ALPC_MESSAGE_ZONE* MessageZone; //0x140
struct _CALLBACK_OBJECT* CallbackObject; //0x148
VOID* CallbackContext; //0x150
struct _LIST_ENTRY CanceledQueue; //0x158
volatile LONG SequenceNo; //0x168
union
{
struct
{
ULONG Initialized : 1; //0x16c
ULONG Type : 2; //0x16c
ULONG ConnectionPending : 1; //0x16c
ULONG ConnectionRefused : 1; //0x16c
ULONG Disconnected : 1; //0x16c
ULONG Closed : 1; //0x16c
ULONG NoFlushOnClose : 1; //0x16c
ULONG ReturnExtendedInfo : 1; //0x16c
ULONG Waitable : 1; //0x16c
ULONG DynamicSecurity : 1; //0x16c
ULONG Wow64CompletionList : 1; //0x16c
ULONG Lpc : 1; //0x16c
ULONG LpcToLpc : 1; //0x16c
ULONG HasCompletionList : 1; //0x16c
ULONG HadCompletionList : 1; //0x16c
ULONG EnableCompletionList : 1; //0x16c
} s1; //0x16c
ULONG State; //0x16c
} u1; //0x16c
struct _ALPC_PORT* TargetQueuePort; //0x170
struct _ALPC_PORT* TargetSequencePort; //0x178
struct _KALPC_MESSAGE* volatile CachedMessage; //0x180
ULONG MainQueueLength; //0x188
ULONG PendingQueueLength; //0x18c
ULONG LargeMessageQueueLength; //0x190
ULONG CanceledQueueLength; //0x194
ULONG WaitQueueLength; //0x198
};
之后判斷進程的合法性:
if (!alpcPort->OwnerProcess ||
PsGetProcessId((PEPROCESS)alpcPort->OwnerProcess) !=TargetPrcessId ||
!alpcPort->CompletionPort)
continue;
然后就是設(shè)置回調(diào)了
void* IoMiniCompletPtr=IoAllocateMiniCompletionPacket(ALPC_NotifyCallback, alpcPort);
if (IoMiniCompletPtr==NULL)
{
__debugbreak();
return;
}
IoSetIoCompletionEx(
alpcPort->CompletionPort,
alpcPort->CompletionKey,
nullptr,
NULL,
NULL,
FALSE,
IoMiniCompletPtr);
這個IoAllocateMiniCompletionPacket和他的結(jié)構(gòu)我是自己IDA逆向推導(dǎo)出來的,因為沒公開,沒任何可靠的信息
struct _IO_MINI_COMPLETION_PACKET_USER
{
struct _LIST_ENTRY ListEntry; //0x0
ULONG PacketType; //0x10
VOID* KeyContext; //0x18
VOID* ApcContext; //0x20
LONG IoStatus; //0x28
ULONGLONG IoStatusInformation; //0x30
VOID(*MiniPacketCallback)(struct _IO_MINI_COMPLETION_PACKET_USER* arg1, VOID* arg2); //0x38
VOID* Context; //0x40
UCHAR Allocated; //0x48
};
/*
這兩玩意是我IDA逆向看的,微軟也沒說
PAGE:00000001402C807C IoAllocateMiniCompletionPacket proc near
PAGE:00000001402C807C ; CODE XREF: AlpcpAllocateCompletionPacketLookaside+82↑p
PAGE:00000001402C807C ; NtCreateWorkerFactory+179↓p
PAGE:00000001402C807C ; DATA XREF: ...
PAGE:00000001402C807C
PAGE:00000001402C807C arg_0 =qword ptr 8
PAGE:00000001402C807C
PAGE:00000001402C807C 48 89 5C 24 08 mov [rsp+arg_0], rbx
PAGE:00000001402C8081 57 push rdi
PAGE:00000001402C8082 48 83 EC 20 sub rsp, 20h
PAGE:00000001402C8086 48 8B DA mov rbx, rdx
PAGE:00000001402C8089 33 D2 xor edx, edx
PAGE:00000001402C808B 48 8B F9 mov rdi, rcx
PAGE:00000001402C808E 8D 4A 03 lea ecx, [rdx+3]
PAGE:00000001402C8091 E8 AA 9C 03 00 call IopAllocateMiniCompletionPacket
PAGE:00000001402C8096 48 85 C0 test rax, rax ; 【rax=分配的io包結(jié)構(gòu)】
PAGE:00000001402C8099 74 0C jz short loc_1402C80A7
PAGE:00000001402C809B 48 89 78 38 mov [rax+38h], rdi ; 【0x38=_IO_MINI_COMPLETION_PACKET_USER->MiniPacketCallback】
PAGE:00000001402C809F 48 89 58 40 mov [rax+40h], rbx ; 【0x40=_IO_MINI_COMPLETION_PACKET_USER->Context】
PAGE:00000001402C80A3 C6 40 48 01 mov byte ptr [rax+48h], 1
PAGE:00000001402C80A7
PAGE:00000001402C80A7 loc_1402C80A7: ; CODE XREF: IoAllocateMiniCompletionPacket+1D↑j
PAGE:00000001402C80A7 48 8B 5C 24 30 mov rbx, [rsp+28h+arg_0]
PAGE:00000001402C80AC 48 83 C4 20 add rsp, 20h
PAGE:00000001402C80B0 5F pop rdi
PAGE:00000001402C80B1 C3 retn
PAGE:00000001402C80B1 IoAllocateMiniCompletionPacket endp
rax=一個指針
*/
typedef void(__fastcall* MINIPACKETCALLBACK)(
__in _IO_MINI_COMPLETION_PACKET_USER* miniPacket,
__inout void* context
);
extern "C" {
NTKERNELAPI
void*
NTAPI IoAllocateMiniCompletionPacket(
__in MINIPACKETCALLBACK miniPacketCallback,
__in const void* context
);
NTKERNELAPI
void
NTAPI IoSetIoCompletionEx(
__inout void* completitionPort,
__in const void* keyContext,
__in const void* apcContext,
__in ULONG_PTR ioStatus,
__in ULONG_PTR ioStatusInformation,
__in bool allocPacketInfo,
__in const void* ioMiniCoompletitionPacketUser
);
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
NTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader(
_In_ PVOID Base
);
NTKERNELAPI
void*
NTAPI IoFreeMiniCompletionPacket(
__in const void* miniPacket
);
}
掛鉤后,我們順利得到信息
ALPC這個只是一個標(biāo)準(zhǔn)協(xié)議,每個不同的服務(wù)比如 創(chuàng)建服務(wù)與創(chuàng)建賬號與搜索系統(tǒng)信息 等的具體內(nèi)容都是不同的,要自己手動解碼,但是在這里如zeroman所說,這個keycontext帶了一個叫做SubProcessTag的東西,這個東西就對應(yīng)這個RPC所帶的服務(wù)(其實不是ALPC傳過來的),就可以拿到基本的,某進程發(fā)了RPC給某進程,并且RPC是關(guān)于xxx服務(wù)的,但是服務(wù)的具體信息比如名字啥的
具體解碼禁止抄作業(yè)
其他的參考:
https://bbs.pediy.com/thread-268225.htm#msg_header_h1_7
https://blog.csdn.net/weixin_43787608/article/details/84555474
https://bbs.pediy.com/thread-251158.htm
http://www.zer0mem.sk/?p=542
點擊收藏 | 0關(guān)注 | 2