git推送命令记录

刚刚搭好博客,记录一下推送到git仓库的命令,防止忘记

1
2
3
4
5
6
git init //初始化
git add . //添加所有文件到暂存区
git commit -m '' //添加commit
git remote add origin 原创仓库地址 //关联原创仓库
git pull --rebase origin master
git push -u origin master //推送本地仓库

Thinkphp5.0.24 反序列化链学习

这条链是某师傅在先知对一个Tp5.0.24开发的CMS审计时发现的一个触发点的利用,本来该利用点只能完成SSRF与任意文件删除的,为了提升危害,于是又从TP5.0.24中挖掘出了这一条通过写入文件Getshell的的Pop链(师傅们实在tql,枯了)

关于这条链先知上已经有几篇分析过了,了解了几点:

1.当目标目录无写入权限时我们可以创建一个权限为0755的文件夹,然后写shell

2.使用php伪协议写入shell绕过死亡exit,因为文件名不可控所以Windows下无法利用(后面可以知道原因)

0x01

学习开始,首先开头还是Tp5.1.x中的那条链,具体看我的PaperBook仓库中有详细分析的过程,然后到removeFiles这个方法文件名传入对象触发__toString魔术方法然后到thinkphp/library/think/Model.php 853行的toArray方法开始,原来在5.1.x是利用访问某类中不存在的方法触发__call魔术方法,但是在5.0.x中hook变为静态属性(关于静态属性为何不能利用可以看php相关知识),所以得重新寻找可以利用的地方触发__call魔术方法,先看toArray这个方法在什么位置触发的__call,又是如何触发的 因为图片Base64显示太大了,所以直接代码吧 XD.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();//这一处
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append([$attr])->toArray();
} else {
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);

if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;//另?前的条件为真执行 $value->getAttr
}
}
continue;
}
}
$item[$name] = $value;
} else {
$item[$name] = $this->getAttr($name);
}
}
}
}

看代码,首先append变量我们可控,然后前两个分支第一个是判断$name是否为数组,第二个是判断$name中是否存在".",前两个分支显然是不满足我们的需求的,于是直接到else中,我们看看else的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);

if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;//另?前的条件为真执行 $value->getAttr
}
}
continue;
}
}
$item[$name] = $value;
} else {
$item[$name] = $this->getAttr($name);
}

$relation = Loader::parseName($name, 1, false)追踪了一下意义不大,我们直接看满足条件后的赋值

1
2
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);

我们要使$relation为我们当前类存在的方法(method_exists函数),且方法中返回的值我们可控,这里就找到了1608行的getError方法

1
2
3
4
public function getError()
{
return $this->error;
}

其中error可控,代表$modelRelation可控,$value = $this->getRelationData($modelRelation)

$modelRelation经过getRelationData方法处理后赋值给了$value,我们跟踪一下getRelationData方法,看看他是如何处理$modelRelation,跟踪到639行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected function getRelationData(Relation $modelRelation)
{
if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
$value = $this->parent;
} else {
// 首先获取关联数据
if (method_exists($modelRelation, 'getRelation')) {
$value = $modelRelation->getRelation();
} else {
throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation');
}
}
return $value;
}

可以看到传入类型需为Relation,需要满足$this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)这样$value就能返回我们控制的值,其中$value设置的类中需要有getBindAttr方法,才能满足条件进入,在if判断中的3个条件,我们寻找满足条件的类,可以找到HasOne类满足我们的条件,类型为Relation,同时isSelfRelation(),$this->parent,get_class($modelRelation->getModel()) == get_class($this->parent),这3个条件中的值我们都可控,可以满足if的判断,但是可能我们会发现该类中并不能找到getBindAttr方法,无法通过method_exists的判断,但是看师傅的文章确实是使用该类,于是留心一下注意到了该类第一行的代码class HasOne extends OneToOne,继承自OneToOne类,跟进OneToOne,搜索getBindAttr方法,在222行找到该方法,其中bindAttr值可控

1
2
3
4
5
    //thinkphp/library/think/model/relation/OneToOne.php Line:222
public function getBindAttr()
{
return $this->bindAttr;
}

到了我们触发__call魔术方法的关键之处

1
2
3
4
5
6
7
8
9
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}

