直接找到 system 进程的 EPROCESS 的 token 值,将它覆盖到提权目标进程的 EPROCESS 的 token 值处,就可以完成提权操作了。然后由于子进程会继承父进程权限,所以假设 explorer 现在提权成功有了 system 权限,那么后续 explorer 创建的一切子进程都是 system 权限的了。
STATUS 是一个 LONG 有符号长整数,NT_SUCCESS() 宏定义是 status >= 0 即成功,而不是 status == 0。因为微软对 STATUS 的定义是通过最高两位来表达不同的含义,具体看 https://blog.csdn.net/ZhengZhiRen/article/details/5781515
plus July 14th, 2021 at 08:43 pm
PAGE_GUARD 内存属性是一次性事件,可以利用这个特性在异常处理例程中做一些特别处理
plus July 14th, 2021 at 05:30 pm
简单的 VEH 实现
LONG veh_handler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
PEXCEPTION_RECORD exr = ExceptionInfo->ExceptionRecord;
LONG lRet = EXCEPTION_CONTINUE_SEARCH;
if (exr->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
{
printf("Exception occured -> STATUS_GUARD_PAGE_VIOLATION\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
C++ 类的前向声明:https://blog.csdn.net/yyyxxxzzz111/article/details/39996695
个人备注:结构体也可以用
可以声明一个类而不定义它
class Screen;//declaration of the Screen class
这个声明,有时候被称为前向声明(forward declaration),在程序中引入了类类型的Screen.在声明之后,定义之前,类Screen是一个不完全类型(incompete type),即已知Screen是一个类型,但不知道包含哪些成员.
不完全类型只能以有限方式使用,不能定义该类型的对象,不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数.
windbg 的 !dtx 命令可以自定义数据结构
HEVD:Windows内核漏洞学习
WebGoat,和 DVWA 一样的东西,不过是 java 后端的:https://github.com/WebGoat/WebGoat
HEVE :windows内核漏洞学习靶场
DVWA :web安全漏洞学习靶场
学习 VT 技术可以看看 NewBluePill
windbg 控制窗口的消息显示,有两个很好用的命令:
.ofilter 可以控制显示的消息只包含或不包含指定 string,如:
.ofilter QKEntDATP 只显示包含 QKEntDATP 的输出
.ofilter /! QKEntDATP 只显示不包含 QKEntDATP 的输出
***************************
.outmask 可以控制显示的消息的级别,如 OutputDebugString or DbgPrint 的 flag 级别为 0x80,则:
.outmask- 0x80 即控制显示的消息不包含 OutputDebugString or DbgPrint 打印的消息
.outmask 0x80 即控制显示的消息包含 OutputDebugString or DbgPrint 打印的消息
windbg 修复栈帧的示例:
0:003> dps rsp
000000fe`1627e910 00000000`00000000
000000fe`1627e918 00007ff8`2085d070 ntdll!NtClose
000000fe`1627e920 00000000`00000014
000000fe`1627e928 00000000`00000000
000000fe`1627e930 00000000`c0000008
000000fe`1627e938 00000000`00000000
000000fe`1627e940 00007ff8`20860cea ntdll!KiRaiseUserExceptionDispatcher+0x3a
000000fe`1627e948 00000000`00000000
.......
000000fe`1627e9b0 00000000`00000000
000000fe`1627e9b8 00007ff8`20813c70 ntdll!RtlSetLastWin32Error+0x40
000000fe`1627e9c0 00000000`00000000
000000fe`1627e9c8 00000000`00000000
000000fe`1627e9d0 00000000`00000000
000000fe`1627e9d8 00000000`0040668d 《 ======================== r3hook 注入模块地址
000000fe`1627e9e0 00000000`00000000
000000fe`1627e9e8 00000000`00000000
000000fe`1627e9f0 00000000`00000000
000000fe`1627e9f8 00000000`00000000
000000fe`1627ea00 000000fe`1627ea30
000000fe`1627ea08 00000000`7777004c
000000fe`1627ea10 000000fe`1627ea40
000000fe`1627ea18 00000000`00000018
000000fe`1627ea20 000000fe`1627ea40
000000fe`1627ea28 00000000`00000018
000000fe`1627ea30 00006c02`00000000
000000fe`1627ea38 00000000`00000018
000000fe`1627ea40 00000001`00000018
000000fe`1627ea48 00000000`00000000
000000fe`1627ea50 000002ef`09505ad0
000000fe`1627ea58 000002ef`09505ad0
000000fe`1627ea60 000002ef`09505ad0
000000fe`1627ea68 00007ff8`1e57dee5 KERNELBASE!CloseHandle+0x45
000000fe`1627ea70 000000fe`1627ebd8
000000fe`1627ea78 00000000`00000000
000000fe`1627ea80 000002ef`09505ad0
000000fe`1627ea88 000000fe`1627eb50
0:003> kf=000000fe`1627ea70 00007ff8`1e57dee5 10 《 ============ 还原剩下的堆栈
# Memory Child-SP RetAddr Call Site
00 000000fe`1627ea70 00007ff8`1c6c96c6 KERNELBASE!CloseHandle+0x45
01 30 000000fe`1627eaa0 00007ff8`1c6c93bc profext!wil::unique_any_array_ptr::reset+0x6e
02 30 000000fe`1627ead0 00007ff8`1c6d44b8 profext!CreateDirectoryJunction+0x5ec
03 530 000000fe`1627f000 00007ff8`1778a61c profext!CreateDirectoryJunctionsForUserProfileWorker+0x118
04 90 000000fe`1627f090 00007ff8`1777afe9 profsvc!CUserProfile::RestoreUserProfile+0x167ec
05 220 000000fe`1627f2b0 00007ff8`1776b365 profsvc!::operator()+0xc1
06 80 000000fe`1627f330 00007ff8`17766a9b profsvc!CUserProfile::Load+0x325
07 310 000000fe`1627f640 00007ff8`1776633d profsvc!LogonThreadProcInternal+0x25b
08 200 000000fe`1627f840 00007ff8`1776561f profsvc!LogonThreadProc+0x1d
09 30 000000fe`1627f870 00007ff8`207f2e93 profsvc!_WorkItemWrapper+0x2f
0a 30 000000fe`1627f8a0 00007ff8`207f45b4 ntdll!TppSimplepExecuteCallback+0x123
0b 50 000000fe`1627f8f0 00007ff8`20657c24 ntdll!TppWorkerThread+0x8d4
0c 3c0 000000fe`1627fcb0 00007ff8`2082d721 kernel32!BaseThreadInitThunk+0x14
0d 30 000000fe`1627fce0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
Windbg程序调试系列1-Mex扩展使用总结,有空看看
https://www.cnblogs.com/tianqing/p/9369693.html
detours 在 unhook 时不会等待已经走进 fake 函数的线程离开,在某些场景下,如 unhook 完成后立刻卸载 fake 函数所在的 dll 时,如果此时还有线程跑在 fake 函数里面,则会引起进程崩溃
KeIpiGenericCall 这东西能同时占住所有cpu并执行一个指定回调。用来做内核hook似乎很美好。
汇编指令获取 teb:
x86进程:
TEB = FS:[0] = FS:[0x18]
PEB = FS:[30]
x64进程:
TEB = GS:[0] = GS:[0x30]
PEB = GS:[60]
IE 调试脚本失败,提示 jsdebuggeride.dll 模块内部偏移异常。调试发现是家在一个 com 组件时找不到已注册的组件。
安装一个 VS 运行时就行,比如安装整个 VC++6.0 等。
32位 PE (64位不记录)里记录着这个程序使用的 SEH handler,位置存放在数据目录表第 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 项中,这里记录的 RVA 指向唯一一个 IMAGE_LOAD_CONFIG_DIRECTORY 结构,结构的 SEHandlerTable 和 SEHandlerCount 字段描述 SEH handler 信息,从 SEHandlerTable 指向的地址处依次提取可得每一个 handler 的 RVA。
要注意是,SEHandlerTable 是 VA,不是 RVA。
MSDN页:
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_load_config_directory64
windbg 的 wt 功能,能跟踪详细的函数调用链,但有使用限制,如下:
usermode:
32 和 64 位均可使用
kernelmode:
仅支持 32 位内核调试时使用
示例:
0:000:x86> wt
21 0 [ 0] ntdll32!RtlAllocateHeap
9 0 [ 0] ntdll32!RtlImageNtHeader
3 0 [ 1] ntdll32!RtlImageNtHeader
21 0 [ 2] ntdll32!memcpy
17 21 [ 1] ntdll32!RtlImageNtHeader
7 0 [ 1] ntdll32!RtlDosPathNameToNtPathName_U
6 0 [ 1] ntdll32!RtlUlonglongByteSwap
3 0 [ 2] ntdll32!RtlpNtEnumerateSubKey
21 0 [ 3] ntdll32!memcpy
17 21 [ 2] ntdll32!RtlpNtEnumerateSubKey
14 0 [ 3] ntdll32!RtlLockHeap
38 35 [ 2] ntdll32!RtlpNtEnumerateSubKey
21 0 [ 3] ntdll32!RtlEnterCriticalSection
43 56 [ 2] ntdll32!RtlpNtEnumerateSubKey
15 0 [ 3] ntdll32!RtlpNtEnumerateSubKey
9 0 [ 4] ntdll32!RtlpNtEnumerateSubKey
27 9 [ 3] ntdll32!RtlpNtEnumerateSubKey
47 92 [ 2] ntdll32!RtlpNtEnumerateSubKey
20 0 [ 3] ntdll32!RtlAllocateHeap
9 0 [ 3] ntdll32!RtlImageNtHeader
3 0 [ 4] ntdll32!RtlImageNtHeader
21 0 [ 5] ntdll32!memcpy
17 21 [ 4] ntdll32!RtlImageNtHeader
7 0 [ 4] ntdll32!RtlDosPathNameToNtPathName_U
2 0 [ 4] ntdll32!RtlUlonglongByteSwap
22 0 [ 4] ntdll32!RtlDosPathNameToNtPathName_U
………………
默认情况下只跟踪当前一条指令或一条 call 指令,除非手动指定其实和结束地址
https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapcreate
HeapCreate 可以通过指定 HEAP_CREATE_ENABLE_EXECUTE 来申请一片可以执行的堆块
看:
vbs 中,CreateObject 可以用来创建并得打一个对象实例的引用,而 GetObject 则是用来直接获得某一个资源的引用。
比如说要访问一个网页,可以通过
Set objIE = CreateObject("InternetExplorer.Application")
objIE.Visible = True
objIE.Navigate2 "
"
的方式通过 COM 来获得一个 IE 实例,然后继续导航到指定页面。
也可以直接通过 GetObject 的方式获得指定页面的引用:
Set objDOM = WScript.GetObject("
")
While objDOM.ReadyState "complete"
WScript.Sleep 50
Wend
或
Set CADObject = GetObject("C:\CAD\SCHEMA.CAD")
注意:
对系统内嵌的 wmi 资源的访问只能通过 GetObject 来获取,如通过 wmi 创建进程:
set objWMIService = getobject("winmgmts://./root/cimv2")
Set objProcess = objWMIService.Get("Win32_Process")
Set objProgram = objProcess.Methods_("Create").InParameters.SpawnInstance_
objProgram.CommandLine = "Calc.exe"
objWMIService.ExecMethod "Win32_Process", "Create", objProgram
windbg wt 命令似乎很好用,但试了一下没有成功
UAC 提权实际上是父进程向 svchost 中的 AppInfo 服务发送 RPC 请求,AppInfo 进行提权校验来完成的
更改系统执行 powershell 的策略为不受限,允许所有脚本执行,以管理员权限执行:
Set-ExecutionPolicy Unrestricted
KUSER_SHARED_DATA 数据:
32.0: kd:x86> dT _KUSER_SHARED_DATA 0x7ffe0000
ntdll_77c70000!_KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : 0
+0x004 TickCountMultiplier : 0xf99a027
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x8664
+0x02e ImageNumberHigh : 0x8664
+0x030 NtSystemRoot : [260] "C:\Windows"
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 LargePageMinimum : 0x200000
+0x248 Reserved2 : [7] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 6
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c4 AltArchitecturePad : [1] 0
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 1
+0x2dc DismountCount : 2
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0xee0ae27
+0x2e8 NumberOfPhysicalPages : 0xfff7e
+0x2ec SafeBootMode : 0 ''
+0x2ed TscQpcData : 0 ''
+0x2ed TscQpcEnabled : 0y0
+0x2ed TscQpcSpareFlag : 0y0
+0x2ed TscQpcShift : 0y000000 (0)
+0x2ee TscQpcPad : [2] ""
+0x2f0 SharedDataFlags : 0xe
+0x2f0 DbgErrorPortPresent : 0y0
+0x2f0 DbgElevationEnabled : 0y1
+0x2f0 DbgVirtEnabled : 0y1
+0x2f0 DbgInstallerDetectEnabled : 0y1
+0x2f0 DbgSystemDllRelocated : 0y0
+0x2f0 DbgDynProcessorEnabled : 0y0
+0x2f0 DbgSEHValidationEnabled : 0y0
+0x2f0 SpareBits : 0y0000000000000000000000000 (0)
+0x2f4 DataFlagsPad : [1] 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0
+0x304 SystemCallReturn : 0
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0xf42517
+0x320 ReservedTickCountOverlay : [3] 0xf42517
+0x32c TickCountPad : [1] 0
+0x330 Cookie : 0x8beeb3fe
+0x334 CookiePad : [1] 0
+0x338 ConsoleSessionForegroundProcessId : 0n2580
+0x340 Wow64SharedInformation : [16] 0x77ca9e49
+0x380 UserModeGlobalLogger : [16] 0
+0x3a0 ImageFileExecutionOptions : 0
+0x3a4 LangGenerationCount : 1
+0x3a8 Reserved5 : 0
+0x3b0 InterruptTimeBias : 0x0000023a`92baa4e5
+0x3b8 TscQpcBias : 0x0002c76e`f3ec00bb
+0x3c0 ActiveProcessorCount : 4
+0x3c4 ActiveGroupCount : 1
+0x3c6 Reserved4 : 0
+0x3c8 AitSamplingValue : 0
+0x3cc AppCompatFlag : 1
+0x3d0 SystemDllNativeRelocation : 0xffffffff`fec40000
+0x3d8 SystemDllWowRelocation : 0xf9e00000
+0x3dc XStatePad : [1] 0
+0x3e0 XState : _XSTATE_CONFIGURATION
32.0: kd:x86> dps 0x7ffe0340
00000000`7ffe0340 77ca9e49 ntdll_77c70000!LdrInitializeThunk
00000000`7ffe0344 77c80134 ntdll_77c70000!KiUserExceptionDispatcher
00000000`7ffe0348 77c80038 ntdll_77c70000!KiUserApcDispatcher
00000000`7ffe034c 77c800ec ntdll_77c70000!KiUserCallbackDispatcher
00000000`7ffe0350 77d0fbb4 ntdll_77c70000!LdrHotPatchRoutine
00000000`7ffe0354 77ca26b1 ntdll_77c70000!ExpInterlockedPopEntrySListFault
00000000`7ffe0358 77ca267b ntdll_77c70000!ExpInterlockedPopEntrySListResume
00000000`7ffe035c 77ca26b3 ntdll_77c70000!ExpInterlockedPopEntrySListEnd
00000000`7ffe0360 77c801c4 ntdll_77c70000!RtlUserThreadStart
00000000`7ffe0364 77d1356a ntdll_77c70000!RtlpQueryProcessDebugInformationRemote
00000000`7ffe0368 77cd14f1 ntdll_77c70000!EtwpNotificationThread
00000000`7ffe036c 77c70000 ntdll_77c70000!`string' (ntdll_77c70000+0x0)
00000000`7ffe0370 00000000
00000000`7ffe0374 00000000
00000000`7ffe0378 00000000
00000000`7ffe037c 00000000
00000000`7ffe0380 00000000
00000000`7ffe0384 00000000
00000000`7ffe0388 00000000
00000000`7ffe038c 00000000
https://github.com/tombkeeper/DEP-and-ASLR-bypass-without-ROP-or-JIT
tk教主在 CanSecWest 2013 会议上提出的利用 LdrHotPatchRoutine 来绕过 DEP\ASLR 的方法,关键点是在 x64 系统的 wow64 进程中,固定的地址 0x7ffe0000 处存放的是 KUSER_SHARED_DATA 数据,其 0x350 偏移处是固定的 LdrHotPatchRoutine 函数,这个函数内部会通过 LdrLoadDll 来加载一个 dll,而构造 LdrHotPatchRoutine 的参数非常简单。
因此在漏洞利用成功后,利用 LdrHotPatchRoutine 来加载一个远程 dll,在 dll 的 DllMain 中完成攻击即可。
看了一下金山的ROP防护方案专利文档,专利号 201410459638.1,虽说流程似乎有点问题,但有参考的地方:
1、更改内存属性是判断目标内存为线程栈空间,报攻击事件
2、加载dll判断UAC路径是不是远程的
通过系统提供的各种回调来调用 shellcode,比如:
1、CreateThreadPoolWait 提供的事件等待回调
2、CopyFile2 提供拷贝失败回调等
但前提是回调(也就是 shellcode)所在内存区是可执行的,理论上来说绕不过天狗。
https://github.com/S4R1N/AlternativeShellcodeExec
nmake 编译的 exe 或 dll 不会嵌入清单文件 manifest,清单文件的作用是为 exe 或 dll 指示正确的运行时库,一般发布版本都需要嵌入。可以在 nmake 生成后通过 mt 工具来讲 manifest 文件嵌入 exe 或 dll 中。
参考:
https://blog.csdn.net/clever101/article/details/42741277?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-4.no_search_link&spm=1001.2101.3001.4242.3
https://blog.csdn.net/yacper/article/details/5644372
一般的 makefile 都会 #include 文件,这里有相关的说明,makefile 不明白的地方可以从这里学习一下
https://gist.github.com/ynkdir/688e62f419e5374347bf
nmake 编译选择 release 模式则,nmake nodebug=1 即可
rundll32.exe 利用学习:
https://lolbas-project.github.io/lolbas/Binaries/Rundll32/
64 位程序下,地址最好用 64 位的,哪怕是一个 32 位数足以表示,但也要用 00000000`xxxxxxxx 来指示,不然直接用 xxxxxxxx 的话 windbg 有可能会转换为 ffffffff`xxxxxxxx 使用。如下示例:
1: kd> u fffeba30
ffffffff`fffeba30 ?? ???
^ Memory access error in 'u fffeba30'
1: kd> !address fffeba30
Usage:
Base Address: ffffffff`ffc00000
End Address: ffffffff`ffffffff
Region Size: 00000000`00400000
VA Type: HAL
1: kd> u 00000000`fffeba30
explorer!_chkstk+0x184:
00000000`fffeba30 4c896c2438 mov qword ptr [rsp+38h],r13
00000000`fffeba35 488d85f0010000 lea rax,[rbp+1F0h]
00000000`fffeba3c 4889442430 mov qword ptr [rsp+30h],rax
00000000`fffeba41 4533c9 xor r9d,r9d
00000000`fffeba44 44896c2428 mov dword ptr [rsp+28h],r13d
00000000`fffeba49 4533c0 xor r8d,r8d
00000000`fffeba4c ba1c000900 mov edx,9001Ch
00000000`fffeba51 4c896c2420 mov qword ptr [rsp+20h],r13
win7 64 syscall\sysret,通过 syscall 进入内核时,处理器在 syscall 指令内部,将用户态最后的 rip 赋值给 rcx 寄存器,然后通过 msr 寄存器读取到内核处理系统调用的入口为 KiSystemCall64,接着就转到这里执行。
在 KiSystemCall64 处,实际上 KiSystemCall64 和 KiSystemServiceStart、KiSystemServiceCopyStart、SystemServiceCopyEnd 以及 KiSystemServiceExit,都是连续这的,应该是 naked 函数里的不同 label。
在 KiSystemCall64 起始处会完成系统堆栈的转换,用户态 TRAP_FRAME 的保存等工作,然后在 KiSystemServiceStart 处开始通过 SSDT 或 SSDT shadow 表寻找系统调用地址,最后在 KiSystemServiceCopyEnd 处开始 call r10 调用系统调用。调用结束后直接进入 KiSystemServiceExit。
在 KiSystemServiceExit 中会检查线程 apc 列表,如果有可以投递的用户态 apc,则通过KiInitiateUserApc 来初始化一个用户态 apc 以及构造一个假的 TRAP_FRAME,接着在 KiSystemServiceExit 返回到用户态时,就会被欺骗去执行 apc,apc 执行完之后通过 NtContinue 又一次进入内核(NtContinue的第一个参数是一个 Context,保存的原先 KiSystemServiceExit 真正要返回的用户态地址), 在 NtContinue 的内核代码中会 jmp 到 KiSystemServiceExit ,然后继续检查线程的 apc 列表中还有没有可以投递的用户态 apc,以此形成了一个循环,直到用户态 apc 都执行完毕。
用户态 apc 都执行完毕之后,KiSystemServiceExit 继续往下走,进行恢复用户态堆栈等工作,在调用 sysret 返回的前夕,要返回的用户态地址并存放在 rcx 寄存器中。在 sysret 指令的处理过程中会进行 rip = rcx 的操作。至此,系统便返回到用户态了。
对 TRAP_FRAME 和 context 的理解,TRAP_FRAME 用来保存一个现场,比如用户态进入内核态的现场,发生异常时的现场(以便异常能够继续执行时能正确继续执行)等,而 context 是某些寄存器数据的一个保存的作用,比如可以将 TRAP_FRAME 中的某些数据保存在 context 中,也可以将 context 中的数据用于恢复或重置一个 TRAP_FRAME。
注意,在另一条时光机中有说到,不能直接在 KiSystemServiceExit 的第一条指令出下断,因为这是一条 swapgs 指令,在 sysret 前也紧挨着一条这样的指令。swapgs 是 swap gs base register 的缩写,意思就是交换用户态和内核态的 gs 寄存器。该指令的伪代码如下:
tmp ← GS.base;
GS.base ← IA32_KERNEL_GS_BASE;
IA32_KERNEL_GS_BASE ← tmp;
执行该指令后,gs 寄存器指向了内核态的 gs base,内核态的地址便能正确访问了,否则 gs 还是执行用户态的 gs base,此时任何内核态的地址都是无法访问的。因此不能在这一条特权指令上下断,因为 windbg 调试时也要依赖内核态的地址,此时还没切换过来,直接下断会导致调试器访问内核态地址,继而导致蓝屏。方法是,在 swapgs 指令的下一条指令处下断。
上面评论写错了,nt!Zw*** 是通过 nt!KiSystemService 跳入(非call)nt!KiFastCallEntry。
在内核调用 nt!Zw*** 进入的 nt!KiSystemService 在刚开始处把 KTHREAD.PreviousMode = 0(KernelMode);
而从环 3 下来进入的 nt!KiFastCallEntry 在刚开始处把 KTHREAD.PreviousMode = 1(UserMode)。
上述有这样的区别是因为,nt!Nt**** 函数内部会检查 KTHREAD.PreviousMode,如果是 KernelMode 则不会对参数进行校验,默认他们都是安全的,而如果是 UserMode 则会进行校验,具体为确保参数地址都处于用户态空间而且正确对齐。
因此,在内核编码时:
1、如果能确保当前 KTHREAD.PreviousMode = KernelMode,则可以直接调用 nt!Nt****,可以提高效率;
2、如果不能确保,则最好使用 nt!Zw****,因此该函数会设置 KernelMode,因此可以免去参数校验,无论当前是 UserMode 还是 KernelMode 都会成功
3、否则如果当前 PreviousMode = UserMode,而又直接调用了 nt!Nt***,则因为传参都是内核地址,此时无法通过参数校验,调用就失败了
SSDT 中记录的是 nt!Nt*** 的地址,不是 nt!Zw*** 的地址。因此环3通过 ntdll 调系统调用,是通过 SSDT 直接调的 nt!Nt***,而非 nt!Zw***。
nt!Zw*** 实际上也只是一个简单的包装函数,通过 nt!KiSystemService 跳入(非call) nt!KiFastCallEntry,后续通过 SSDT 表执行 nt!Nt*****。
win7 64 系统调用 syscall/sysret 分析:
https://www.cnblogs.com/DreamoneOnly/p/11819333.html
注意,不能下断点在 nt!KiSystemCall64,而是要下载 nt!KiSystemCall64 的第二条指令,因为第一条是 swapgs,涉及到模式的转换可能?反正下载这里会蓝屏,要跳过这一句来设置
rpc编程介绍:https://blog.csdn.net/xxxluozhen/article/details/5605818
及微软官方示例:
https://blog.csdn.net/xxxluozhen/article/details/5605818
可以通过 dpc 线程来抢占 cpu,以完成内核的 hook 操作。可以强制指定某个 dpc 线程运行在指定的 cpu 核心上
dpc watchdog 超时 导致蓝屏分析:
https://blog.csdn.net/xiangbaohui/article/details/104849967
https://www.cnblogs.com/cposture/p/SpinLock.html
自旋锁,其中说到在 IRQL = DPC_LEVEL 下,当前线程不会再被切换出去了:
#define KeAcquireSpinLock(SpinLock, OldIrql) \
*(OldIrql) = KeAcquireSpinLockRaiseToDpc(SpinLock)
很明显,核心的操作对象是SpinLock,同时也与IRQL有关 。
如果当前的IRQL为PASSIVEL_LEVEL,那么首先会提升IRQL到DISPATCH_LEVEL,然后调用KxAcquireSpinLock()。
如果当前的IRQL为DISPATCH_LEVEL,那么就调用KeAcquireSpinLockAtDpcLevel,省去提升IRQL一步。
因为线程调度也是发生在DISPATCH_LEVEL,所以提升IRQL之后当前处理器上就不会发生线程切换。单处理器时,当前只能有一个线程被执行,而这个线程提升IRQL至DISPATCH_LEVEL之后又不会因为调度被切换出去,自然也可以实现我们想要的互斥“效果”,其实只操作IRQL即可,无需SpinLock。实际上单核系统的内核文件ntosknl.exe中导出的有关SpinLock的函数都只有一句话,就是return。
wrk 介绍:https://www.cnblogs.com/boyxiao/archive/2011/01/08/1930904.html
有空研究一下 antispy 的开源代码:
https://bbs.pediy.com/thread-255535.htm
证书是CA用自己的私钥对申请方的信息(颁发者、申请者,有效日期、申请方公钥)等的hash进行摘要运算,将摘要和申请方信息的明文放一起就形成了证书。CA的公钥是作为守信任的根证书嵌入到系统或者浏览器中的,根证书是CA自签名的
windows 内核 uaf 基础学习文章:
https://blog.csdn.net/qq_38025365/article/details/106242187
内核提权最常用的手法,就是把 system 的 token 赋给一个普通进程就行了,有一篇文章写得很好:http://www.sinkland.cn/?p=220
指向 token 的指针,存放在 EPROCESS 的 token 字段中,因为 system 的 EPROCESS 生命周期和系统一样长,所以不用担心这个指针会被释放的问题,但如果要提升权限到其它进程就得考虑指针会被释放的问题。
直接找到 system 进程的 EPROCESS 的 token 值,将它覆盖到提权目标进程的 EPROCESS 的 token 值处,就可以完成提权操作了。然后由于子进程会继承父进程权限,所以假设 explorer 现在提权成功有了 system 权限,那么后续 explorer 创建的一切子进程都是 system 权限的了。
在内核中找到 system 进程的 EPROCESS 的方法,可以首先通过 gs:[0] 得到 kpcr 结构,进而得到 kpcr->pcrb 处理器控制块结构,然后通过 pcrb->CurrentThread 拿到当前进程的 ETHREAD,然后通过 ETHREAD->ApcState.Process 拿到当前进程的 EPROCESS,然后通过 EPROCESS->UniqueProcessId 与 system_pid=4 做比较,不符合就通过 EPROCESS-> ActiveProcessLink.Flink 得到下一个进程的 EPROCESS.ActiveProcessLink.Flink,继续通过 UniqueProcessId 进行比较即可
pg 原理
windbg !exchain 查看当前线程的异常处理链
内存泄露,除了 verifier,还可以使用 gflags 的特殊池跟踪,对指定的 tag 进行跟踪
.thread /w ETHREAD
可以切换至 wow64 线程空间,查看栈
之后可以 .effmach . 切回64位环境
windbg 查找栈两个招式
!stacks 2 filterstring 查找所有包含指定过滤字符串的内核栈
!running it 显示所有正在运行的栈
windbg 中 !stacks 2 qkentdatp 可以用来搜索所有包含指定符号 qkentdatp 的栈。注意是所有进程的内核栈。对应的用户栈搜索命令是 !uext.findstack
windbg 的 !chkimg 可以用来检查内存映像损坏,方法是通过检查内存数据和符号文件中的数据来做检查,最好是 full dump
ObReferenceObjectByHandle 的分析
https://www.cnblogs.com/lsh123/p/8331707.html
其中说到,当前进程的句柄值永远都是 -1,当前线程的句柄值永远都是 -2,这两个是 wdm.h 中定义好的
fs 寄存器指向 teb
teb+0x30 指向 peb(0x30这个偏移有可能变化)
也就是 fs:30 指向 peb
teb 起始处是个 NT_TIB 结构,包含了用户态栈 StackBase、StackLimit 等信息
利用 peb 里的 _PEB_LDR_DATA 可以得到当前进程加载的所有 dll
peb 里的 _RTL_USER_PROCESS_PARAMETERS 中记录了进程路径和命令行
在内核中,_PEB、_TEB、EPROCESS、ETHREAD 都是不透明的数据结构,虽然有大量的逆向数据,但要收集全才能放心用,因为这些数据在不同的平台上的字段、偏移都会发生改变。EPROCESS 起始处是 KPROCESS,即 pcb;ETHREAD 起始处是 KTHREAD,即 kcb。
WDK 道出了 EPROCESS 和 ETHREAD 结构,但实际上不是透明的,可以用它来定义变量,但看不到具体的字段。要想用,还是得自己定义结构体。
通过 PsGetCurrentProcess() 可以得到当前的 EPROCESS,EPROCESS 中有 peb 字段,可以拿到 peb,进而可以拿到进程的路径和命令行。还能拿到 UniqueProcessId、ImageFileName 等基础数据,但这里的 ImageFileName 只保存前 15 个字节,所以是不完整的进程名,要注意。
通过 EPROCESS 起始处的 KPROCESS,其中有字段 ThreadListHead,是一个双向链表,可以得到进程的每一个 ETHREAD。以及字段 ProcessListEntry,可以遍历系统中每一个 EPROCESS
ETHREAD 可以通过 PsGetCurrentThread 得到,同样可以声明 ETHREAD 变量,但是用不了,只能是用来给其它的内核函数传参,除非自己定义,但要针对具体的系统平台收集数据。
ETHREAD 中有 teb 字段,通过 teb 可以得到 peb。ETHREAD 起始处的 KTHREAD 中有 Process 字段,可以拿到 EPROCESS。
在驱动中,获得 ETHREAD,就可以顺带获得 EPROCESS、teb、peb了。获取 ETHREAD 的方法,要么通过 PsGetCurrentThread 直接获取,但是需要自己定义和收集 ETHREAD 结构体。要么通过 ZwQueryInformationThread 获取,其中 tbi 是 THREAD_BASIC_INFORMATION 结构,同样是未公开但有逆向了的。
NTSTATUS Status = s_fnZwQueryInformationThread(NtCurrentThread(), ThreadBasicInformation, &tbi, sizeof(THREAD_BASIC_INFORMATION), NULL);
stl map 的一些底层数据结构解析
https://blog.csdn.net/messiran10/article/details/52072487
1: kd> dt _KTHREAD ffff880b5e8ed080 -y Cet
ntdll!_KTHREAD
+0x074 CetUserShadowStack : 0y0
挂 verifier 蓝屏报 C4 62 是 pool 泄露
有空了解一下 CET。用来防护 ROP 的
https://windows-internals.com/cet-on-windows/
计算机\HKEY_CLASSES_ROOT\Interface
查找接口
解析因内核栈溢出导致的 “double fault” 蓝屏 ------
https://www.cnblogs.com/flying-shark/p/8454533.html
dump 文件卡死分析
1、怀疑是自己的驱动,用 !stack 命令看看所有线程栈里面有自己驱动的部分,有无可疑的地方
2、因为是卡死,所以 explore.exe 的 0 号线程(界面线程)有很大嫌疑,要仔细看
3、用 !running 状态看看当前 cpu 在哪些线程上面运行,有无可疑点
The first WinDBG command you will want to run is: !runaway.
This command will show you which thread was using the CPU for the longest time
!stacks 查找包含自定符号的栈,要先 .load uext.dll
手动触发蓝屏
1、windbg 内核调试时,可以通过 .crash 命令触发蓝屏
2、或者在注册表中键盘相关服务项添加一个键值,重启电脑后,按 ctrl+scroll lock 即可触发蓝屏,实际上是键盘相关的驱动主动去产生的蓝屏
C C++ 中初始化结构体,在定义时初始化,如
DataInfo info = { 0 };
如果 info 里面发生对齐,即存在一些空隙字节,非正常字段的,那么编译器不一定会把这些空隙字节也给初始化为 0,而只是把字段给初始化为 0
https://bbs.pediy.com/thread-226625.htm
[翻译]DEP缓解技术(一)
a1->lpVtbl->InvokeEx
栈回溯时遇到 jscript 或 vbscript,反汇编后是如此的 InvokeEx 或 Invoke 调用时,实际上这是 IDisPatchEx 或 IDisPatch 接口在获取或设置属性,或调用方法,具体看第三个参数:
#define DISPATCH_METHOD 0x1
#define DISPATCH_PROPERTYGET 0x2
#define DISPATCH_PROPERTYPUT 0x4
#define DISPATCH_PROPERTYPUTREF 0x8
参考
https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/reference/idispatchex-invokeex
https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/windows-scripting/reference/idispatchex-interface
COleSite::IsSafeToScript 有点看头
https://wooyun.js.org/drops/IE%E5%AE%89%E5%85%A8%E7%B3%BB%E5%88%97%EF%BC%9A%E8%84%9A%E6%9C%AC%E5%85%88%E9%94%8B%EF%BC%88I%EF%BC%89.html
在 windbg 中查看线程时,可以看到线程当前的不同状态,如:
THREAD fffffa8005642b60 Cid 01b4.0ae8 Teb: 000000007ef97000 Win32Thread: fffff900c2a13c30 WAIT: (WrUserRequest) UserMode Non-Alertable
其中 WAIT 说明当前线程处在等待状态,而 WrUserRequest 则是具体的等待类型,这里可以有不同的状态。如:
WrUserRequest、UserRequest、WrQueue 等。
这几个的含义和不同,可以看这个帖子:
https://stackoverflow.com/questions/48009277/thread-wait-reasons
简单说,一般 UserRequest 就是线程在通过 WaitForSingleObject 等待对象
WrUserRequest 一般是线程在通过 GetMessage 等待窗口消息
mona:https://github.com/corelan/mona
windbg 的mona插件:https://github.com/corelan/windbglib
mona 可以用来快速的搜索并生成 roop chain
基于 com 通用对象模型开发的 ActiveX 插件可以是 dll 或 ocx 后缀,都是 pe 格式文件,但是只能被调用,不能执行。ocx 插件是只有 IE 能使用的(好像也不是,网上有一些文章说 chrome 等也能用,不过方法稍稍不同),看文章 https://www.cnblogs.com/qguohog/archive/2013/01/22/2871805.html
ActiveX 插件可以通过 regsvr32 命令手动注册,如
严重性 代码 说明 项目 文件 行 禁止显示状态
警告 MSB3075 命令regsvr32 /s "F:\repos17\MFCActiveXControl1\Debug\MFCActiveXControl1.ocx"
或者 cab 包方式自动注册。最终都是在注册表中和其它 com 对象一样记录起来,有 CLSID 和 ProgID https://www.cnblogs.com/qguohog/archive/2013/01/24/2875524.html
在 IE 中使用 ActiveX 插件,可以通过静态和动态两种方式调用,静态的是在 html 中通过 object 标签来定义,动态的是通过 js 的 new ActiveXObject 方法来创建实例 https://blog.csdn.net/mowwwcom/article/details/45970055
用 AMD 处理器的第一次不方便,就是以前的虚拟机都用不了了。以前的虚拟机都是基于 intel 处理器的指令集的,迁移不来 AMD 处理器
KeRegisterBugCheckCallback 注册一个回调,在系统发生蓝屏时被调用
64 位系统中的 wow64 进程会加载两个 ntdll,有两个 peb,分别对应 system32 和 wow64 ,因为内核时 64 位的,wow64 进程进入 64 位内核,必须由 64 位 ntdll 进入,必须准备对应的 64 位 peb 结构
C C++ 运算符优先级,& 的优先级没有 + 高。以前一直以为 & 比 + 高,惨
原来还可以通过 lib 的方式来使用 ntdll 中的未文档化函数,ntdll.lib 在 DDK 中有提供
c++ 中的 nullptr 才是空指针,而 NULL 实际上是 #define NULL 0。而 C 中 NULL 实际上是 #define NULL (void*)0,是一个指针。
详细看:
https://blog.csdn.net/reasonyuanrobot/article/details/100022574
Windows安全描述符 SD SECURITY_DESCRIPTOR
https://blog.csdn.net/eggfly178/article/details/41773601
STATUS 是一个 LONG 有符号长整数,NT_SUCCESS() 宏定义是 status >= 0 即成功,而不是 status == 0。因为微软对 STATUS 的定义是通过最高两位来表达不同的含义,具体看 https://blog.csdn.net/ZhengZhiRen/article/details/5781515
PAGE_GUARD 内存属性是一次性事件,可以利用这个特性在异常处理例程中做一些特别处理
简单的 VEH 实现
LONG veh_handler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
PEXCEPTION_RECORD exr = ExceptionInfo->ExceptionRecord;
LONG lRet = EXCEPTION_CONTINUE_SEARCH;
if (exr->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION)
{
printf("Exception occured -> STATUS_GUARD_PAGE_VIOLATION\n");
return EXCEPTION_CONTINUE_EXECUTION;
}
return lRet;
}
######################
if (NULL == AddVectoredExceptionHandler(1, veh_handler))
{
printf("AddVectoredExceptionHandler err\n");
return 1;
}
写驱动一定要注意异常处理,健壮性非常重要。C++特性少用为好,比如C++异常不能捕捉,析构函数不一定被调用。
代码实现时要考虑项目代码风格的问题,最好能够做到一致。还要考虑新特性会不会对其他同事不友好。
verifier 会将 LookasideList 的 Depth 设置为 0,即关闭了 LookasideList 的缓存能力,都是直接向系统申请直接返回给系统
今天接触了 OverLapped IO 技术,以及完成端口
一定要现在 github 创建了远程仓库,才可以将本地的新仓库推到远端
git remote add origin git@github.com:plu-s/ReposName
git push -u origin master
pdb 可以用 VS 自带的 Dia2dump 项目,它使用的是微软提供的 DIA SDK,就是用来解析 pdb 文件的。
回归测试是在软件维护阶段,对软件进行修改之后进行的测试。其目的是检验对软件进行的修改是否正确。这里,修改的正确性有两重含义:一是所作的修改达到了预定目的,如错误得到改正,能够适应新的运行环境等等;二是不影响软件的其他功能的正确性。
1,类的成员静态变量在进入main之前已被初始化
2,函数内部的局部静态变量在该函数第一次被调用时初始化,只初始化一次
const_cast 可以将一个 const 变量的 const 属性去掉,如:
(const_cast(pExtInfo->pInfo))->clear();
其中 pExtInfo->pInfo 定义是一个 const 指针,如果不使用 const_cast 转换,编译是无法通过的。
因为需要开发者明确使用 const_cast 转换,所以 const 值可能发生改变而导致的隐患,开发者已完全知悉,后果也完全由开发者承担。
C++11标准前的静态局部变量初始化过程是不线程安全的,在 VS 的具体实现中,判断一个静态局部变量是否已经初始化,是通过一个DWORD值判断的,为 1 则已初始化,为 0 则未初始化。这个DWORD值本身就是一个关键资源,但是对这个DWORD值的判断过程并没有做到线程安全,导致有多次初始化的可能。
头文件互相包含导致出现错误,如变量未定义等,可以在 VS 命令提示窗口中,通过手动 CL.exe 编译拿到预处理文件来分析,如:
CL.exe StackMatch.cpp /P /I "C:\WinDDK\7600.16385.1\inc\api" /I "C:\WinDDK\7600.16385.1\inc\crt" /D "_AMD64_" /D "_WIN32_WINNT=0x0501" /D "NTDDI_VERSION=0x05010200" /D "NT_KERNEL_MODE" /D "DDK_VER=6000" /D "_STLP_THROW_BAD_ALLOC" /D "PERFORMANCE_COLLECT_ENABLE=0"
上述三个开关:
/P 指示只做宏展开等预处理动作并产出 StackMatch.i 文件
/I 指定需要的包含文件路径
/D 指定编译架构等选项
C++ 类的前向声明:https://blog.csdn.net/yyyxxxzzz111/article/details/39996695
个人备注:结构体也可以用
可以声明一个类而不定义它
class Screen;//declaration of the Screen class
这个声明,有时候被称为前向声明(forward declaration),在程序中引入了类类型的Screen.在声明之后,定义之前,类Screen是一个不完全类型(incompete type),即已知Screen是一个类型,但不知道包含哪些成员.
不完全类型只能以有限方式使用,不能定义该类型的对象,不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数.
头文件重复包含导致的编译链接错误分析,来自博客 https://blog.csdn.net/u013321328/article/details/53229681
问题重现
我把问题脱离于项目简单描述一下:我写了一个函数 bool func(ClassA* CA) 需要加到项目中,我就把这个函数的声明放到 head1.h 中,函数参数类型 ClassA 定义在另一个头文件 head2.h 中,因此我需要在 head1.h 中包含 head2.h;而 head2.h 中之前又包含了 head1.h,这样就构成了一种头文件相互包含的场景。再加上一些其它的声明与定义,就构成了这样的一个文件结构:
head1.h
#ifndef __HEAD_1_H__
#define __HEAD_1_H__
#include "head2.h"
#define VAR_MACRO 1 //define a macro, which used in head2.h
bool func(ClassA* CA); //ClassA is defined in head2.h
#endif
head2.h
#ifndef __HEAD_2_H__
#define __HEAD_2_H__
#include "head1.h"
class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h
... //other members and functions
};
#endif
那么,现在另有两个源文件
source1.cpp
#include "head1.h"
//... some source code
source2.cpp
#include "head2.h"
//... some source code
整个项目会分别编译这两个源文件,编译完之后会报错,大致意思是 ClassA 和 VAR_MACRO 没有定义,那么问题就比较奇怪了,每个头文件都分别引用了另一个头文件,为什么会出现未定义呢?
问题分析
我们都知道 C/C++ 中头文件开始习惯使用 #ifndef ... #define ... #endif 这样的一组预处理标识符来防止重复包含,例如上面的问题中我也使用了,如果不使用的话,两个头文件相互包含,就出现递归包含。这个很好理解就不多叙。
回到问题本身,我在微博上贴出了这个问题,有人说在 head1.h 的函数前加上 ClassA A; 的前置声明,至于为什么要加,估计很多人都没理解...
对于 source1.cpp,它包含了头文件 head1.h,那么在编译之前,在 source1.cpp 中展开 head1.h,而 head1.h 又包含了 head2.h, 那么也展开它,这时 source1.cpp 就变成类似下面这样:
class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h
... //other members and functions
};
#define VAR_MACRO 1 //define a macro, which used in head2.h
bool func(ClassA* CA); //ClassA is defined in head2.h
//... source1.cpp source code
看到没,这地方 func 函数之前有 ClassA 类型的定义,根本没必要像有些人说的那样加上 ClassA CA; 这样的前置声明。
我们再展开 source2.cpp 看看:
#define VAR_MACRO 1 //define a macro, which used in head2.h
bool func(ClassA* CA); //ClassA is defined in head2.h
class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h
... //other members and functions
};
//... source2.cpp source code
这时问题就很清楚了,func 函数声明之前并没有发现 ClassA 类型定义,该定义在函数声明的后面,这时候如果能在head1.h 的函数声明之前加上 ClassA CA; 的前置声明,就不会在编译的时候报找不到 ClassA 的定义的错误了。
再回到 source1.cpp 展开的源码看看,是不是一下子明白了为什么报找不到 VAR_MACRO 的定义的错误了?修改方法也简单,把宏定义拉到 #include "head2.h" 语句之前就 OK 了。
QT 信号槽的 connect 写法
QPushButton *btn = new QPushButton;
// 方式一:老式写法
connect(btn, SIGNAL(clicked()), this, SLOT(close()));
// 方式二:Qt5后新写法
connect(btn, &QPushButton::clicked, this, &MainWindow::close);
// 方式三:lambda表达式
connect(btn, &QPushButton::clicked, this, [&]() {
this->close();
});
漫谈兼容内核之十二:Windows的APC机制
http://blog.chinaunix.net/uid-22683402-id-1771348.html
测试绑定域名
Windows 无文件攻击总结
http://t3ngyu.leanote.com/post/Windows-noFile
发现这个是因为tg无法拦截搭配chrome漏洞利用的 dllregsvr32 DllInstall 利用方法
手动添加带图片的时光机
测试带图片的时光机
来自公司电脑的测试
LPC是“本地过程调用(Local Procedure Call)”的缩写。所谓“本地过程调用”是与“远程过程调用”即RPC相对而言的。其实RPC是广义的,RPC可以发生在不同的主机之间,也可以发生在同一台主机上,发生在同一台主机上就是LPC
深夜的一条测试信息