分类: 建站进阶

  • 网站收录突然减少是怎么回事?

    网站收录突然减少是怎么回事?

    网站收录量一直是SEOer比较关注的一项网站重要数据,因为这关乎网站的权重积累,长尾关键词的布局和排名。

    最近在给客户优化网站的时候发现了这样一个问题:网站收录量突然一下子减少了三分之一,而且就是在一夜之间的事情,毫无征兆就出现了这种情况,确实是让我有点始料未及。

    网站收录

    于是我认真检查近一段时间的网站SEO优化工作以及监测统计的网站SEO各项数据,发现在此之前网站状况都是良好的,并且在网站SEO优化期间我也没有进行过违规操作,根本没用过作弊手法来优化。网站的权重没有变化,快照更新稍有延迟但也就是稍慢了三天的时间。网站的关键词排名没有波动,已经有排名的关键词数量也没有减少。

    网站收录突然减少就是网站降权的征兆了吗?我当时还是有点无语的,因为我的SEO优化都是严格按照搜索引擎官方优化建议来做的。

    既然网站也没有降权,只是网站收录量突然减少,我想可能是百度一时调整导致的结果,百度一段时间会清理一次索引库。于是我没有改变我的优化策略,第二天情况依旧如此,收录量的减少问题丝毫没有改变。

    我冷静下来思考了一下,在继续优化的同时,将外部优化工作稍微加大了力度,而且在站长平台后台做了反馈申诉处理,就这样,到了第三天的时候,收录量终于恢复过来,网站没有受到任何影响。

    最后这次网站突然收录减少只是虚惊一场,小插曲以网站收录量恢复正常而收尾。所以如果站长朋友们遇到跟我类似的情况,请不要太过紧张,我们只管做好我们自己的SEO优化工作,切勿在出现突发状况的时候乱了阵脚采取极端操作而导致前功尽弃!

    网站收录下降的原因是非常复杂的,很多因素都会影响网站收录情况。接下来给大家摘要几点重要的原因:

    1、内容质量过于低下

    有些企业在建站的初期为了能获取到更多的用户流量,是坚持了原创文章更新。但是,原创文章内容耗费的人力物力成本昂贵,且无法达到一定的更新数量。所以,后面企业为了节省运营成本,使用机器采集文章,这样的文章更新是保证了数量,却无法保证高质量内容的输出,从而导致网站收录下降。

    2、网站修改较为频繁

    网站优化都必须先制定详细的方案再进行实施,这样能确保优化的方向不会发生错误。但是,有些企业经常会忘记这一步骤,导致后期的网站出现频繁的修改标题、网站框架等,让搜索引擎误以为是新网站,从而影响到网站收录情况。

    3、网站死链接太多了

    网站中有时候删除页面都会产生死链接,如果没有及时的处理,对搜索引擎的体验是非常不友好的,即使是做了404页面链接,还是会影响到网站收录问题。

    4、友情链接质量比较低下

    友情链接的交换是网站建设中不可或缺的一个重要部分,但是如果对方网站受到搜索引擎的惩罚时,也会导致我们自身的网站收录下降。

    5、百度内部调整

    如上述案例,网站没有没有进行过违规操作,根本没用过作弊手法来优化,可能就是百度自身调整引起的索引库异常,可以尝试在站长平台后台做了反馈申诉处理。

    通过以上的内容可知,网站收录下降的具体原因了。然而,网站排名想要更好,实现更好的发展就必须快速的被搜索引擎收录到,即网站相关页面,可以百度快速收录,才有可能实现美好的发展愿望。

  • 为什么网站无法被百度收录[百度不收录原因分析]

    为什么网站无法被百度收录[百度不收录原因分析]

    有朋友提问,说为什么百度不收录网站,是不是因为网站没有备案的原因。事实上,百度不收录网站页面的原因很多,抛开域名本身的因素,下面我们就来分析。

    百度蜘蛛

    1、域名进入了百度黑名单导致百度不收录

    有些朋友在购买新域名的时候,没有去查域名的历史表现。事实上,有些域名已经进入了百度的黑名单,或者说被百度列入了可疑名单。对于这样的域名,即便网站是全新的内容,百度也不会怎么收录的。

    解决办法:查询域名历史状态,如果域名的确被百度惩罚过,建议更换域名。

    2、网站服务器极度不稳定导致百度不收录

    我们需要知道,百度蜘蛛爬行页面是需要访问网站服务器的,如果蜘蛛来的时候服务器宕机导致蜘蛛无法访问,这就会直接影响页面抓取,影响百度收录。

    百度蜘蛛不定期的访问网站,如果经常遇到无法访问的情况,久而久之,百度蜘蛛会减少对网站的访问次数!蜘蛛访问的少,抓取自然就少,对应的网站的百度收录就少。

    很多人在抱怨,为什么百度不收录网站页面,明明页面质量都是很不错的!这其中的原因或许就是服务器不稳定。

    解决办法:尽可能选择国内服务器,且保证服务器的稳定性。

    3、robots协议设置错误导致百度不收录

    robots文件是百度搜索遵循的协议,我们在网站调试期间往往会屏蔽百度蜘蛛的抓取,待正式上线后开放搜索。但很多时候,我们忘记了修改robots文件,网站一直处于屏蔽状态,这样百度也是不会收录的。

    解决办法:如果其他方面正常,可是百度不收录网站,那么请记得去检查robots文件的设置是否正确。

    4、页面未被蜘蛛抓取导致百度不收录

    有朋友问为什么百度不收录某些特定的页面,其他页面收录正常,可有些页面百度就是不收录。

    通常起来下,笔者都让先去分析网站日志,看百度蜘蛛是否已经成功抓取了这些页面,如果百度蜘蛛没有被抓取到,那自然就不会收录。

    解决办法:百度不收录页面很多的原因在于没有抓取到,如果网站其他页面收录正常,而百度不收录的页面质量不错,建议多给浙西诶页面增加链接入口,包括内链和外链。

    推荐阅读:分析网站日志找到页面不收录的原因。

    5、页面质量低导致百度不收录

    互联网信息越来越多,这导致百度对于页面的要求提高,如果百度判断某页面的质量过低,那么就很可能不收录该页面。

    解决办法:请确保页面内容的质量。

    关于百度不收录的问题,本文从五个方面简要的说了说百度不收录的原因,且给出了对应的解决办法。如果你的网站百度收录不正常,不妨从这五个方面去进行seo诊断,或许就能找到百度不收录的真正原因。

    最后再补充一句,一般新域名都有一定的沙盒期,如果没有收录也不要着急,做好站内站外优化

  • 网站外链应该怎么发[外链发布小技巧]

    网站外链应该怎么发[外链发布小技巧]

    本地测试正常后上线:大部分的人都喜欢先把网站上线,然后一边改网站一边运营网站,搜索引擎收录网站是可以通过各种渠道,其中包括:外链,浏览器,使用的软件,提交等,所以只要网站上线,搜索引擎是很容易知道你的网站,如果你先上线后改网站,容易被搜索引擎认为你网站未改版成功,延长考核期,另外上线之前最好准备3-5篇文章,可以让搜索引擎第一次抓取网站的时候识别到内容,以免让搜索引擎判断为空白网页。

    标题关键词描述:标题根据百度下拉框的需求和网站能够满足的需求填写即可,关键词和描述可不填让搜索引擎自动识别,如果填写描述和关键词,那最好是带有关键词需求的描述。

    图片[1] - 网站外链应该怎么发[外链发布小技巧] - 长江技术博客

    外链:

    只要是可以引到相关流量的外链均可以做,一来可以推广网站的知名度,二来可以让搜索引擎判断你的网页需求比较大,有流量至少可以证明有需求。

    内容:

    更新的内容主要根据主关键词需求来更新,比如:网站主关键词为SEO优化,那么内容最好更新关于SEO教程的相关内容,比如:SEO优化第一章+标题,第二章+标题等。

    另外也有不少SEO网站优化少的网站是利用内容文章来做排名的,那么文章的标题就主要是根据长尾关键词来编写,内容页不适宜选择主关键词,从URL的优势上来讲,自然是竞争不过别人用首页做优化的,比如关键词:网站快速收录,我们可编写文章:如何让网站快速收录,从关键词的需求来说,快速收录的方法是用户最关心的,所以标题拟成这个一定没有错,但内容最好也是根据标题,不要跑题。

    推广:我们回头想几个很简单的问题,首先百度为何会给你排名,给你排名的原因很简单,百度要么就是要流量,要么就是要钱,自然,我们做搜索引擎优化的是不可能给钱给百度的,剩下的就是给百度流量,百度才会给你排名,那么问题来了,我们如何给百度流量。

    1,当用户第一次搜索某一个关键词,第二次搜索你的品牌词,你会发现百度的搜索结果页面URL有两个关键词,这就是百度的一个数据记录,所以当用户多次这样搜索,自然也就说明了,你网站满足这个关键词的需求是最大的,排名自然也就上来了,百度自然也从你的网站上赢得了流量。

    2,土豪式用广告砸出品牌,常德SEO网站优化自然也就有了搜索引擎的流量,大家都知道,论排名,搜索引擎自然原意给一个正规有实力的公司网站排名,不会随意给出一个个人网站,甚至没有任何实名的网站,但如今大部分的网站都是有实名制并且正规,所以这个时候,搜索引擎就选择品牌大的网站,比如:你做商城,搜索引擎会给京东也不会给你,因为百度不希望用户在使用搜索引擎购物的时候被骗,而京东是有实际保障的。

    3,最后一个就是我们使用的百度广告联盟,最近百度推出百度文字广告,主要是用户点击网站内容的锚文字,进入百度搜索结果页,但百度显示的内容都是你网站的内容,所以使用此工具对搜索引擎优化和排名都有非常好的帮助。

    作为一个seo新手,外链一直让我们很困惑,做了那么多外链很容易被册除,今天我就总结SEO发外链快速提升排名的方法,给新手朋友们作为一个参考,都知道做外链很重要,一般有两种方式,一种是纯文本的做另外一种是锚文字的做法,有效的外链能增加网站的收录,对其所做的锚文字进行投票提升排名,当然所做的外链能点击链接到网站,才算是一个有效的外杭州SEO网站优化链,只要我们平时掌握了这种方法就可以快速的把我们的网站推到首页,那么。

    我就和大家分享一下在哪些地方可以留外链,从而辅助网站的SEO优化。

    友情链接

    友情链接无疑是作用最大的外链,有些情况下一个友情链接的作用可能等价于几百个甚至上千个论坛外链的作用,所以说要想明显地提升网站权重或是关键词排名,最有效的办法就是获得高权重的友情链接。

    在实际工作中主要是通过以下几个方面来判断友情链接的质量:1,友情链接页面的PR值,2,友情链接网站与自己网站的相关性,这个是搜索引擎非常看重的一点,如果你的网站是一个化妆品网站,而对方网站是一个重型机械的网站,那么效果不会太好,3,友情链接页面导出链接的数量,如果对方做了太多的友情链接,效果势必会下降,4,友情链接页面的内容更新量,如果友情链接页面的内容经常更新,搜索引擎经常来抓取这个页面,那么效果就比较好,如果是一个单独的友情链接页面,基本没有什么内容更新,效果就比较差,5,网站首页的快照日期,快照日期一定程度上反映了对方搜狗网站关键词快速排名优化网站的权重和更新速度,快照日期越新越好,6,看友情链接网站的Alexa排名,收录量,外链数等。

    论坛

    论坛外链是除了友情链接之外效果最好的一种方式,尤其是在一些高权重的论坛留外链,在选择论坛的时候,我们要注意两点:一是论坛与我们网站的相关性要高,二是论坛的人气要高,每天的发帖量要大,只有在这样的论坛留外链,才更有可能被搜索引擎认为是有效的外链。

    论坛留外链也有几种方式,分别是”在主帖中留外链”,”在回复中留外链”,”在签名中留外链”,如果是在主贴中留外链,最好是采用软文的形式,因为直接发广告是很容易被论坛版主删除的,在回复中留外链,一定要回复一些有价值的内容再附上外链,千万不要只回复广告信息,这样也是容易被删除的,作者认为签名中留外链是一种非常好的留外链方式,效果和直接发帖留外链差不多,而且还不会被删除,因此大家一定要好好利用这种方式。

    有些论坛还专门开设有软文区,广告区,蜘蛛区这样的版块,这种版块就是专门为推广人员准备的,是一个值得好好利用的地方。

  • 绿豆pro前端APP源码v5.1.7编译教程

    绿豆pro前端APP源码v5.1.7编译教程

    绿豆pro前端APP源码 编译教程全图文操作萝卜白菜app通用:

    图文一:

    打开前端加载项目后,选择图下文件名为app.java文件打开操作修改位置如下:

    public static String SDKID = "6416";  //媒体ID
    public static String UMENG_KEY = "62387db8242477110c5bb"; //友盟统计
    public static String BASE_URL = "http://你的后台对接域名";

    图文二:

    打开前端文件名为MainActivity.java后,选择修改位置如下: 

    BottomNavigationView bnv_main;
    private String path = "http://对接域名/1.txt";

    如短视频不想要也可以直接去除,去除方法下期再说~~~~~~~   

    图文三:

    打开前端app文件夹下的build.gradle工程文件,选择修改位置如下:

    defaultConfig {
            applicationId "cn.yuenos.com" //包名
            minSdkVersion 22
            versionCode 503
            versionName "5.1.7" //版本号

    版本号如升级可修改信息为目前的多一级 反正不要比现在的版本低就好
    不然会一直弹出升级提示.

    fileName = " 包名为英文名成${variant.versionName}.apk"

    图文四:

    打开前端\app\src\main\res\values\strings.xml文件,选择修改位置如下:

    <string name="app_name">多啦咪</string> //app名称

    图文五:

    打开Android Studio 顶部第二个edit进入找到find如图 查找替换功能: 

    图文六:

    输入关键词 搜索查找替换: 

    输入搜索例如:插屏、信息流、开屏、激励这几个关键词
    即可找到对应的id位置,修改成你的id就好了
    总共5个信息流占2个

    图文七:

    修改完以上步骤后;进入到启动图跟logo图标替换成你自己的:

    替换路径文件为;logo图标;\app\src\main\res;

    mipmap-hdpi、mipmap-mdpi、mipmap-xhdpi、mipmap-xxhdpi、mipmap-xxxhdpi 五个文件夹为logo图标文件夹,

    启动图标文件为:\app\src\main\res\drawable\ lanch_bg2.png 图片文件

    图文八:

    前端修改完后,进入打包编译步骤:

    首先第一步找到以下进入选项

    Build选项 >Generate Signed Bundle / APK…. 点击进入 

    图文九:

     前端修改完后,进入打包编译步骤:

    首先第一步找到以下进入选项

    图文十:

     进入此页面后,选择新建秘钥证书,进入到新建页面,填写信息,

    填写完成后确定即可,然后进入下一步编译环境就好了 

  • 如何防御CC攻击和DDos攻击[教学篇]

    如何防御CC攻击和DDos攻击[教学篇]

    随着互联网的发展给我们带来极大的便利,但是同时也带来一定的安全威胁,网络恶意攻击逐渐增多,很多网站饱受困扰,而其中最为常见的恶意攻击就是cc以及DDos攻击。网站遭受攻击后,会直接影响到用户访问和蜘蛛对网站的爬取,用户访问量,甚至网站的收录、权重都会受到影响。

    站长网站被攻击了怎么办?今天带大家科谱一下什么叫CC攻击或是DDos攻击,以及如何做好防御。

    1 CC攻击、DDos攻击是什么

    DDOS是英文Distributed Denial of Service的缩写,意即”分布式拒绝服务”,DDOS的中文名叫分布式拒绝服务攻击,俗称洪水攻击。DoS的攻击方式有很多种,最基本的DoS攻击就是利用合理的服务请求来占用过多的服务资源,从而使合法用户无法得到服务的响应。

    CC主要是用来攻击页面的。大家都有这样的经历,就是在访问论坛时,如果这个论坛比较大,访问的人比较多,打开页面的速度会比较慢,访问的人越多,论坛的页面越多,数据库压力就越大,被访问的频率也越高,占用的系统资源也就相当可观。

    2 CC攻击和DD攻击的差别

    用专业术语而言便是,一个是WEB传输层拒绝服务攻击(DDoS),一个是WEB网络层拒绝服务攻击(CC),传输层便是运用肉食鸡的总流量去攻击总体目标网址的服务器,对于较为源头的物品去攻击,服务器偏瘫了,那么运作在服务器上的网址毫无疑问也不可以一切正常浏览了。而网络层便是大家客户看获得的物品,就例如网页页面,CC攻击便是对于网页页面来攻击的,CC攻击自身是一切正常请求,网址动态性网页页面的一切正常请求也会和数据库查询开展互动的,当这类”一切正常请求”做到一种水平的情况下,服务器便会回应不回来,进而奔溃。

    表现:被CC和DD后最直接的表现是网站页面打不开,

    CC攻击的是服务器资源,攻击者控制某些主机不停地发大量数据包给对方服务器造成服务器资源耗尽,服务器的CPU和内存资源会被占满,而长时间硬件的CPU高负载,等于CPU一直在最大功耗下工作,所以哪怕不考虑服务器业务系统受影响,从硬件层面来看,都很危险,搞不好散热器都蹦掉

    而DDoS攻击打的是网站的服务器,是对IP的攻击,其实就是黑客对我们网站web服务器发送大流量请求来堵塞我们的80端口,导致用户无法正常访问,被DD后一般服务器的带宽会飙的非常高,结果就是主机商进行黑洞引流,自损IP。

    3 被攻击了怎么办

    首先基于网站监控情况,确认是被CC还是被DD了。

    图片[1] - 如何防御CC攻击和DDos攻击 - 长江技术博客

    如果是DDOS攻击,建议直接换服务器或者联系服务器商换IP地址,因为是针对IP地址的攻击,您把源服务器怎么做防御都是无效的,除非升级高防(一般有钱人的方式),换上新IP套上CDN隐藏IP,这样一般的DDOS就能防住。

    如果确定是CC攻击,也是建议使用CDN加速,国外推荐CF CDN也是免费的哦,打死不暴露您源IP的良心CDN商家。你也可以使用宝塔里面的Nignx防火墙插件,增强模式下一般的CC都能防住。(注意,宝塔nginx防火墙不要长时间开着,实现测试中发现会影响蜘蛛对网站的爬取

    图片[2] - 如何防御CC攻击和DDos攻击 - 长江技术博客
    宝塔防火墙设置

    4 如何防御CC、DD攻击

    为了避免被攻击,建议在网站上线前就做好相关防御措施:

    • 新网站上线前必须做CDN,防止服务器源IP暴露
    • 关闭不需要的端口
    • 禁Ping
    • 限制同时打开的Syn半连接数目(设置运行环境)
    • 缩短Syn半连接的time out 时间
    • windows服务器漏洞太多尽量别“裸奔”
    • 及时更新系统补丁防止被鸡肉
  • 使用Fofa确定网站真实IP地址的小技巧

    使用Fofa确定网站真实IP地址的小技巧

    介绍

    我们想确定一个网站的真实IP地址,通常现在网站都会使用https协议,用到SSL证书是必不可少的,绝大多数企业证书都是通配符证书,因此我们可以把证书的序列号拿下来然后搜索这个证书用在了哪些业务里,然后如果部分业务中没有使用CDN或者没有覆盖到CDN,源IP地址就显示出来了!

    Fofa

    快捷入口

    教程

    这里以知乎网站为例,我们先访问网站,然后按F12打开开发者工具,点击Security

    图片[1]-使用Fofa确定网站真实IP地址的小技巧-FancyPig's blog

    点击View certificate,查看证书详情

    图片[2]-使用Fofa确定网站真实IP地址的小技巧-FancyPig's blog

    这里有详细信息,其中我们可以看到序列号

    图片[3]-使用Fofa确定网站真实IP地址的小技巧-FancyPig's blog
    0aebda9950b9ae3faaa3a00e68fc5565

    然后,我们使用在线转换工具,将16进制的序列号转换为10进制

    14516903431790578896883864801849922917
    图片[4]-使用Fofa确定网站真实IP地址的小技巧-FancyPig's blog

    这时候Fofa的作用就来了,我们可以通过下面的语句来进行调查

    cert="14516903431790578896883864801849922917"
    图片[5]-使用Fofa确定网站真实IP地址的小技巧-FancyPig's blog
  • Dumping PHP Opcodes Protected by SourceGuardian

    Dumping PHP Opcodes Protected by SourceGuardian

    Intro

    In this article, we’ll walk through my process for revealing SourceGuardian-protected PHP bytecode. We’ll get into some PHP 5.4 internals since this is the version Nagios XI was built on. Also we’ll perform some static and dynamic analysis of the SourceGuardian loader extension. Finally, the end result is a modified version of the Vulcan Logic Dumper (VLD). Many thanks to Derick Rethans and all who contributed to VLD!

    Here is a brief outline of the topics to be covered:

    • PHP Bytecode
    • The SourceGuardian Loader
    • Vulcan Logic Dumper
    • Hooking zend_execute
    • Challenges encountered
    • Opcode Handlers
    • Analyzing Custom Handlers
    • My Solution

    Below is a protected file. The goal is to decode this into something we can analyze.

    Do you read SourceGuardian?

    Before we move onto analysis, let’s see a description of the SourceGuardian product. Their website says, “Our PHP encoder protects your PHP code by compiling the PHP source code into a binary bytecode format, which is then supplemented with an encryption layer.“

    PHP Bytecode

    Similar to other interpreted programming languages, PHP source code is compiled into bytecode. For example, the following PHP code:

    <?php
    echo "hello world";
    ?>

    Would be compiled into the below. Although, the below graphic is a visual representation of a zend_op_array. The Vulcan Logic Dumper (VLD) can be used to dump bytecode in this format. The output shows individual opcodes and their associated fields.

    Source: https://www.php.net/manual/en/internals2.opcodes.echo.php

    Here is another short example:

    <?php
    for($i=0; $i<3; $i++){
    echo "hi";
    }
    ?>

    Would be compiled into:

    Source: https://www.php.net/manual/en/internals2.opcodes.jmpnz.php

    As we go, keep in mind that source code is compiled into operations. I may call them instructions as well.

    sg_load()

    From now on, I’ll refer to SourceGuardian-protected files simply as “encoded” files, and SourceGuardian will be abbreviated as “SG”. When an encoded file is launched by the PHP interpreter, it is decoded by an SG “loader,” which is implemented as a PHP extension.

    Given that the encoder compiles the source code and encrypts the bytecode, the loader must decrypt and execute the compiled bytecode. The loader implements a key function called sg_load(), which does this. In all encoded files, you’ll find a call to this function at the end of the file.

    sg_load() is called in an encoded file

    My goal was to simply dump the original bytecode instructions with VLD.

    VLD

    Let’s check out how VLD works. We’ll start with an unencoded “hello world” example:

    <?php
    echo "Hello world!\n";
    ?>

    If we were to dump this with VLD, it would show:

    The catch is that VLD hooks zend_compile_file(), and this output is coming from there. After zend_compile_file() is called to compile the source code into a zend_op_array, the op array is dumped using the vld_dump_oparray() function. This is all handled in vld_compile_file().

    Source: https://github.com/derickr/vld/blob/483716c1626d05edb01ef9bc9a70046c327c5218/vld.c#L374

    If we were to run VLD as-is against an encoded file, the results would not give us what we want. It was not designed to decode protected files. Instead, we would see opcodes for the SG wrapper code along with a call to sg_load(). The input to sg_load(), containing encrypted bytecode, would not be dumped because it does not need to be compiled.

    Note: The VLD project description explicitly states it “can not be used to un-encode PHP code that has been encoded with any encoder.”

    SG wrapper code dumped. Notice the call to sg_load() at the top.

    Dumping Opcodes in zend_execute()

    An encoded file must be executed, right? The bytecode is decrypted then executed by zend_execute(). This is where I started to get my hands dirty.

    VLD already has a hook built in for zend_execute(), so if we modify VLD to dump the zend_op_array passed to zend_execute(), we can see the opcodes being executed. Note that VLD renames the function to vld_execute().

    static void vld_execute(zend_op_array *op_array TSRMLS_DC)
    #endif
    {
    php_printf("\nexecute()\n");
    vld_dump_oparray (op_array TSRMLS_CC);
    old_execute(op_array);
    }

    Modified Vld.c

    In order to have a controlled test environment, I created some sample files and encoded them. I started with hello.php from before.

    Here is the result of running the modified VLD against hello.php.

    It clearly executed just fine. But why are there no opcodes shown?

    Empty Opcode Dump

    I needed to start debugging to see what was going on under the hood. First off, a zend_op_array is passed as an argument here.

    static void vld_execute(zend_op_array *op_array TSRMLS_DC)

    Let’s see what the structure looks like:

    Source: https://github.com/php/php-src/blob/09d2b01f384dee54f0348c865a6b2e3c85d26ebd/Zend/zend_compile.h#L53

    Source: https://github.com/php/php-src/blob/09d2b01f384dee54f0348c865a6b2e3c85d26ebd/Zend/zend_compile.h#L255

    Now, what does vld_dump_oparray() do with it? This is defined in srm_oparray.c. Quite a bit happens, in fact. It analyzes the branches, formats the output and dumps the opcodes in the array. There is a loop that iterates over each zend_op in the opcodes member and calls vld_dump_op().

    Here is the zend_op structure.

    Source: https://github.com/php/php-src/blob/09d2b01f384dee54f0348c865a6b2e3c85d26ebd/Zend/zend_compile.h#L54

    Source: https://github.com/php/php-src/blob/09d2b01f384dee54f0348c865a6b2e3c85d26ebd/Zend/zend_compile.h#L106

    Okay, so what does vld_dump_op() do? Essentially, it inspects the specified zend_op and outputs the relevant pieces. One unusual thing is this: the lineno is always 0.

    In comes the debugger!

    All debugging was performed in the GNU Debugger (GDB). I set a breakpoint on execute() so we can inspect the op_array and opcodes contained within. I’ve left out the SG wrapper code dump and excessive debugger output. Something to note is that execute() must be hit twice because the first call to execute is for the wrapper code, and the second call executes the bytecode we’re after.

    $ gdb php
    Reading symbols from php...
    (gdb) b execute
    Breakpoint 1 at 0x36f760: file php-src/Zend/zend_vm_execute.h, line 343.
    (gdb) r -dvld.dump_paths=0 -dvld.execute=0 hello.php
    Starting program: /usr/local/bin/php -dvld.dump_paths=0 -dvld.execute=0 hello.php
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    . . . <snip> . . .Breakpoint 1, execute (op_array=0x7ffff5b7e918) at php-src/Zend/zend_vm_execute.h:343
    343 {
    (gdb) c
    Continuing.execute()
    filename: hello.php
    function name: (null)
    number of ops: 3
    compiled vars: none
    line #* E I O op fetch ext return operands
    --------------------------------------------------------------------
    Breakpoint 1, execute (op_array=0x7ffff5b85340) at php-src/Zend/zend_vm_execute.h:343
    343 {
    (gdb) p op_array
    $1 = (zend_op_array *) 0x7ffff5b85340
    (gdb) p *op_array
    $2 = {type = 2 '\002', function_name = 0x0, scope = 0x0, fn_flags = 134217728, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, refcount = 0x7ffff5b805f8, opcodes = 0x7ffff5b7ea18, last = 3, vars = 0x0, last_var = 0, T = 0, brk_cont_array = 0x0,
    last_brk_cont = 0, try_catch_array = 0x0, last_try_catch = 0, static_variables = 0x0, this_var = 4294967295, filename = 0x7ffff5b7eab8 "hello.php", line_start = 0, line_end = 0, doc_comment = 0x0, doc_comment_len = 0, early_binding = 4294967295,
    literals = 0x7ffff5b85440, last_literal = 2, run_time_cache = 0x0, last_cache_slot = 0, reserved = {0x555555f4e450, 0x0, 0x0, 0x0}}

    Take note of a few things here in the op_array. Last = 3, which makes sense, there are 3 operations. It’s also weird that line_start and line_end are both 0 though. Let’s look at the individual zend_op’s.

    (gdb) p op_array->opcodes[0]
    $4 = {handler = 0x7ffff4a09280, op1 = {constant = 4122471032, var = 4122471032, num = 4122471032, hash = 140737315859064, opline_num = 4122471032, jmp_addr = 0x7ffff5b7ea78, zv = 0x7ffff5b7ea78, literal = 0x7ffff5b7ea78, ptr = 0x7ffff5b7ea78}, op2 = {constant = 0,
    var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, extended_value = 0, lineno = 0, opcode = 42 '*',
    op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}(gdb) p op_array->opcodes[1]
    $5 = {handler = 0x5555558dfaa0 <ZEND_ECHO_SPEC_CONST_HANDLER>, op1 = {constant = 4122498112, var = 4122498112, num = 4122498112, hash = 140737315886144, opline_num = 4122498112, jmp_addr = 0x7ffff5b85440, zv = 0x7ffff5b85440, literal = 0x7ffff5b85440,
    ptr = 0x7ffff5b85440}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0},
    extended_value = 0, lineno = 0, opcode = 40 '(', op1_type = 1 '\001', op2_type = 0 '\000', result_type = 0 '\000'}(gdb) p op_array->opcodes[2]
    $6 = {handler = 0x5555558cd390 <ZEND_RETURN_SPEC_CONST_HANDLER>, op1 = {constant = 4122498152, var = 4122498152, num = 4122498152, hash = 140737315886184, opline_num = 4122498152, jmp_addr = 0x7ffff5b85468, zv = 0x7ffff5b85468, literal = 0x7ffff5b85468,
    ptr = 0x7ffff5b85468}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0},
    extended_value = 0, lineno = 0, opcode = 62 '>', op1_type = 1 '\001', op2_type = 0 '\000', result_type = 0 '\000'}

    All three instructions have a lineno of 0.

    Looking at vld_dump_op(), it was clear why the ops are not being dumped.

    Source: https://github.com/derickr/vld/blob/483716c1626d05edb01ef9bc9a70046c327c5218/srm_oparray.c#L696

    I commented that if-block out. And this was the new output:

    Encoded

    Comparing this output to the original, unencoded file:

    Not Encoded

    Interesting. So the encoded sample has an additional JMP instruction at the beginning. Oddly, the JMP goes straight to the return though… that can’t be right. This didn’t make sense, so I created more samples.

    Comparing Samples

    Let’s look at a basic example with an if…else.

    <?php$num = rand(0, 1);
    if ($num == 1)
    {
    echo "1\n";
    }
    else
    {
    echo "0\n";
    }?>

    And here is the VLD output.

    Encoded
    Not Encoded

    Very interesting… the encoded sample output has 2 additional instructions, and the JMP is at the beginning again. Also, oddly, if you follow the instructions for the encoded output, it just doesn’t add up. First we jump to instruction 4, and then rand() is called. However, only 1 argument is passed to rand. Instruction 3 is not executed prior to the call to rand. Also you can see that the JMPZ is changed to a JMPZNZ. Either we jump to instruction 11 then instruction 3, which is a SEND_VAL. Or we jump to the ASSIGN instruction. None of it makes sense.

    There was a common trend I saw when analyzing sample after sample:

    • An initial additional JMP instruction
    • Some instructions were completely changed – e.g. JMPZ turned into JMPZNZ
    • Control flow via branching did not match the logic for an unencoded dump

    These observations led me to believe that there was some obfuscation going on.

    Opcode Handlers

    Back to the debugger. If you look at the op handlers, something sticks out. For reference, there are a variety of op handlers that know what to do with a specific opcode.

    (gdb) p op_array->opcodes[0]
    $4 = {handler = 0x7ffff4a09280, op1 = {constant = 4122471032, var = 4122471032, num = 4122471032, hash = 140737315859064, opline_num = 4122471032, jmp_addr = 0x7ffff5b7ea78, zv = 0x7ffff5b7ea78, literal = 0x7ffff5b7ea78, ptr = 0x7ffff5b7ea78}, op2 = {constant = 0,
    var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, extended_value = 0, lineno = 0, opcode = 42 '*',
    op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}(gdb) p op_array->opcodes[1]
    $5 = {handler = 0x5555558dfaa0 <ZEND_ECHO_SPEC_CONST_HANDLER>, op1 = {constant = 4122498112, var = 4122498112, num = 4122498112, hash = 140737315886144, opline_num = 4122498112, jmp_addr = 0x7ffff5b85440, zv = 0x7ffff5b85440, literal = 0x7ffff5b85440,
    ptr = 0x7ffff5b85440}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0},
    extended_value = 0, lineno = 0, opcode = 40 '(', op1_type = 1 '\001', op2_type = 0 '\000', result_type = 0 '\000'}(gdb) p op_array->opcodes[2]
    $6 = {handler = 0x5555558cd390 <ZEND_RETURN_SPEC_CONST_HANDLER>, op1 = {constant = 4122498152, var = 4122498152, num = 4122498152, hash = 140737315886184, opline_num = 4122498152, jmp_addr = 0x7ffff5b85468, zv = 0x7ffff5b85468, literal = 0x7ffff5b85468,
    ptr = 0x7ffff5b85468}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0},
    extended_value = 0, lineno = 0, opcode = 62 '>', op1_type = 1 '\001', op2_type = 0 '\000', result_type = 0 '\000'}

    Notice in opcode 0 that the handler address is in a different address space than the other two opcode handlers. 0x7ffff4a09280 vs 0x5555558dfaa0 or 0x5555558cd390. Also, opcode 0 doesn’t seem to have a symbol associated with the address. On the other hand, opcodes 1 and 2 have handlers that point to ZEND_ECHO_SPEC_CONST_HANDLER and ZEND_RETURN_SPEC_CONST_HANDLER.

    Let’s take a look at the address ranges for loaded libraries:

    0x7ffff4a09280 belongs to ixed.5.4.lin, which is the SG loader extension.

    The other two handlers are mapped within the PHP executable. This is quite curious. The first jump instruction handler points to a function contained in the SG loader extension. We’ll set a breakpoint in there and let execution continue.

    (gdb) b *0x7ffff4a09280
    Breakpoint 2 at 0x7ffff4a09280
    (gdb) c
    Continuing.

    Breakpoint 2, 0x00007ffff4a09280 in ?? () from /usr/local/lib/php/extensions/no-debug-non-zts-20100525/ixed.5.4.lin

    I prefer intel over at&t syntax. So we set the flavor.

    (gdb) set disassembly-flavor intel
    (gdb) disas
    No function contains program counter for selected frame.

    Weird. Let’s try disassembling a range. No need to read this. More on that later.

    (gdb) disas $rip,$rip+128
    Dump of assembler code from 0x7ffff4a09280 to 0x7ffff4a09300:
    => 0x00007ffff4a09280: push rbp
    0x00007ffff4a09281: movabs rsi,0xaaaaaaaaaaaaaaab
    0x00007ffff4a0928b: push rbx
    0x00007ffff4a0928c: sub rsp,0x8
    0x00007ffff4a09290: mov rdx,QWORD PTR [rip+0x210ff9]
    0x00007ffff4a09297: mov rbx,QWORD PTR [rdi]
    0x00007ffff4a0929a: mov rax,QWORD PTR [rdi+0x28]
    0x00007ffff4a0929e: movsxd rdx,DWORD PTR [rdx]
    0x00007ffff4a092a1: mov rbp,QWORD PTR [rbx+0x8]
    0x00007ffff4a092a5: mov rcx,rbp
    0x00007ffff4a092a8: mov rdx,QWORD PTR [rax+rdx*8+0xd0]
    0x00007ffff4a092b0: mov rax,QWORD PTR [rax+0x40]
    0x00007ffff4a092b4: sub rcx,rax
    0x00007ffff4a092b7: mov rdx,QWORD PTR [rdx]
    0x00007ffff4a092ba: sar rcx,0x4
    0x00007ffff4a092be: imul rcx,rsi
    0x00007ffff4a092c2: shl rcx,0x4
    0x00007ffff4a092c6: mov ecx,DWORD PTR [rcx+rdx*1]
    0x00007ffff4a092c9: lea rcx,[rcx+rcx*2]
    0x00007ffff4a092cd: shl rcx,0x4
    0x00007ffff4a092d1: lea rcx,[rax+rcx*1]
    0x00007ffff4a092d5: mov QWORD PTR [rbx+0x8],rcx
    0x00007ffff4a092d9: mov rcx,rbx
    0x00007ffff4a092dc: sub rcx,rax
    0x00007ffff4a092df: mov rax,rcx
    0x00007ffff4a092e2: sar rax,0x4
    0x00007ffff4a092e6: imul rax,rsi
    0x00007ffff4a092ea: shl rax,0x4
    0x00007ffff4a092ee: call QWORD PTR [rdx+rax*1+0x8]
    0x00007ffff4a092f2: mov QWORD PTR [rbx+0x8],rbp
    0x00007ffff4a092f6: add rsp,0x8
    0x00007ffff4a092fa: pop rbx
    0x00007ffff4a092fb: pop rbp
    0x00007ffff4a092fc: ret

    I stepped through this, and ultimately landed at the CALL instruction:

    0x00007ffff4a092ee: call   QWORD PTR [rdx+rax*1+0x8]

    Next, I stepped into this function call.

    (gdb) si
    ZEND_JMP_SPEC_HANDLER (execute_data=0x7ffff5b4c9e0) at php-src/Zend/zend_vm_execute.h:430
    430 {

    My, oh my. The SG custom JMP handler eventually called the ZEND_JMP_SPEC_HANDLER. There is a zend_execute_data structure passed as an argument as well. After a bit of fumbling around – starting and restarting the debugger – and scratching my head, I noticed something about the data structure passed to the Zend handler.

    Operand 1 to the current PHP operation (opline.. which points inside op_array->opcodes), had changed!

    Before entering the SG jmp handler
    After entering the zend jmp handler

    The jmp_addr is different! This explains why the control flow logic in the VLD opcode dumps don’t make sense. The JMP operands have been tampered with.

    At this point, I felt I needed to do some in depth analysis of the SG jmp handler.

    Source Guardian JMP Handler Analysis

    I opened ixed.5.4.lin in Hopper Disassembler. The JMP handler function is at offset 0x9280 in the file, and a cursory glance around revealed that there are 4 additional functions composed of similar logic. The usage of constant 0xaaaaaaaaaaaaaaab in each of them was a dead giveaway.

    I then realized that these were probably additional custom opcode handlers, and I would need to analyze each of them. My next task was to figure out which opcodes map up to which handlers. I did this by modifying the vld_dump_op() function to compare the current opcode structure’s handler address to the handler supplied by the Zend engine. If the handler’s address didn’t match up with the Zend handler’s address, it would print some output prior to dumping the operation’s fields.

    Added some debug statements

    This allowed me to determine some of the offsets of custom handlers and their corresponding opcodes. For example, here is a JMPZNZ:

    and a JMP:

    These offsets (0x280 and 0x3f0) correspond to the handlers in the Hopper disassembly. This was confirmation that the nearby functions were almost all surely custom handlers.

    At this point I knew I had to accomplish a couple things:

    • Map all custom handler functions to opcode values in the SG loader extension
    • Figure out how to “fix” the opcode structures so that vld_dump_op() would display the correct operands. This would make the control flow logic make sense.

    I decided to go with option 2 first. I wanted to prove that I could doctor up a basic JMP instruction before I moved on to other instructions. I’m going to run through the JMP handler, and we’ll talk about what’s happening. Once we’ve gone through this handler, the others are quite similar.

    Dynamic Analysis of the JMP Handler

    As we’ve seen, a JMP is placed at the beginning of each op_array. At the second invocation of execute(), we can print the first opcode to get the address of the JMP handler. It should look familiar.

    (gdb) p op_array->opcodes[0]
    $1 = {handler = 0x7ffff4a09280, op1 = {constant = 4122470936, var = 4122470936, num = 4122470936, hash = 140737315858968, opline_num = 4122470936, jmp_addr = 0x7ffff5b7ea18, zv = 0x7ffff5b7ea18, literal = 0x7ffff5b7ea18,
    ptr = 0x7ffff5b7ea18}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0,
    zv = 0x0, literal = 0x0, ptr = 0x0}, extended_value = 0, lineno = 0, opcode = 42 '*', op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}
    (gdb) b *0x7ffff4a09280
    Breakpoint 2 at 0x7ffff4a09280

    Next, we’ll continue into the handler function.

    (gdb) c
    Continuing.
    Breakpoint 2, 0x00007ffff4a09280 in ?? () from /usr/local/lib/php/extensions/no-debug-non-zts-20100525/ixed.5.4.lin

    Next, I dumped the registers to see what’s pointing where. My research was conducted on an x86_64 architecture – System V. This is important to know for recognizing function arguments.

    (gdb) info registers
    rax 0x7ffff5b7ea18 140737315858968
    rbx 0x7ffff5b4c9e0 140737315654112
    rcx 0x7ffff5b4ca70 140737315654256
    rdx 0x555555dbaa68 93825001040488
    rsi 0x0 0
    rdi 0x7ffff5b4c9e0 140737315654112
    rbp 0x7ffff5b809f8 0x7ffff5b809f8
    rsp 0x7fffffff93a8 0x7fffffff93a8
    r8 0x555555f53ec0 93825002716864
    r9 0x7ffff5b85770 140737315886960
    r10 0xfffffffffffff6bf -2369
    r11 0x55555594a9b0 93824996387248
    r12 0x1 1
    r13 0x3ff0 16368
    r14 0x7ffff5b4ca70 140737315654256
    r15 0x0 0
    rip 0x7ffff4a09280 0x7ffff4a09280
    eflags 0x246 [ PF ZF IF ]
    cs 0x33 51
    ss 0x2b 43
    ds 0x0 0
    es 0x0 0
    fs 0x0 0
    gs 0x0 0

    So the rdi register is pointing to 0x7ffff5b4c9e0. This is the first function argument for System V calling convention. If you look at zend_vm_execute.h, you’ll see that a handler takes an argument of type ZEND_OPCODE_HANDLER_ARGS.

    static int ZEND_FASTCALL  ZEND_JMP_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

    Really, it’s just a macro for a pointer to a zend_execute_data structure.

    #define ZEND_OPCODE_HANDLER_ARGS zend_execute_data *execute_data TSRMLS_DC

    Let’s print out the structure contents in GDB.

    (gdb) p *((zend_execute_data *)0x7ffff5b4c9e0)$4 = {opline = 0x7ffff5b7ea18, function_state = {function = 0x7ffff5b809f8, arguments = 0x0}, fbc = 0x0, called_scope = 0x0, op_array = 0x7ffff5b809f8, object = 0x0, Ts = 0x7ffff5b4ca70, CVs = 0x7ffff5b4ca70,symbol_table = 0x555555dbaa68 <executor_globals+392>, prev_execute_data = 0x7ffff5b4b060, old_error_reporting = 0x0, nested = 0 '\000', original_return_value = 0x7ffff5b4c438, current_scope = 0x7ffff5b4c458,current_called_scope = 0x7ffff5b4c478, current_this = 0x7ffff5b4c498, current_object = 0x7ffff5b4c4b8}

    This makes sense because the op_array has the same address as the argument to execute(). Here’s a look back at when we hit that break point.

    Breakpoint 1, execute (op_array=0x7ffff5b809f8) at php-src/Zend/zend_vm_execute.h:343

    Now that we know the argument is zend_execute_data, allow me to show you the important functionality in the function. For reference, here is the disassembly again:

       0x00007ffff4a09280: push   rbp
    0x00007ffff4a09281: movabs rsi,0xaaaaaaaaaaaaaaab
    0x00007ffff4a0928b: push rbx
    0x00007ffff4a0928c: sub rsp,0x8
    0x00007ffff4a09290: mov rdx,QWORD PTR [rip+0x210ff9]
    0x00007ffff4a09297: mov rbx,QWORD PTR [rdi]
    0x00007ffff4a0929a: mov rax,QWORD PTR [rdi+0x28]
    0x00007ffff4a0929e: movsxd rdx,DWORD PTR [rdx]
    0x00007ffff4a092a1: mov rbp,QWORD PTR [rbx+0x8]
    0x00007ffff4a092a5: mov rcx,rbp
    0x00007ffff4a092a8: mov rdx,QWORD PTR [rax+rdx*8+0xd0]
    0x00007ffff4a092b0: mov rax,QWORD PTR [rax+0x40]
    0x00007ffff4a092b4: sub rcx,rax
    0x00007ffff4a092b7: mov rdx,QWORD PTR [rdx]
    0x00007ffff4a092ba: sar rcx,0x4
    0x00007ffff4a092be: imul rcx,rsi
    0x00007ffff4a092c2: shl rcx,0x4
    0x00007ffff4a092c6: mov ecx,DWORD PTR [rcx+rdx*1]
    0x00007ffff4a092c9: lea rcx,[rcx+rcx*2]
    0x00007ffff4a092cd: shl rcx,0x4
    0x00007ffff4a092d1: lea rcx,[rax+rcx*1]
    0x00007ffff4a092d5: mov QWORD PTR [rbx+0x8],rcx
    0x00007ffff4a092d9: mov rcx,rbx
    0x00007ffff4a092dc: sub rcx,rax
    0x00007ffff4a092df: mov rax,rcx
    0x00007ffff4a092e2: sar rax,0x4
    0x00007ffff4a092e6: imul rax,rsi
    0x00007ffff4a092ea: shl rax,0x4
    0x00007ffff4a092ee: call QWORD PTR [rdx+rax*1+0x8]
    0x00007ffff4a092f2: mov QWORD PTR [rbx+0x8],rbp
    0x00007ffff4a092f6: add rsp,0x8
    0x00007ffff4a092fa: pop rbx
    0x00007ffff4a092fb: pop rbp
    0x00007ffff4a092fc: ret

    The Important Parts

    0x00007ffff4a09290: mov rdx,QWORD PTR [rip+0x210ff9]

    What happens is a pointer is dereferenced and the value is stored into rdx. Notice that the pointer address is calculated as a relative offset from the instruction pointer, rip.

    (gdb) p/x $rdx
    $1 = 0x7ffff4c1a640

    And it points into the SG loader … so it’s dipping into the loader to grab another pointer.

    (gdb) info proc mappings
    ...0x7ffff4c1a000 0x7ffff4c1b000 0x1000 0x1a000 ixed.5.4.lin...

    Prior to this instruction:

    0x00007ffff4a092d5: mov QWORD PTR [rbx+0x8],rcx

    Rbx points to opline (current operation), so this means the instruction sets opline->op1 to the value at rcx.

    (gdb) p *(zend_op *)$rbx
    $35 = {handler = 0x7ffff4a09280, op1 = {constant = 4122470936, var = 4122470936, num = 4122470936, hash = 140737315858968, opline_num = 4122470936, jmp_addr = 0x7ffff5b7ea18, zv = 0x7ffff5b7ea18, literal = 0x7ffff5b7ea18,
    ptr = 0x7ffff5b7ea18}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0,
    zv = 0x0, literal = 0x0, ptr = 0x0}, extended_value = 0, lineno = 0, opcode = 42 '*', op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}

    After the instruction executes, notice that op1 has changed, and the jmp_addr is a different address.

    (gdb) p *(zend_op *)$rbx
    $39 = {handler = 0x7ffff4a09280, op1 = {constant = 4122470984, var = 4122470984, num = 4122470984, hash = 140737315859016, opline_num = 4122470984, jmp_addr = 0x7ffff5b7ea48, zv = 0x7ffff5b7ea48, literal = 0x7ffff5b7ea48,
    ptr = 0x7ffff5b7ea48}, op2 = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0, zv = 0x0, literal = 0x0, ptr = 0x0}, result = {constant = 0, var = 0, num = 0, hash = 0, opline_num = 0, jmp_addr = 0x0,
    zv = 0x0, literal = 0x0, ptr = 0x0}, extended_value = 0, lineno = 0, opcode = 42 '*', op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}

    At the point when the zend vm opcode handler is called, the operands have been de-obfuscated. The actual JMP handler is called, and control flow can occur as it was originally intended to work.

    0x00007ffff4a092ee: call QWORD PTR [rdx+rax*1+0x8]

    Finally, the opline->op1 is restored back to its obfuscated value before the function returns.

    0x00007ffff4a092f2: mov QWORD PTR [rbx+0x8],rbp 

    So basically,

    1. The current op is de-obfuscated with its original operands.
    2. Then the zend vm opcode handler is called.
    3. And finally, the op is restored back into an obfuscated state.

    My strategy

    Now that we’ve seen how the most basic SG opcode handler (JMP) is implemented, I’d like to talk about my process for “fixing” the zend_op structures prior to dumping them with vld_dump_op(). Remember that the control flow logic doesn’t add up as of now. It took me a while to figure out a solid strategy for this.

    What I ended up doing was creating functions matching up to each of the SG handlers. I copied all of the assembly instructions, and modified the functions slightly. The modifications include the following:

    • construct a zend_execute_data object and pass it in as argument 1 (rdi)
    • dynamically calculate the address for this: mov rdx,QWORD PTR [rip+0x210ff9] … and pass it in as argument 2 (rsi)
    • instead of calling the zend vm handler, store that address as the handler in the opline (current instruction). This would cause the zend vm handler to be called instead of the SG handler.
    • don’t restore the operands! they’ve already been modified to reflect the correct ones. e.g. jmp destination will make sense

    Here is my function for fixing JMP operations. The instructions I’ve added or edited are bold:

    fix_jmp:
    mov rdx, QWORD PTR [rsi] # set rdx to point to some structure containing other pointers
    push rbp
    movabs rsi, 0xaaaaaaaaaaaaaaab
    push rbx
    sub rsp, 0x8
    mov rbx, qword ptr [rdi] # rdi points to opline
    mov rax, qword ptr [rdi+0x28]
    movsxd rdx, dword ptr [rdx]
    mov rbp, qword ptr [rbx+8]
    mov rcx, rbp
    mov rdx, qword ptr [rax+rdx*8+0xd0]
    mov rax, qword ptr [rax+0x40]
    sub rcx, rax
    mov rdx, qword ptr [rdx]
    sar rcx, 0x4
    imul rcx, rsi
    shl rcx, 0x4
    mov ecx, dword ptr [rcx+rdx]
    lea rcx, qword ptr [rcx+rcx*2]
    shl rcx, 0x4
    lea rcx, qword ptr [rax+rcx]
    mov qword ptr [rbx+8], rcx
    mov rcx, rbx
    sub rcx, rax
    mov rax, rcx
    sar rax, 0x4
    imul rax, rsi
    shl rax, 0x4
    # originally this would call ZEND_SPEC_JMP_HANDLER
    # but now, we'll just set the opline->handler to the real one
    mov rcx, qword PTR [rdx+rax+8]
    mov qword PTR [rbx], rcx
    # removed
    # this would reset op1 values to original "obfuscated" values
    # mov qword [rbx+8], rbp

    add rsp, 0x8
    pop rbx
    pop rbp
    ret

    This process was repeated for all of the custom operation handlers. A new function was created to fix various instruction types.

    Once I was able to fix all instruction types that SG seemed to have mangled, there was one final (or two, really) hurdle to jump over. The problem was that, since I was hooking zend_execute, I was only dumping opcodes that were actually being executed. So for example, the “main” part of a PHP file would be dumped because it was the logic that had to run. But as we’ll see, this leaves out some key components.

    Functions and Classes

    Any functions that were defined but were never executed would not be dumped. This was true for classes and their methods as well.

    We’ll look at an example with classes, since it tests both.

    <?phpclass ClassOne
    {
    function func_one()
    {
    echo "one";
    }function notused_one()
    {
    return 1;
    }
    }class ClassTwo
    {
    function func_two()
    {
    echo "two";
    }function notused_two()
    {
    return 2;
    }
    }$a = rand(1, 2);if ($a == 1)
    {
    $b = new ClassOne();
    $b->func_one();
    }else
    {
    $b = new ClassTwo();
    $b->func_two();
    }?>

    There are two classes, each with a method that could be used and a “notused” method that will absolutely not be called. Depending on whether rand() returns a 1 or 2, either ClassOne->func_one() or ClassTwo->func_two() will be executed. The output will indicate which method was called.

    As you can see in this output, ClassOne->func_one() was called. The main logic of the script is dumped along with func_one(). However, notused_one() is missing from the output as well as all of ClassTwo’s methods.

    The key to dumping the unused classes and functions is to access the compiler globals function table and class table. The only trick is that these tables need to be “fixed” prior to dumping, just like we’ve done before. Every function entry is a zend_op_array, so we can apply the same “fixing” logic to functions and class methods.

    Wrapping Up

    All in all, the main opcode dumping logic, handled in vld_execute, looks like the below snippet. First the main op_array is dumped. After this, any functions are dumped that exist in the function_table, and finally, the class_table is searched for methods, and these methods are dumped as well.

    // first, fix opcodes not contained in a function or class
    if (op_array->function_name == NULL || strlen(op_array->function_name) == 0) {
    fix_op_array(op_array);
    vld_dump_oparray (op_array TSRMLS_CC);
    }// now fix defined functions
    zend_hash_apply(CG(function_table), (apply_func_t) vld_fix_fe TSRMLS_CC);
    zend_hash_apply_with_arguments (CG(function_table) APPLY_TSRMLS_CC, (apply_func_args_t) vld_dump_fe, 0);// now fix defined classes and class funcs
    zend_hash_apply (CG(class_table), (apply_func_t) vld_fix_cle TSRMLS_CC);
    zend_hash_apply (CG(class_table), (apply_func_t) vld_dump_cle TSRMLS_CC);

    The “fix_op_array” function is responsible for “fixing” all of the op_arrays, and it is used inside vld_fix_fe as well. This function performs several tasks including calculating offsets within the SG loader extension, determining which opcodes to fix, and ultimately, calling the functions that were implemented to “fix” the op_arrays. Here is a switch case showing the opcode numbers that are handled. Notice that several opcodes can map to the same fix function.

    switch (execute_data->op_array->opcodes[i].opcode)
    {
    // 42
    case ZEND_JMP:
    // 100
    case ZEND_GOTO:
    fix_jmp(execute_data, sg_offset);
    break;
    // 46
    case ZEND_JMPZ_EX:
    // 47
    case ZEND_JMPNZ_EX:
    // 152
    case ZEND_JMP_SET:
    // 158
    case ZEND_JMP_SET_VAR:
    fix_jmpnz_ex(execute_data, sg_offset);
    break;
    // 45
    case ZEND_JMPZNZ:
    fix_jmpznz(execute_data, sg_offset);
    break;
    // 68
    case ZEND_NEW:
    // 78
    case ZEND_FE_FETCH:
    // 77
    case ZEND_FE_RESET:
    fix_new(execute_data, sg_offset);
    break;
    // 107
    case ZEND_CATCH:
    fix_catch(execute_data, sg_offset);
    break;
    default:
    break;
    }

    If you’re interested in viewing all of the code, take a look at the project on GitHub. The “fix” functions are all defined in fix_sg.S. Keep in mind that this is all tailored to the SG 5.4 Linux x86_64 loader extension. Additionally, to limit the length of output, I’ve coded things up so that no includes will be dumped.

    Before you leave, let’s see a fully decoded class.php. I’ve had to split the output up into multiple images due to the size.

    “main” function
    ClassOne
    ClassTwo and the output (“Two”)

    There you have it. By hooking zend_execute() and fixing opcodes using SourceGuardian’s own decoder logic, we can dump an encoded file with VLD’s functionality. As I said before, the decoder was implemented to target encoded PHP 5.4 files on an x86_64 Linux environment. If you find any bugs or see improvement opportunities, please feel free to reach out

  • PHP:vld扩展的安装与使用

    PHP:vld扩展的安装与使用

    一、安装

    1、下载官方插件安装压缩包

    官方网址:http://pecl.php.net/package/vld

    下载命令:

    wget http://pecl.php.net/get/vld-0.17.0.tgz
    

    注:下载的URL是在相对的版本链接上,点击右键,复制链接即可

    2、解包

    解包命令:

    tar zxvf vld-0.17.0.tgz 
    

    3、编译和安装

    进入解压后的vld目录:

    cd vld-0.17.0/
    

    扩展php扩展模块:

    phpize
    

    使用locate找php-config路径:

    locate php-config

    注:locate命令没有的话可以使用命令:【# yum -y install mlocate 】 安装后使用 【#  updatedb】 更新数据后可以直接使用

    配置编译vld的php-config路径(替换?): 

    ./configure --with-php-config=? --enable-vld

    编译安装:

    make && make install
    

    编辑php.ini,添加vld.so新扩展:

    extension=vld.so
    

    4.重启php配置生效

    二、使用

    注意:当有多个PHP版本时,运行php命令,需要指定装有vld扩展的php版本路径命令!

    1.linux多PHP版本下指定PHP版本执行命令?

    以php7.4版本为例,该版本执行文件命令路径为:

    /www/server/php/74/bin/php
    

    进入命令行的配置文件.bashrc,添加:

    alias php74=/www/server/php/74/bin/php

    就可以用php74 执行命令了!

    2.vld命令,显示opcode

    ①显示opcode,并显示运行结果

    php74 -dextension=vld.so -dvld.active=1 test.php

    ②只显示opcode

    php74 -dextension=vld.so -dvld.active=1 -dvld.execute=0 test.php
    

  • PHP:利用vld扩展SG11解密基础学习

    PHP:利用vld扩展SG11解密基础学习

    什么是SG11?

    • Source Guardian,一种PHP加密器,可以说是目前最好的加密方式了,多用于保护源代码不被盗取倒卖。

    • 它的代码特征是文件中包含:sg_load(

    • 搜索后,发现这类SG11解密方面的教程非常少,几乎没有。但也能看到有一些Decoder提供解密服务,价格基本在100-200元/文件。价格之贵,足以说明它的保密性了。

    解密原理

    通过安装PHP vld扩展,用操作码OP对PHP文件进行逆向解密。

    [content_hide]

    vld.c 文件
    
    //if (!VLD_G(execute)) {			
    //}
    
    vld_dump_oparray(&execute_data->func->op_array);
    return old_execute_ex(execute_data TSRMLS_DC);
    ----------------------------------------------------------------
    srm_oparray.c 文件
    
    #include "zend_smart_str.h"
    #include "ext/standard/php_var.h"
    
    static inline int vld_dump_zval_double(ZVAL_VALUE_TYPE value)
    {
    	return vld_printf (stderr, "%f", value.dval);
    }
    
    
    static inline int vld_dump_zval_array(zval* value)
    {
    	smart_str buf = {0};
    	php_var_export_ex(value,1,&buf);
    	smart_str_0 (&buf);
    	ZVAL_VALUE_STRING_TYPE *new_str;
    	new_str = php_url_encode(ZSTRING_VALUE(buf.s), buf.s->len);
    	int ret = vld_printf(stderr,"%s",ZSTRING_VALUE(new_str));
    	efree(new_str);
    	smart_str_free(&buf);
    	return ret;
    }
    
    case IS_ARRAY:          return vld_dump_zval_array (&val);

    [/content_hide]

  • 通过宝塔面板实现多端口建站与SG11解密

    通过宝塔面板实现多端口建站与SG11解密

    实践过程

    通过宝塔面板搭建网站

    说明 

    因为篇幅原因,在这里只写几个主要的小问题,也是我在之前第一次搭建过程中遇到的问题,具体搭建教程网上都有。

    • 在安装宝塔面板之前,请注意将云服务器的操作系统更换为 CentOS操作系统,因为我在初始配置中选的默认系统,结果不知何种原因,在安装宝塔面板后,无法安装Web服务器(Nginx)。3
    • 记得一定要在安全组配置里开放宝塔面板的端口(8888),否则打不开,也可以把其他常用端口打开。4
    • 小技巧:如何在没有域名及二级域名的情况下,通过不同的IP端口来实现访问不同的网站内容?
      1. 添加站点,随便输入一个域名(例:aliyun.com),创建数据库,提交。5
      2. 在设置-域名管理,添加域名(格式:IP:端口号)(ps:端口号尽量避开常用端口),然后添加,如下图。6添加成功,可以把之前的那个删掉。7
      3. 在安全组/防火墙中,开放上一步添加的端口号,只有配置了端口号,我们才能打开网站。8
      4. 在浏览器输入地址,网站创建成功。按照这样的步骤,我们就可以创建不同的IP端口地址进入不同的网站了。9

    SG11解密

    What?

    • Source Guardian,一种PHP加密器,可以说是目前最好的加密方式了,多用于保护源代码不被盗取倒卖。
    • 它的代码特征是文件中包含:sg_load(
    • 搜索后,发现这类SG11解密方面的教程非常少,几乎没有。但也能看到有一些Decoder提供解密服务,价格基本在100-200元/文件。价格之贵,足以说明它的保密性了。

    Why?

    因为最近买了一个源码,部分文件就是用SG11加密的,很想尝试给它破解了。(仅用于学习)

    How?

    结果国内SG11解密教程非常少,找了许久,才找到一个只有四小节的视频课程,这里就不放视频了,还有在外国网站上看到的 SG11解密教程,之后都会在无错源码上单独发出来。

    那么具体是怎么做的呢?

    1. 首先需要下载vld(PHP的扩展),然后把它上传到ECS服务器中,并解压。说明 什么是vld?是一个PHP扩展,它可以查看PHP程序的opcode,也就是操作码。10
    2. 通过一系列配置(配置较多,就不放在这了),安装成功。11
    3. 简单操作:将一个简单加密文件解密。
      1. 先写一个php文件,例如:Helloworld!12
      2. 输入命令php -dvld.avtive=1 index.php,然后就能看到它的操作码op。13
      3. 再来给index.php文件进行SG11加密。结果如下图。14
      4. 再次输入命令php -dvld.avtive=1 index.php ,如下图。15

    以上是我使用vld对SG11加密文件解码的一个基本操作,具体解密还需要一定的操作码知识和PHP知识,利用操作码对PHP文件进行逆向解密。