$bindAttr中的值是从OneToOne中的getBindAttr方法获取的,其中的值我们知道我们可控,$attr可控,$value可控我们就可以触发__call方法,寻找符合条件的类找到thinkphp/library/think/console/Output.php类208行中的__call方法,其中会调用该类中的block方法,需满足in_array($method, $this->styles)因为styles的值我们可控,设置为包含getAttr的数组即可

1
2
3
4
5
6
7
8
9
10
11
12
13
public function __call($method, $args)
{
if (in_array($method, $this->styles)) {
array_unshift($args, $method);
return call_user_func_array([$this, 'block'], $args);
}

if ($this->handle && method_exists($this->handle, $method)) {
return call_user_func_array([$this->handle, $method], $args);
} else {
throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
}
}

所以成功进入return call_user_func_array([$this, 'block'], $args);,array_unshift这处在5.1的链中已经提过,在这条链中无太大影响故不提,我们继续追踪block方法,在Output类的122行

1
2
3
4
protected function block($style, $message)
{
$this->writeln("<{$style}>{$message}</$style>");
}

调用了writeln方法,我们跟踪writeln方法,在141行

1
2
3
4
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $type);
}

随后又调用了write方法,在152行

1
2
3
4
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->handle->write($messages, $newline, $type);
}

其中handle可控,也就是说我们能访问任意带有write方法的类,全局查找write方法(一般在继承File类的类中)

找到thinkphp/library/think/session/driver/Memcached.php(发现thinkphp/library/think/session/driver/Memcache.php文件也实现了相同功能,两者应该都能使用) 92行处

1
2
3
4
public function write($sessID, $sessData)
{
return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']);
}

这里调用了set方法,handler还是一样可控,我们可以访问带有set方法类,在文件thinkphp/library/think/cache/driver/File.php 141行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}

这里获取文件名是通过getCacheKey方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected function getCacheKey($name, $auto = false)
{
$name = md5($name);
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DS . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DS . $name;
}
$filename = $this->options['path'] . $name . '.php';
$dir = dirname($filename);

if ($auto && !is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $filename;
}

这里需将可控数组options中的值cache_subdir与prefix设置一下,$filename是从options数组取path的值与传入的$name进行拼接为文件名,然后通过dirname函数返回目录,并判断目录是否存在,不存在则调用mkdir函数创建并返回$filename,这里文件名部分已经弄清楚了,我们来看要写入的数据,$value通过序列化后赋值给了$data变量,然后进行数据压缩,这一步我们可以设置options数组中的data_compress值来跳过这一步骤,随后在数据拼接的时候又加入了死亡exit,这时候可以利用php中伪协议来处理数据绕过,然后就到了file_put_contents函数中,但是$data我们从前面跟踪下来发现是无法控制的(前面默认传入的参数为true),我们继续看代码,当执行完file_put_contents函数后将返回结果赋值给$result然后执行了isset($first) && $this->setTagItem($filename),我们看setTagItem方法做了什么,首先$filename传入了该方法即$name变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function setTagItem($name)
{
if ($this->tag) {
$key = 'tag_' . md5($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value = explode(',', $this->get($key));
$value[] = $name;
$value = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0);
}
}

此时tag变量可控,$key的值我们就可以知道,然后$name($filename)又赋值给$value,最后将$key与$value继续传入set方法(此时写入的内容可控即第一次写入的文件名),文件名为$key即tag_md5(tag)->getCacheKey()->文件名:(path+md5(tag_md5(tag))+.php),将path中的值设置为带有php代码的值并进行处理绕过死亡exit即可实现写入webshell也正是因为文件名中path的值为php代码导致该链只能在linux下使用

0x02

流程大概是

  1. 第一次写入文件,文件内容不可控,然后进入setTagItem方法第二次写文件

  2. 第二次写文件,文件内容为第一次序列化后的文件名其中包含rot13的字符串和php死亡exit的代码,文件名为options[“path”]+md5(tag_md5(tag))+.php

  3. 但是经过php://filter/write=string.rot13/resource=<?cuc cucvasb();?>+文件名将写入文件的exit代码rot13编码 而 前面文件名已经rot13编码过的字符则还原成php代码自此成功写入webshell且文件名我们可以计算得出

具体的Exp在师傅们的Blog中已经给出,就不写出来了,因为最近重新装了系统,linux环境没换上就没有经典的弹calc图了,求原谅 :)

师傅们tql Orz

0x03 补充

补充:在先知中已经看到有师傅提供了在Windows下利用的解决方法,这里补充一下

