REC
首页
文章分类
源码资源
技术教程
程序软件
文创娱乐
玄学修炼
关于我们
其他页面
网站统计
友情链接
用户留言
高清壁纸
关于易航
热门文章
Joe再续前缘主题 - 本站同款
易航网址导航系统 – 功能强大,轻量易用
Js自动播放HTML音乐(不受浏览器限制,无需先与浏览器交互,无需对浏览器进行修改)
网易云官方歌曲解析接口
JsonDb-PHP轻量级文件数据库系统
标签搜索
PHP
Web前端
网站源码
PHP源码
Typecho
课程资料
武术内功
HTML源码
Windows程序
Web
Android软件
Typecho插件
Joe主题
国漫
网络协议
MySQL
Python
NodeJs
Windows
小说
发布
登录
注册
找到
130
篇与
技术教程
相关的结果
- 第 10 页
2024-08-24
PHP 8.0:新时代的编程巨浪
随着技术的不断进步,PHP也在不断演化,迎来了它的8.0版本。这一版本不仅仅是数字的增加,更是PHP语言的一次重大飞跃。今天,让我们一起探索PHP 8.0的新特性,以及它如何为开发者带来前所未有的便利。 图片 PHP 8.0简介 PHP 8.0是PHP语言的最新主要版本,它引入了许多新特性和改进,包括JIT编译器、命名参数、联合类型、属性、改进的类型系统等。这些变化旨在提高性能、增强代码的可读性和可维护性。 PHP 7与PHP 8.0的差异 PHP 8.0在多个方面与PHP 7有所不同,以下是一些主要的差异: JIT编译器:PHP 8.0引入了Just-In-Time (JIT) 编译器,这使得PHP代码的执行速度有了显著提升。 命名参数:PHP 8.0允许开发者使用命名参数,这使得函数调用更加灵活和可读。 联合类型:PHP 8.0支持联合类型,允许一个变量或函数参数接受多种类型的值。 属性:PHP 8.0引入了属性(Attributes),这是一种新的元数据语法,可以用于注解类、方法和属性。 改进的类型系统:PHP 8.0对类型系统进行了改进,包括更严格的类型检查和新的类型声明。 PHP 8.0新增功能和方法 PHP 8.0带来了许多新功能和方法,以下是一些值得关注的亮点: 图片 JIT编译器:通过在配置文件中启用JIT,可以显著提高代码执行速度。 命名参数:允许在函数调用时指定参数名称,例如: htmlspecialchars($string, double_encode: false);联合类型:允许函数参数或返回值为多种类型之一,例如: function foo(int|float $number): int|float { return $number * 2; }属性:使用属性来注解代码,例如: #[Route("/api/items/{id}", methods: ["GET"])] function getItem($id) { // 你的代码 }Match表达式:提供了一种更简洁的switch语法,例如: $status = match ($statusCode) { 200 => 'OK', 404 => 'Not Found', default => 'Unknown', };入门指导 要开始使用PHP 8.0,你需要确保你的开发环境已经支持这个版本。你可以通过以下步骤来安装或升级到PHP 8.0: 检查当前版本:首先,运行以下命令来检查你当前的PHP版本: php -v升级到PHP 8.0:如果你的系统支持包管理器,你可以通过包管理器来安装或升级到PHP 8.0。例如,在Ubuntu上,你可以使用以下命令: sudo apt update sudo apt install php8.0代码案例 让我们来看一个简单的例子,展示PHP 8.0中的一些新特性: // 命名参数 htmlspecialchars($string, double_encode: false); // 联合类型 function foo(int|float $number): int|float { return $number * 2; } // 属性 #[Route("/api/items/{id}", methods: ["GET"])] function getItem($id) { // 你的代码 } // Match表达式 $status = match ($statusCode) { 200 => 'OK', 404 => 'Not Found', default => 'Unknown', };PHP 8.0优劣势 优势: 性能提升:PHP 8.0引入了JIT编译器,显著提高了执行速度。 代码简洁:命名参数和联合类型等新特性使得代码更加简洁和易读。 开发效率:属性和其他改进减少了样板代码,提高了开发效率。 劣势: 兼容性问题:升级到PHP 8.0可能会遇到一些兼容性问题,需要对现有代码进行适配。 学习曲线:新特性可能需要开发者花费时间去学习和适应。 使用方法和场景 PHP 8.0适用于所有希望提高性能和代码质量的PHP开发者。它特别适合以下场景: 高性能应用:对于需要快速响应和高吞吐量的应用,PHP 8.0的JIT编译器是一个巨大的优势。 现代化开发:PHP 8.0的新特性使得代码更加现代化,适合追求最新技术趋势的开发者。 注意事项 在使用PHP 8.0时,开发者需要注意以下几点: 兼容性测试:在升级之前,确保对现有代码进行充分的兼容性测试。 文档学习:深入学习PHP 8.0的新特性,充分利用其带来的优势。 结尾 亲爱的开发工程师们,PHP 8.0为我们带来了新的机遇和挑战。让我们拥抱变化,勇敢地迈向新时代。记住,技术的海洋无边无际,每一次升级都是我们航行的新起点。明天,我们将继续探索PHP 8.1的奥秘,敬请期待! 在这篇文章中,我们深入探讨了PHP 8.0的新特性,并提供了入门指导、代码案例和使用场景。希望这些信息能帮助你在开发旅程中做出明智的选择,并在技术的海洋中乘风破浪。
技术教程
# PHP
易航
1年前
3
294
2
2024-08-22
《黑神话·悟空》是用什么编程语言开发的?
最近这个“黑神话·悟空”真是火的一塌糊涂,这款被誉为中国第一款3A制作的单机游戏,凭借其惊艳的画面、流畅的战斗体验和深厚的文化内涵,属实是火出圈儿了,连央视、外交部都纷纷点赞了。 图片 关于这款游戏的制作和文化咱就不聊了,毕竟我也不是专业的,今天咱们聊聊另一个话题。 话说,你知道黑神话·悟空这款游戏是用什么编程语言开发的吗? 答案主要是C++(也用到C#和其他语言) 根据游戏开发公司游戏科学官网的显示,《黑神话:悟空》游戏使用的是虚幻引擎(Unreal Engine): 图片 而这个引擎的底层源码主要使用的就是C++。 图片 图片 开发这款引擎的是Epic Games,一家来自美国的游戏公司。 虚幻引擎属于开源项目,大家自己学习或者开发一些免费的东西是可以直接用的。但如果要开发像《黑神话:悟空》这样的商业产品,那就得给钱了。按照他们的协议,当产品销售超过100万$时,就要付5%的版税。 图片 悟空的销售早就超过这个数了,所以现在每多一个人购买这款游戏,就会有5%进入到这家公司的钱包里。当然这种算法可能有些粗糙,在不同平台还有平台抽成,但大体是这么个意思,每销售一次,就会有一笔钱进到这家公司的账户。 我在微博、知乎、公众号很多平台看到大家都在感叹,国内的游戏公司很少愿意投入到这样的3A游戏制作,投入太大、周期长、收益不确定,导致大家都更愿意做投入小、来钱快的网游、手游,做一些奶头乐的游戏,而这类游戏,往往很难承载起文化传播的重担。 确实是这样,过去的十几年里,中国的游戏开发者们大多集中在网游和手游领域,原因很简单——来钱快。网游可以通过内购和会员制等方式持续盈利,而手游更是凭借短平快的开发周期和庞大的用户基数成为了香饽饽。 每次看到这样的评论留言,我就想到咱们软件开发领域其实不是一样的吗? 在中国的软件开发行业,Java和Python几乎成为了开发者的首选。无论是互联网公司开发的各种应用,还是传统企业的信息系统,这两种语言无处不在。为什么它们如此受欢迎?原因很简单:Java和Python的学习门槛低,开发速度快,并且有丰富的生态系统和社区支持。这让企业能够迅速推出产品,抢占市场份额,迎合快速迭代的商业需求。 再来看C++,国内做C++开发的团队实在是太少了,企业想招聘一个合适的C++人员往往要付出更多的时间和成本。这里面有多方面的原因,一方面C++学习曲线陡峭,开发人员需要掌握内存管理、操作系统机制等复杂内容,不仅耗时,而且容易出错。其次,C++的开发周期较长,企业在如此内卷的市场下,也很难快速迭代,毕竟市场不等人。 于是多年下来,造成的局面就是,国内的软件开发主要集中在网站、APP、小程序等领域,很少有涉及底层的软件系统。各种工业软件、操作系统、游戏引擎、数据库、浏览器这些东西基本都是国外的东西。 我看到很多人期望《黑神话:悟空》能像一束光,照亮国内3A游戏的道路,有更多的公司和团队进入到这个领域。其实我也希望,国内有更多的C、C++这些编程语言的开发者,开发出各种基础设施软件,走向全球市场,未来别人用我们的东西,每卖出一份也能给我们交钱。而不只是像现在一样,做一些同质化严重的小程序和APP。 期望归期望,但坦率来讲,这很难,毕竟《流浪地球》五年过去了,如今也只有一个《流浪地球》。 商业和资本本质毕竟还是逐利的,尤其是国内的市场太过浮躁,大家更看重短期利益。别说公司和企业了,个人同样如此,很多人都期望培训班培训三个月,就能月薪上万进入大厂。 不过星星之火总好过一片暗淡,当《黑神话:悟空》这样的星星之火越来越多时,终究有燎原之日。 最后期望中国游戏产业和中国软件产业都能有美好的未来!
技术教程
# C++
易航
1年前
0
195
5
2023-01-05
网易云官方歌曲解析接口
单曲音频文件解析接口:http://music.163.com/song/media/outer/url?id=歌曲的ID
技术教程
易航
2年前
2
1,501
3
2022-12-19
iFrame父子相互获取对方DOM元素
父页面获取子页面dom的方法 /* 必须用onload */ window.onload = () => { const sonWindow = document.querySelector("iframe").contentWindow; const sonDiv = sonWindow.document.querySelector(".son") console.log(sonDiv); }子页面获取父页面dom的方法 /* 必须用onload */ window.onload = () => { const parentWindow = window.parent; const parentDiv = parentWindow.document.querySelector(".parent") console.log(parentDiv); }
技术教程
# Web前端
易航
3年前
1
83
0
2022-12-13
ThinkPHP中日期时间区间查询以及whereTime用法
一、使用where方法进行时间的比较查询 where('create_time', '> time', '2021-8-8'); // 大于某个时间 where('create_time', '<= time', '2020-8-8'); // 小于某个时间 where('create_time', 'between time', ['2020-1-1', '2020-10-1']); // 时间区间查询二、使用whereTime方法 whereTime('birthday', '>=', '1970-10-1')->select(); // 大于某个时间 whereTime('birthday', '<', '2000-10-1')->select(); // 小于某个时间 whereTime('birthday', 'between', ['1970-10-1', '2000-10-1'])->select(); // 时间区间查询 whereTime('birthday', 'not between', ['1970-10-1', '2000-10-1'])->select(); // 不在某个时间区间三、时间表达式 // 获取今天的文章 Db::table('think_news')->whereTime('create_time', 'today')->select(); // 获取昨天的文章 Db::table('think_news')->whereTime('create_time', 'yesterday')->select(); // 获取本周的文章 Db::table('think_news')->whereTime('create_time', 'week')->select(); // 获取上周的文章 Db::table('think_news')->whereTime('create_time', 'last week')->select(); // 获取本月的文章 Db::table('think_news')->whereTime('create_time', 'month')->select(); // 获取上月的文章 Db::table('think_news')->whereTime('create_time', 'last month')->select(); // 获取今年的文章 Db::table('think_news')->whereTime('create_time', 'year')->select(); // 获取去年的文章 Db::table('think_news')->whereTime('create_time', 'last year')->select();四、如果查询当天、本周、本月和今年的时间,还可以简化为: // 获取今天的文章 Db::table('think_news')->whereTime('create_time', 'd')->select(); // 获取本周的文章 Db::table('think_news')->whereTime('create_time', 'w')->select(); // 获取本月的文章 Db::table('think_news')->whereTime('create_time', 'm')->select(); // 获取今年的文章 Db::table('think_news')->whereTime('create_time', 'y')->select();五、时间范围查询 // 查询两个小时内的文章 Db::table('think_news')->whereTime('create_time', '-2 hours')->select(); // 查询两天内的文章 Db::table('think_news')->whereTime('create_time', '-2 days')->select();
技术教程
# PHP
# MySQL
易航
3年前
0
424
2
2022-12-11
网站添加雪花飘落特效
直接添加下方引入JS链接的HTML代码即可 隐藏内容,请前往内页查看详情
技术教程
# Web前端
易航
3年前
1
252
1
2022-12-09
网站添加Loading加载动画
Loading动画作用 网站添加Loading动画可有效防止部分浏览器打开网页后因为有些静态资源还没有加载完毕导致的闪屏、白屏、花屏、错误排版等BUG 效果展示 Loading动画图片 Loading动画代码 隐藏内容,请前往内页查看详情 HTML是从上至下解析,所以添加到哪里合适应该知道了吧
技术教程
# Web前端
易航
3年前
6
306
10
2022-11-21
给你的网站增加一款简洁而功能强大的音乐播放器
H5播放器介绍 APlayer 是一个简洁漂亮、功能强大的 Html5 音乐播放器 MetingJS 是为 APlayer 添加网易云、QQ音乐、酷狗音乐等支持的插件 安装教程 安装很简单,一共需要调用三个文件:APlayer.min.js APlayer.min.css Meting.min.js 你可以使用 CDN 调用,只需要在 <head> 里面插入: <link href="https://cdn.bootcdn.net/ajax/libs/aplayer/1.10.1/APlayer.min.css" rel="stylesheet"> <script src="https://cdn.bootcdn.net/ajax/libs/aplayer/1.10.1/APlayer.min.js"></script>在 footer 里面插入: <script src="https://cdn.bootcdn.net/ajax/libs/meting/2.0.1/Meting.min.js"></script>当然,你可以将这些文件托管在自己的服务器,把上面的调用链接改成自己的就行了 使用方法 APlayer 原生用法 先看一个最简单的例子: <div id="aplayer"></div> <script type="text/javascript"> const ap = new APlayer({ container: document.getElementById('aplayer'), audio: [{ name: '你从未离去', artist: '白挺', url: 'https://doge.ottoli.cn/你从未离去.mp3', cover: 'https://doge.ottoli.cn/你从未离去.jpg' }] }); </script>在js 代码中: 参数 container 值为 document.getElementById('aplayer') 意思是定义当前播放器容器 id 为 aplayer 参数 audio 中有 4 个子参数,定义关于音频的相关参数: 参数 name 定义音频名称 参数 artist 定义艺术家名 参数 url 指向音频文件的地址 参数 cover 指向音频封面的地址 然后,在需要使用播放器的地方,将容器 <div> 的 id 设置为参数 container 中设定的值即可 MetingJS 的用法 前面已经看到,APlayer 原生用法设置参数十分繁琐,而且只能调用音频文件直链,增加服务器开销。而使用 MetingJS 就很好地解决了这个问题 先看一个最简单的例子: 对应的代码为: <meting-js server="netease" type="song" id="31365604" > </meting-js>一个 MetingJS 播放器至少需要三个参数: server 指定调用的 API ,可选 netease, tencent, kugou, xiami, baidu ,分别对应网易云音乐、QQ音乐、酷狗音乐、虾米音乐、百度音乐 type 指定调用类型,可选 song, playlist, album, search, artist ,分别对应单曲、歌单、专辑、搜索结果、艺术家 id 指定调用的 id ,一般可以在地址栏中找到 当 type 选择的是个播放列表时,生成的播放器是这样的: 播放列表默认是打开的,你可以使用参数 listFolded="true" 使其默认折叠 当你设定 fixed="true" ,会生成一个吸附在页面左下角的播放器,就像我的博客左下角那个 当你设定 mini="true" ,会生成一个 mini 播放器: 值得注意的是:除了 mini 模式,MetingJS 生成的播放器默认是带有歌词的(而且关不掉) 全部参数说明请查阅 MetingJS 官方文档(其实除了三个必要参数其余都和 APlayer 原生参数一样)
技术教程
# Web前端
易航
3年前
1
584
5
2022-09-06
初探PHP-Parser和PHP代码混淆
初探PHP-Parser PHP-Parser 是 nikic 用PHP编写的PHP5.2到PHP7.4解析器,其目的是简化静态代码分析和操作。 解析 创建一个解析器实例: use PhpParser\ParserFactory; $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);ParserFactory接收以下几个参数: ParserFactory::PREFER_PHP7 :优先解析PHP7,如果PHP7解析失败则将脚本解析成PHP5 ParserFactory::PREFER_PHP5 :优先解析PHP5,如果PHP5解析失败则将脚本解析成PHP7 ParserFactory::ONLY_PHP7 :只解析成PHP7 ParserFactory::ONLY_PHP5 :只解析成PHP5 将PHP脚本解析成抽象语法树(AST) <?php use PhpParser\Error; use PhpParser\ParserFactory; require 'vendor/autoload.php'; $code = file_get_contents("./test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; } var_dump($ast);节点转储 如果是用上面的 var_dump 的话显示的 AST 可能会比较乱,那么我们可以使用 NodeDumper 生成一个更加直观的 AST <?php use PhpParser\NodeDumper; $nodeDumper = new NodeDumper; echo $nodeDumper->dump($stmts);或者我们使用 vendor/bin/php-parse 也是一样的效果 λ vendor/bin/php-parse test.php ====> File test.php: ==> Node dump: array( 0: Stmt_Expression( expr: Expr_Assign( var: Expr_Variable( name: a ) expr: Scalar_LNumber( value: 1 ) ) ) )节点树结构 PHP是一个成熟的脚本语言,它大约有140个不同的节点。但是为了方便使用,将他们分为三类: PhpParser\Node\Stmts 是语句节点,即不返回值且不能出现在表达式中的语言构造。例如,类定义是一个语句,它不返回值,你不能编写类似 func(class {}) 的语句。 PhpParser\Node\expr 是表达式节点,即返回值的语言构造,因此可以出现在其他表达式中。如:$var (PhpParser\Node\Expr\Variable)和func() (PhpParser\Node\Expr\FuncCall)。 PhpParser\Node\Scalars 是表示标量值的节点,如"string" (PhpParser\Node\scalar\string)、0 (PhpParser\Node\scalar\LNumber) 或魔术常量,如"FILE" (PhpParser\Node\scalar\MagicConst\FILE) 。所有 PhpParser\Node\scalar 都是延伸自 PhpParser\Node\Expr ,因为 scalar 也是表达式。 需要注意的是 PhpParser\Node\Name 和 PhpParser\Node\Arg 不在以上的节点之中 格式化打印 使用 PhpParser\PrettyPrinter 格式化代码 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; require 'vendor/autoload.php'; $code = file_get_contents('./index.php'); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; return; } $prettyPrinter = new PrettyPrinter\Standard; $prettyCode = $prettyPrinter->prettyPrintFile($ast); echo $prettyCode;节点遍历 使用 PhpParser\NodeTraverser 我们可以遍历每一个节点,举几个简单的例子:解析php中的所有字符串,并输出 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\NodeTraverser; use PhpParser\NodeVisitorAbstract; use PhpParser\Node; require 'vendor/autoload.php'; class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ($node instanceof Node\Scalar\String_) { echo $node->value; } } } $code = file_get_contents("./test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); $traverser = new NodeTraverser; $traverser->addVisitor(new MyVisitor); try { $ast = $parser->parse($code); $stmts = $traverser->traverse($ast); } catch (Error $error) { echo "Parse error: {$error->getMessage()}"; return; }遍历php中出现的函数以及类中的成员方法 <?php class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ( $node instanceof Node\Expr\FuncCall || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_ || $node instanceof Node\Expr\MethodCall ) { echo $node->name; } } }替换php脚本中函数以及类的成员方法函数名为小写 <?php class MyVisitor extends NodeVisitorAbstract { public function leaveNode(Node $node) { if ($node instanceof Node\Expr\FuncCall) { $node->name->parts[0] = strtolower($node->name->parts[0]); } elseif ($node instanceof Node\Stmt\ClassMethod) { $node->name->name = strtolower($node->name->name); } elseif ($node instanceof Node\Stmt\Function_) { $node->name->name = strtolower($node->name->name); } elseif ($node instanceof Node\Expr\MethodCall) { $node->name->name = strtolower($node->name->name); } } }需要注意的是所有的 visitors 都必须实现 PhpParser\NodeVisitor 接口,该接口定义了如下4个方法: public function beforeTraverse(array $nodes); public function enterNode(\PhpParser\Node $node); public function leaveNode(\PhpParser\Node $node); public function afterTraverse(array $nodes); beforeTraverse 方法在遍历开始之前调用一次,并将其传递给调用遍历器的节点。此方法可用于在遍历之前重置值或准备遍历树。 afterTraverse 方法与 beforeTraverse 方法类似,唯一的区别是它只在遍历之后调用一次。 在每个节点上都调用 enterNode 和 leaveNode 方法,前者在它被输入时,即在它的子节点被遍历之前,后者在它被离开时。 这四个方法要么返回更改的节点,要么根本不返回(即null),在这种情况下,当前节点不更改。 PHP代码混淆 下面举两个php混淆的例子,比较简单(郑老板@zsx所说的20分钟内能解密出来的那种),主要是加深一下我们对 PhpParser 使用 phpjiami 大部分混淆都会把代码格式搞得很乱,用 PhpParser\PrettyPrinter 格式化一下代码 <?php use PhpParser\Error; use PhpParser\ParserFactory; use PhpParser\PrettyPrinter; require 'vendor/autoload.php'; $code = file_get_contents('./test.php'); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $prettyPrinter = new PrettyPrinter\Standard; $prettyCode = $prettyPrinter->prettyPrintFile($ast); file_put_contents('en_test.php', $prettyCode);格式基本能看了 图片 因为函数和变量的乱码让我们之后的调试比较难受,所以简单替换一下混淆的函数和变量 <?php use PhpParser\Error; use PhpParser\NodeFinder; use PhpParser\ParserFactory; require 'vendor/autoload.php'; $code = file_get_contents("./index_1.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); $nodeFinder = new NodeFinder; try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } $Funcs = $nodeFinder->findInstanceOf($ast, PhpParser\Node\Stmt\Function_::class); $map = []; $v = 0; foreach ($Funcs as $func) { $funcname = $func->name->name; if (!isset($map[$funcname])) { if (!preg_match('/^[a-z0-9A-Z_]+$/', $funcname)) { $code = str_replace($funcname, "func" . $v, $code); $v++; $map[$funcname] = $v; } } } $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } file_put_contents("index_2.php", $code);变量和函数基本能看了,还是有一些数据是乱码,这个是它自定义函数加密的字符串,大多数都是php内置的函数,我们调试一下就基本能看到了 图片 但是得注意一下,phpjiami有几个反调试的地,在35行的地方打个断点 图片 可以看到4个反调试的点: 第一个点: 当你是以cli运行php的时候就会直接 die() 掉,直接注释掉即可 php_sapi_name()=="cli" ? die() : '';第二个点: 和第一个差不多,也是验证运行环境的,cli模式下没有这些变量索引,直接注释即可 if (!isset($_SERVER["HTTP_HOST"]) && !isset($_SERVER["SERVER_ADDR"]) && !isset($_SERVER["REMOTE_ADDR"])) { die(); }第三个点: 如果你在 if 语句处停留超过100ms的话就会直接 die 掉,注释即可 $v46 = microtime(true) * 1000; eval(""); if (microtime(true) * 1000 - $v46 > 100) { die(); }第四个点: $51就是整个文件内容,这行是用于加密前的文件对比是否完整,如果不完整则执行$52(),因为$52不存在所以会直接报错退出,而如果对比是完整的话那么就是$53,虽然$53也不存在,但只是抛出一个Warning,所以我们这里也是直接把这行注释掉。 !strpos(func2(substr($v51, func2("???"), func2("???"))), md5(substr($51, func2("??"), func2("???")))) ? $52() : $53;注释完之后我们在return那里打一个断点,可以发现在return那里我们需要解密的文件内容呈现了出来。 图片 解密之后的内容: <?php @eval("//Encode by phpjiami.com,Free user."); // Clear the uploads directory every hour highlight_file(__FILE__); $sandbox = "uploads/" . md5("De1CTF2020" . $_SERVER['REMOTE_ADDR']); @mkdir($sandbox); @chdir($sandbox); if ($_POST["submit"]) { if (($_FILES["file"]["size"] < 2048) && Check()) { if ($_FILES["file"]["error"] > 0) { die($_FILES["file"]["error"]); } else { $filename = md5($_SERVER['REMOTE_ADDR']) . "_" . $_FILES["file"]["name"]; move_uploaded_file($_FILES["file"]["tmp_name"], $filename); echo "save in:" . $sandbox . "/" . $filename; } } else { echo "Not Allow!"; } } function Check() { $BlackExts = array("php"); $ext = explode(".", $_FILES["file"]["name"]); $exts = trim(end($ext)); $file_content = file_get_contents($_FILES["file"]["tmp_name"]); if ( !preg_match('/[a-z0-9;~^`&|]/is', $file_content) && !in_array($exts, $BlackExts) && !preg_match('/\.\./', $_FILES["file"]["name"]) ) { return true; } return false; } ?> <html> <head> <meta charset="utf-8"> <title>upload</title> </head> <body> <form action="index.php" method="post" enctype="multipart/form-data"> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="submit"> </form> </body> </html>其实phpjiami加密之后的整个脚本就是解密我们文件的脚本,我们的文件内容被加密之后放在了 ?> 最后面 图片 整个解密过程也比较简单,其中$v51是我们加密之后内容,$v55是解密后的内容。 $v55 = str_rot13(@gzuncompress(func2(substr($v51,-974,$v55))));其中func2是解密函数 图片 最后是拿func2解密之后的代码放在这个 eval 中执行 图片 还有一种比较简单快捷的方法是通过 hook eval 去获取 eval 的参数,因为不涉及 PHP-Parser 所以就不过多展开了。 Enphp混淆 官网:http://enphp.djunny.com/ github: https://github.com/djunny/enphp 使用官方的加密例子: <?php include './func_v2.php'; $options = array( //混淆方法名 1=字母混淆 2=乱码混淆 'ob_function' => 2, //混淆函数产生变量最大长度 'ob_function_length' => 3, //混淆函数调用 1=混淆 0=不混淆 或者 array('eval', 'strpos') 为混淆指定方法 'ob_call' => 1, //随机插入乱码 'insert_mess' => 0, //混淆函数调用变量产生模式 1=字母混淆 2=乱码混淆 'encode_call' => 2, //混淆class 'ob_class' => 0, //混淆变量 方法参数 1=字母混淆 2=乱码混淆 'encode_var' => 2, //混淆变量最大长度 'encode_var_length' => 5, //混淆字符串常量 1=字母混淆 2=乱码混淆 'encode_str' => 2, //混淆字符串常量变量最大长度 'encode_str_length' => 3, // 混淆html 1=混淆 0=不混淆 'encode_html' => 2, // 混淆数字 1=混淆为0x00a 0=不混淆 'encode_number' => 1, // 混淆的字符串 以 gzencode 形式压缩 1=压缩 0=不压缩 'encode_gz' => 0, // 加换行(增加可阅读性) 'new_line' => 1, // 移除注释 1=移除 0=保留 'remove_comment' => 1, // debug 'debug' => 1, // 重复加密次数,加密次数越多反编译可能性越小,但性能会成倍降低 'deep' => 1, // PHP 版本 'php' => 7, ); $file = 'test.php'; $target_file = 'en_test.php'; enphp_file($file, $target_file, $options);加密之后大概长这样子 图片 可以看到,我们的大部分字符串、函数等等都被替换成了类似于 $GLOBALS{乱码}[num] 这种形式,我们将其输出看一下: 图片 可以看到我们原本的脚本中的字符串都在此数组里面,所以我们只要将$GLOBALS{乱码}[num]还原成原来对应的字符串即可。 那么我们如何获取 $GLOBALS{乱码} 数组的内容,很简单,在我们获取AST节点处打断点即可找到相关内容: 图片 $split = $ast[2]->expr->expr->args[0]->value->value; $all = $ast[2]->expr->expr->args[1]->value->value; $str = explode($split, $all); var_dump($str);可以看到,和上面输出的是一样的(如果加密等级不一样则还需要加一层 gzinflate ) 图片 然后就是通过AST一个节点一个节点将其替换即可,如果不知道节点类型的同学可以用 $GLOBALS[A][1] ,将其输出出来看一下即可,然后根据节点的类型和数据进行判断即可,如下: class PhpParser\Node\Expr\ArrayDimFetch#1104 (3) { public $var => class PhpParser\Node\Expr\ArrayDimFetch#1102 (3) { public $var => class PhpParser\Node\Expr\Variable#1099 (2) { public $name => string(7) "GLOBALS" protected $attributes => array(2) { ... } } public $dim => class PhpParser\Node\Expr\ConstFetch#1101 (2) { public $name => class PhpParser\Node\Name#1100 (2) { ... } protected $attributes => array(2) { ... } } protected $attributes => array(2) { 'startLine' => int(2) 'endLine' => int(2) } } public $dim => class PhpParser\Node\Scalar\LNumber#1103 (2) { public $value => int(1) protected $attributes => array(3) { 'startLine' => int(2) 'endLine' => int(2) 'kind' => int(10) } } protected $attributes => array(2) { 'startLine' => int(2) 'endLine' => int(2) } }根据上面的节点编写脚本 public function leaveNode(Node $node) { if ( $node instanceof PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof PhpParser\Node\Expr\ArrayDimFetch && $node->var->var instanceof PhpParser\Node\Expr\Variable && $node->var->var->name === "GLOBALS" && $node->var->dim instanceof PhpParser\Node\Expr\ConstFetch && $node->var->dim->name instanceof PhpParser\Node\Name && $node->var->dim->name->parts[0] === $this->str && $node->dim instanceof PhpParser\Node\Scalar\LNumber ) { return new PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } return null; }解出来的内容如下,可以看到大部分已经成功解密出来了 图片 还有就是解密的一部分出现这样语句:('highlight_file')(__FILE__); ,很明显不符合我们平时的写法,将其节点重命名一下 if ( ($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\MethodCall) && $node->name instanceof Node\Scalar\String_ ) { $node->name = new Node\Name($node->name->value); }现在看起来就舒服多了 图片 我们分析剩下乱码的部分 图片 可以看到是函数里面的局部变量还是乱码,从第一句可以看出所有的局部变量都是以 & $GLOBALS[乱码] 为基础的,而 & $GLOBALS[乱码] 是我们上面已经找出来的,所以也是将其替换即可。 if ( $node instanceof \PhpParser\Node\Stmt\Expression && $node->expr instanceof \PhpParser\Node\Expr\AssignRef && $node->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->expr->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr->var->name === "GLOBALS" && $node->expr->expr->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->expr->expr->dim->name instanceof \PhpParser\Node\Name && $node->expr->expr->dim->name->parts != [] ) { $this->Localvar = $node->expr->var->name; return NodeTraverser::REMOVE_NODE; } else if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\Variable && $node->var->name === $this->Localvar && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); }替换之后,可以看到与 & $GLOBALS[乱码] 有关的已经全部被替换了,只有变量部分是乱码了 替换变量为 $v 这种形式 <?php function BeautifyVariables($code) { $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } return $code; }至此所有代码全部被还原(除了变量名这种不可抗拒因素之外) 图片 还有一部分是没有用的全局变量和常量,手动或者根据AST去进行删除即可,下面贴一下完整解密脚本 <?php require "./vendor/autoload.php"; use \PhpParser\Error; use \PhpParser\ParserFactory; use \PhpParser\NodeTraverser; use \PhpParser\NodeVisitorAbstract; use \PhpParser\Node; use \PhpParser\PrettyPrinter\Standard; class MyVisitor extends NodeVisitorAbstract { public $str; public $str_arr; public $Localvar; public function leaveNode(Node $node) { if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var->var instanceof \PhpParser\Node\Expr\Variable && $node->var->var->name === "GLOBALS" && $node->var->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->var->dim->name instanceof \PhpParser\Node\Name && $node->var->dim->name->parts[0] === $this->str && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } if (($node instanceof Node\Expr\FuncCall || $node instanceof Node\Expr\StaticCall || $node instanceof Node\Expr\MethodCall) && $node->name instanceof Node\Scalar\String_ ) { $node->name = new Node\Name($node->name->value); } if ( $node instanceof \PhpParser\Node\Stmt\Expression && $node->expr instanceof \PhpParser\Node\Expr\AssignRef && $node->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->expr->expr->var instanceof \PhpParser\Node\Expr\Variable && $node->expr->expr->var->name === "GLOBALS" && $node->expr->expr->dim instanceof \PhpParser\Node\Expr\ConstFetch && $node->expr->expr->dim->name instanceof \PhpParser\Node\Name && $node->expr->expr->dim->name->parts != [] ) { $this->Localvar = $node->expr->var->name; return NodeTraverser::REMOVE_NODE; } else if ( $node instanceof \PhpParser\Node\Expr\ArrayDimFetch && $node->var instanceof \PhpParser\Node\Expr\Variable && $node->var->name === $this->Localvar && $node->dim instanceof \PhpParser\Node\Scalar\LNumber ) { return new \PhpParser\Node\Scalar\String_($this->str_arr[$node->dim->value]); } return null; } } function BeautifyVariables($code) { $v = 0; $map = []; $tokens = token_get_all($code); foreach ($tokens as $token) { if ($token[0] === T_VARIABLE) { if (!isset($map[$token[1]])) { if (!preg_match('/^\$[a-zA-Z0-9_]+$/', $token[1])) { $code = str_replace($token[1], '$v' . $v++, $code); $map[$token[1]] = $v; } } } } return $code; } $code = file_get_contents("./en_test.php"); $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); try { $ast = $parser->parse($code); } catch (Error $error) { echo "Parse error: {$error->getMessage()}\n"; return; } var_dump($ast); $split = $ast[2]->expr->expr->args[0]->value->value; $all = $ast[2]->expr->expr->args[1]->value->value; $str1 = $ast[2]->expr->var->dim->name->parts[0]; $str_arr = explode($split, $all); $visitor = new MyVisitor; $visitor->str = $str1; $visitor->str_arr = $str_arr; $traverser = new NodeTraverser; $traverser->addVisitor($visitor); $stmts = $traverser->traverse($ast); $prettyPrinter = new Standard; $code = $prettyPrinter->prettyPrintFile($stmts); $code = BeautifyVariables($code); echo $code;注:需要注意的是 enphp 使用的全局变量不一定是 GLOBALS ,也可能是 _SERVER、_GET 等等,根据具体情况进行判断,还有就是加密等级不同对应的解密方式也是不同的,不过其中的思想都是大同小异 参考 https://www.52pojie.cn/thread-693641-1-1.html https://www.52pojie.cn/thread-883976-1-1.html https://xz.aliyun.com/t/7363 https://github.com/nikic/PHP-Parser/blob/master/doc/2_Usage_of_basic_components.markdown
技术教程
# PHP
易航
3年前
1
303
1
上一页
1
...
9
10
11
...
15
下一页