之前对Thinkphp5.0.24中存在的反序列化利用链进行了分析,说到因为写入方式的问题导致在Windows环境下不可用的问题,后面在先知看到某师傅的一篇文章,发现还是有办法在Windows环境下控制文件名进行写入,文章地址:关于 ThinkPHP5.0 反序列化链的扩展

重新跟踪

thinkphp/library/think/session/driver/Memcache.php文件的92行处进行了文件写入

1
2
3
4
public function write($sessID, $sessData)
{
return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']);
}

在这里我们可以看到session_name是通过config获取的,而config数组又可控,所以可以通过控制session_name的值为我们的php代码即可,至于与WIndows文件写入有啥关系,我们继续向下跟thinkphp/library/think/cache/driver/File.php 文件 141

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public function set($name, $value, $expire = null)
{
if (is_null($expire)) {
$expire = $this->options['expire'];
}
if ($expire instanceof \DateTime) {
$expire = $expire->getTimestamp() - time();
}
$filename = $this->getCacheKey($name, true);
if ($this->tag && !is_file($filename)) {
$first = true;
}
$data = serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
isset($first) && $this->setTagItem($filename);
clearstatcache();
return true;
} else {
return false;
}
}

前面写的文章中说明了为啥只能在Linux环境下利用的原因,因为在getCacheKey方法中,options数组中的path值已经固定,且文件内容不可控导致无法getshell,于是找到了setTagItem方法,该方法中进行了二次文件写入,写入的内容就是第一次写入的文件名,然而options数组中的path的值一直没有改变(文件名开头一直为rao13编码后的php代码)所以导致虽然能控制文件写入的内容,但文件名中包含了特殊字符,导致不能在windows环境下利用成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected function getCacheKey($name, $auto = false)
{
$name = md5($name);
if ($this->options['cache_subdir']) {
// 使用子目录
$name = substr($name, 0, 2) . DS . substr($name, 2);
}
if ($this->options['prefix']) {
$name = $this->options['prefix'] . DS . $name;
}
$filename = $this->options['path'] . $name . '.php';
$dir = dirname($filename);

if ($auto && !is_dir($dir)) {
mkdir($dir, 0755, true);
}
return $filename;
}

但是看了师傅的方法是,刚刚开头的session_name处这里可以控制第一次写入的文件名,而options数组中的path值就不需要加入我们的rao13编码后的php代码,能实现控制第一次写入的文件名,不影响第二次写入的文件名,从而在Windows环境下的利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function setTagItem($name)
{
if ($this->tag) {
$key = 'tag_' . md5($this->tag);
$this->tag = null;
if ($this->has($key)) {
$value = explode(',', $this->get($key));
$value[] = $name;
$value = implode(',', array_unique($value));
} else {
$value = $name;
}
$this->set($key, $value, 0);
}
}

大概的过程

第一次进入set方法(session_name+$sessid)然后通过getCacheKey方法返回文件名 options数组path的值为php://filter/write=string.rot13/resource=即可,此时文件名中含有我们开头设置session_name的值为rot13编码后的php代码,然后进入第二次写文件也就是setTagItem方法,进行第二次写文件操作,进入getCacheKey,此时options数组中的path值已经正常,返回的文件名为md5('tag_' +md5($this->tag)).php,到了file_put_contents函数$filename为php://filter/write=string.rot13/resource=md5('tag_' +md5($this->tag)).php

这里文件名可控,写入的值也可控,就能成功在Windows下利用


CVE-2020-2551复现

影响范围:10.3.6.0.0,12.1.3.0.0,12.2.1.4.0,12.2.1.3.0 具体可以看宇师傅的博客,https://www.r4v3zn.com/posts/b64d9185/#more 写的非常详细,各种问题也已经说明,网上也已经有具体的Poc了,直接拷贝下来让后把相关的包添加即可,可以稍微修改一下代码自定义

Tips:这里会有一个坑就是包要与要打的目标相同,不然会出现Mismatched serialization UIDs错误,解决方法就从安装的Weblogic中复制到包的目录下即可

复现环境

  • windows2008 R2 x64

  • Weblogic版本:12.2.1.3.0

  • Jdk版本:8u112(本来是用最新的,后面因为高版本的jdk受trustURLCodebase影响不能远程加载class,导致无法复现成功)

    这里放一张经典图片来说明

JNDI注入这里没有使用marshalsec,而是使用JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar这个工具,个人认为比marshalsec方便快捷许多,少了一些繁琐的步骤 膜Welk1n师傅一下

复现过程

将Weblogic搭建好后去到目录user_projects\domains\base_domain运行startWeblogic.cmd,即可启动Weblogic,访问

image-20200313190937366

出现上面的Weblogic登录页面则Weblogic搭建成功

将JNDI访问启动命令:

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "calc.exe" -A "127.0.0.1"

-C为要执行的命令, -A为JNDI服务地址,成功运行后如图

运行我们的Poc将rmi地址填入,这里填jdk7的rmi地址也是可以复现成功的

成功利用,并弹出了我们期待的calc

在低版本jdk中编译可以用javac Poc.java -source 1.6 -target 1.6命令来编译,同样类的版本要对应,听Wyatu师傅说有通用版本的利用方式,只能感慨师傅们Tql,Orz


Jwt security issue

JWT介绍与jwt的结构

什么是JWT,jwt全称:Json Web Token,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的,jwt可以用于授权与信息交换,作为跨域身份验证的一种方案.

JWT的结构:Header(头部) . Payload(负载) . Signature(签名) 由3部分组成

Header结构
1
2
3
4
5
{
"alg":"HS256"//默认HMAC SHA256 HS256
"typ":"JWT"
"kid":"/key/"//可选参数 指定加密算法的密钥
}

alg表示签名算法,typ表示令牌类型 后用Base64Url加密为字符串

Payload结构
1
{"jti":"1","iat":1528630988,"sub":"user1","exp":1528631588}

iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

Signature结构

Header指定的算法( base64UrlEncode(header) + “.” + base64UrlEncode(payload), secret) ,例如:

1
2
3
4
5
6
7
8
9
10
header
{
"alg":"HS256"//默认HMAC SHA256 HS256
"typ":"JWT"
"kid":"/key/"//可选参数 指定加密算法的密钥
}
payload
{"jti":"1","iat":1528630988,"sub":"user1","exp":1528631588}
signature
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

用来根据Header指定的算法和secret对header和payload加密作为客户端的Cookie

Base64Url算法

1
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

最终:

header.payload.Signature 其中header 与 payload 可以解密 Signature用于保证 内容不可篡改

base64encode(header).base64encode(payload).Header指定的算法( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

攻击面:

加密算法层面

修改算法攻击:加密方法是RS256(非对称加密),此时获得了公钥,无法获得解密私钥,可以修改RS256为HS256,这时候服务器会使用公钥解密客户端提交的信息

1
2
3
4
5
6
7
8
9
{
"alg" : "RS256",
"typ" : "jwt"
}
换成
{
"alg" : "HS256",
"typ" : "jwt"
}

JWT RFC允许空秘钥:得到一个token后,我们可以base64解密,把header的算法为none时,使服务端不使用算法解密 直接读取内容 即无法校验内容是否被篡改,使得我们可以修改jwt中的信息

即:

1
2
3
4
{
"alg":"none"//默认HMAC SHA256 HS256
"typ":"JWT"
}

签名中的secret可控通过一些方法可以获得任意用户的秘钥,就可以通过秘钥伪造jwt登陆任意用户
签名中的secret可爆破:在HS签名算法时,只有一个秘钥就是secret

其他攻击方法

使用JWT作为cookie用于跨域登录认证导致的问题,所有子域名都能通过该jwt登录,当该token泄露无需密码就能登录等,以及token过期时间问题。

signature中的secret泄露,各种泄露方式debug报错泄露,模板注入泄露获取配置文件,当Flask等Py模板注入受限只能获取配置文件中的内容可以考虑一下该攻击方案,获取备份文件解析问题导致下载备份文件泄露,以及默认的secret等等。。

敏感信息泄露:payload中的内容包含敏感信息如手机号,身份证,银行卡,密码等等

header中的Kid

kid在header的作用是用于指定加密算法的密钥,当kid指定密钥文件时修改为我们要读取的文件时导致的任意文件读取漏洞,当kid是从数据库中读取时可以导致SQL注入漏洞,当kid是通过grep这种调用程序匹配时出现命令执行问题

XSS的拓展

修改payload中内容导致的xss问题 如报错导致的xss 返回payload 信息的xss(如登录某一系统采用的是jwt方式进行身份验证,其中登录后展示的用户信息包含在jwt中修改这些信息为xss payload

在与服务器传输数据时,如一些修改个人信息的数据包中被放到jwt中,可以结合csrf发送这些数据包

jwt的玩法是多种多样的,可以根据具体情况来利用

Tips:因为Cookie不能跨域的原因所以一般是会放在HTTP请求头的 Authorization 字段 或者使用POST跨域传递

关于JWT鉴权安全问题

chybeta师傅的文章


Windows 剪切板与微软Hook框架Detours学习

*之前看了某师傅的一篇文章关于通过mstsc反向攻击客户端,奈何对Windows Api的相关操作不太了解于是打算学习一下,希望能构造Exp * XD

剪切板中的数据类型

1
2
3
4
5
6
7
CF_TEXT        //一段ANSI文本
CF_OEMTEXT //一段DOS文本
CF_UNICODETEXT //一段Unicode文本
CF_LOCALE //区域标识
CF_BITMAP //位图
CF_DIB //设备无关位图
CF_HDROP //指向HDROP这种结构的句柄,可以通过DragQueryFile函数来获得文件信息,HDROP是一个结构体。

常用操作剪切板的APi

1
2
3
4
5
BOOL OpenClipboard(HWND hWndNewOwner); //打开剪切板
BOOL EmptyClipboard(VOID); //清空剪切板
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem); //设置剪切板内容
HANDLE GetClipboardData(UINT uFormat); //返回剪切板内容的内存块句柄
BOOL CloseClipboard();

使用Win32 Api与剪切板交互

1
2
3
4
5
6
1. HGLOBAL GlobalAlloc(分配选项, 分配内存大小); //分配全局内存并返回它的句柄或指针
2. HGLOBAL GlobalFree(内存块句柄); //释放全局内存
3. SIZE_T GlobalSize(内存块句柄); //返回全局内存的大小
4. LPVOID GlobalLock(内存块句柄); //获得全局内存所有权,并返回指向全局内存的指针
5. BOOL GlobalUnlock(内存块句柄); //释放全局内存所有权,GlobalAlloc分配选项如果是GHND,则分配可变内存,清零内存并返回全局内存句柄,需要GlobalLock以获取指针
6.GlocalAlloc//分配选项如果是GPTR,则分配固定内存,清零内存并直接返回指针

微软Hook框架Detours

关于这个框架了解了RdpThief的朋友们都知道,RdpThief就是使用这个框架去Hook相关Api实现抓取Rdp登录凭证的

关于编译Detours的过程就不说了,网上一堆教程,简单的使用一下hook

在VS中新建控制台项目根据操作系统位数将编译出来的detours.hdetver.h,detours.lib复制到项目根目录下,在头文件处引用

avatar

项目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include "detours.h"
#pragma comment(lib,"detours.lib")

static int(WINAPI* HookMessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;
int WINAPI _MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
int ret = HookMessageBoxW(hWnd, L"hook", L"hook Success", uType);
return ret;
}



void DetourHook() {
DetourRestoreAfterWith();//恢复原来状态
DetourTransactionBegin();//开始Hook
DetourUpdateThread(GetCurrentThread());//刷新当前线程.
DetourAttach((void**)&HookMessageBoxW,_MessageBoxW);
DetourTransactionCommit();//Hook生效
}

void DetourUnHook() {

DetourTransactionBegin();//开始
DetourUpdateThread(GetCurrentThread());//刷新当前线程.
DetourDetach((void**)&HookMessageBoxW, _MessageBoxW);
DetourTransactionCommit();
}

int main()
{
MessageBoxW(NULL, L"Hello", L"Hello", 0);
DetourHook();//启动Hook
MessageBoxW(NULL, L"Hello", L"Hello", 0);
DetourUnHook();//取消Hook
return 0;

}

可以看到以上代码我们Hook了MessageBoxW这个api,查看效果

先是弹出了Hello 然后弹出了hook 第二个MessageBox被我们hook成功

使用Detours可以帮助我们快速的Hook指定的api

Tips:顺便插一个无关的内容,之前看Rdpthief这个项目有个小Bug(Win7抓不到Server地址),后面三好学生师傅提出了解决方法,看了一些博客修改的代码是比较冗长的,稍微用简单的方法修正了一下

直接判断lpServer的值是否为Null即可,如为Null,则使用Hook CredReadW 这个api来获取Server的值

对Windows编程的知识又复习了一遍,i了i了