获取斗鱼直播视频的下载地址

斗鱼直播应该火了好一阵了,和每一个互联网新兴业务一样,人气激增到国家都专门为其制定管理政策了。对流行节拍有意而为之的后知后觉,让我近一两个月才关注了几个主播。女主播看脸蛋,也插科打诨来几个污段子,才美兼备的自是不多。男主播的话,就看他套路其他女主播的真人秀,或视频或夜店。

只是直播时间都太晚,看几次就觉得严重影响睡眠,能自动录播就好了。

网页里的播放器自然是用Flash开发的,P2P式的NetStream。如果找到主播房间号对应的NetStream传输地址就可以使用第三方软件去下载视频流了。虽然播放器的主程序是加密传输到本地后载入内存的,但毕竟Loader真正开始加载的时候,该解密的都解了。播放器外部代码还做了混淆,但主程序因为加密放下戒备,真Dump下来的话,可读性极高。

开源的FFDEC出现以后,SWF Decompiler和As3 Sorcerer都再没用过。直播开始后,FFDEC去Dump浏览器进程中的Flash文件,大小1.5M左右的就是解密后的播放器主程序。反编译可以看见代码本身包含调试信息,只要当前页面的URL中包含dydebug的字样,播放器就会调用浏览器的console.log输出不同阶段的中间结果,这当中就包含了NetStream流的地址,形如:

NetConnection连接状态:
NetConnection.Connect.Success 
Param.RtmpUrl =http://hdl3.douyucdn.cn/live   
Param.LiveID=602624rWYVytrxHL_550.flv?wsAuth=00f153fe25aa13a9e735f72774ae495a&token=web-0-602624-bb8f0c410da443bc88baa2c53e8d76c0&logo=0&expire=0

组合RtmpUrl和LiveID就得到了视频地址,直接粘贴到浏览器地址栏就下载视频到本地了。

正好新装了Visual Studio 2015,就写了个下载地址解析的工具,距离上一次使用C#写点什么已经四五年了。Anyway,webBrowser控件很方便,可以自动调用IE引擎打开附加dydebug的直播地址。由于console是浏览器自定义的调试模块,webBrowser作为一个控件需要自行实现一个console供Flash调用。其实也就是在页面加载完毕后,添加一段javascript,声明一个window.console.log,内部调用external.log就能传递消息调试信息给C#代码了。

解析程序是Windows 10的VS2015编译的,其他系统可能要安装.net运行时才能正常运行。输入房间号后,视网络状况,一分钟内可以解析出直播地址并放入剪贴板。

斗鱼直播地址解析程序下载:douyu

douyu

更多实现细节可参考主窗口的代码,为了运行清爽,webBrowser控件会隐藏,视频页面加载时会静音。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace douyu
{
    [System.Runtime.InteropServices.ComVisible(true)]
    public partial class Form1 : Form
    {
        [System.Runtime.InteropServices.DllImport("winmm.dll")]
        public static extern int waveOutGetVolume(IntPtr h, out uint dwVolume);
        
        [System.Runtime.InteropServices.DllImport("winmm.dll")]
        public static extern int waveOutSetVolume(IntPtr h, uint dwVolume);
        
        public Form1()
        {
            InitializeComponent();
            browser.ObjectForScripting = this;
        }

        public void log(String msg)
        {
            if(msg.Contains("NetConnection.Connect.Success"))
            {
                Match res = Regex.Match(msg, "RtmpUrl =(.+?) ");
                String rtmpurl = res.Groups[1].Value;
                res = Regex.Match(msg, "LiveID=(.+)");
                String liveid = res.Groups[1].Value;                
                String url_address = rtmpurl + "/" + liveid;
                /* considering the relocation (Thanks to lipinghao) */
                HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create(url_address);
                myReq.AllowAutoRedirect = false;
                WebResponse response = myReq.GetResponse();
                if (response.Headers["Location"]!=null)
                {
                    url_address = response.Headers["Location"];
                }
                Clipboard.SetDataObject(url_address);
                resultbox.Text = url_address;
                urlpath.Text = "下载地址已经复制到剪贴板";
            }            
        }

        private uint _savedVolume;
        private void button_parse_Click(object sender, EventArgs e)
        {
            waveOutGetVolume(IntPtr.Zero, out _savedVolume);
            browser.Navigate("http://www.douyu.com/" + urlpath.Text + "?dydebug");
            waveOutSetVolume(IntPtr.Zero, 0);
        }

        private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            HtmlElement head = browser.Document.GetElementsByTagName("head")[0];
            HtmlElement scriptEl = browser.Document.CreateElement("script");
            scriptEl.SetAttribute("text", "function hook_console() { window.console = new Object(); window.console.log = function(param){external.log(param)}}");
            head.AppendChild(scriptEl);
            browser.Document.InvokeScript("hook_console");
        }
        
    }
}

变革时别分神

这个假期回家有点早,也真的是后面的票都卖没了。也许是以天为单位的时差的缘故,除了旅途中短暂的喧嚣,世界甚至有些寂静。回想大学每次回家前,我总爱制定个时间表,紧锣密鼓、分秒必争,预习明年的课程或者写个程序练练手。研究生以后,说不上是看破红尘了还是怎么的,习惯就凭空消失了。怀着这段倍感亲切的记忆,我煞有介事地又写下了这个假期的时间表

望着新表,我觉得张口就能过度解释,拽很多理论进来。但还是很满意,下了车就逐条解释给最友好的听众,我妈。

虽然总看英文材料,但拼写能力真是递弱代偿,抽空抄抄单词是个简单的任务,练练字熟悉拼写,赚取第一笔成就感。然后看看书,随便哪本,毕竟书比网络快餐,收获的不仅仅是事实还有思想。

精彩的部分来了,11:00开始的一个小时是灵活时间段,姑且这么称呼好了。灵活时间段是本表区别以往的最大亮点,在这段时间里,可以和姥爷打牌,可以帮忙干活,并没有固定方向。一天中总会发生计划外的情况,尽量把他们安排在灵活时间段,既不会因为计划没完成产生负罪感,搞得一天心情都很糟糕,也能确保后面的计划照常执行。As you see,下午2:30是另一个灵活时间段。

午饭和晚饭的时间也相对靠后,家人和上班族当然不一样,即,计划时也要考虑周边人的情况,越少地影响他们越容易顺利执行。晚饭过后除了活动活动筋骨,foremost的任务就是完成家里电脑集体升迁Windows 10。回家前我就全副武装了U盘,常用工具和操作系统都备齐,毕竟家装的方正宽带(电信)表面提速暗地里断路。

现实是我妈实在懒得折腾去备份她电脑的资料,我只能循循善诱在D盘目录建了个bak文件夹,让她每天整理一点资料进去,打算最后再把bak拷到活动硬盘。这期间我大刀阔斧地给买了小米60寸、换了联通线路的宽带、给朋友爹的iMac重装了系统、买了新鞋,并通过数次谈话节目给姥爷从软文讲起,告诉他不能相信报刊上的医院信息,开药要适量,还有我的工作。到后来由于释放能量过多,甚至觉得空虚寂寞冷了。

不管怎样终于要开始升级Windows 10了,说说笑笑,我就重启进入硬盘分区步骤,把所有盘合并,准备分成三个区,再安装系统,恍然间,妈问,bak还没拷到活动硬盘吧。这下傻了,除了临行前她叮嘱的消消乐的作弊脚本拷走了,bak的内容全成了分区狂魔的刀下鬼。哎,准备了这么久,全白费啊,妈赶紧说没了就没了,更加剧了我的懊悔。

Windows 10安装完毕,期间无语。我只分了C盘用于操作系统,其余空间保持未分配的状态。一进入系统,赶紧试了试最近听说的一个磁盘恢复软件TestDisk,迷迷糊糊的用它恢复了分区信息。然后重启,果然Windows 10没了。重装Windows 10,D盘找回来了,bak也回来了。也许说了这么多都是变革,但初衷的升级Windows 10一溜号差点成了绝唱。

总结一下技术细节:

最初,C盘60G,D盘180G

重新分区时候

C盘50G,后面的空间没有分配所以没有改动

第一次装好Windows,用TestDisk恢复了原分区,由于C盘被破坏,Windows没了

分区再次变成C盘60G,D盘180G

第二次在C盘上安装系统,顺利进入,D盘也就赫然出现了,bak也在

Linux下编译WebKit和JSC

Safari和Chrome的内核都是webkit,无论是打算自己开发个浏览器还是在程序里集成完整的HTML解析功能,webkit都是为数不多的选择。特别是webkit分支中的jsc,可以命令行下解释执行javascript,真是想想就让人亢奋的玩物。编译环境选择的是Ubuntu 15.04 x86_64,由于玩心太重,所以首先考虑把jsc编译出来。

下载代码

有大概三种途径弄到代码,git,svn和直接下载tar.xz,我只试了后面两种

svn checkout https://svn.webkit.org/repository/webkit/trunk webkit

或者从webkitgtk直接下载tar.xz

准备编译环境

执行webkit/Tools/gtk/install-dependencies可以安装大部分缺失的库代码,另外再手动补下刀

sudo apt-get install libgstreamer*

其余的库要是还缺,apt-cache search+apt-get install 缺啥补啥吧

编译

如果下载的tar.xz的话,参考linuxfromscratch进入webkit目录执行以下代码,编译好的jsc位于./build/bin/jsc

sed -e 's/“/"/' -e 's/”/"/' 
    -i Source/WebCore/xml/XMLViewer.{css,js} &&

mkdir -vp build &&
cd        build &&

cmake -DCMAKE_BUILD_TYPE=Release 
 -DCMAKE_INSTALL_PREFIX=/usr 
 -DCMAKE_SKIP_RPATH=ON 
 -DENABLE_GEOLOCATION=OFF 
 -DPORT=GTK 
 -DLIB_INSTALL_DIR=/usr/lib 
 -DUSE_LIBHYPHEN=OFF 
 -DSHARED_CORE=OFF 
 -DCMAKE_C_COMPILER=/home/jack/afl/afl-gcc 
 -DCMAKE_CXX_COMPILER=/home/jack/afl/afl-g++ 
 -DBUILD_SHARED_LIBS=OFF 
 -DENABLE_MINIBROWSER=ON 
 -Wno-dev .. &&
make -j10

如果是svn得到的代码,进入webkit目录后执行编译脚本得到./WebKitBuild/Release/bin/jsc

./Tools/Scripts/build-jsc --gtk --makeargs="-j10"

WordPress自动安装本地升级包

自从安装了WordPress,升级工作一直不太顺利。本地搭建的LAMP环境测试没问题,但虚拟空间真刀真枪的时候就每每提示自动升级失败。试过手动升级,小心地覆盖各个文件夹时候心揪的稀碎啊,特别是WordPress不解风情地老推送升级包过来。

社区讨论最多升级失败都归咎于文件夹权限没设置好。但我这个调来调去发现就是虚拟主机访问WordPress资源站点,耽搁的太久,超时带来的副作用。

所以自己走VPN下载升级包是在所难免了。不过比起手动升级,把升级包下载到站点目录,然后让WordPress自动升级还是能省不少心,也就是自动安装本地升级包。唯一要做的就是稍微hack一下升级代码。以升级WordPress 4.4为例,修改代码示例:

// .wp-adminincludesclass-wp-upgrader.php

public function download_package( $package ) {
  ...
  //d:\www\root\xxxwordpress-4.4-zh_CN.zip是升级包传到虚拟主机后的绝对路径
  if ($package == "https://downloads.wordpress.org/release/zh_CN/wordpress-4.4.zip")
    $download_file = "d:\wwwroot\xxx\wordpress-4.4-zh_CN.zip";
  else
    $download_file = download_url($package);
  ...
}

OS X运行MATLAB时addpath函数报错解决方法

最近用新买的MacBook Pro运行MATLAB程序,在Windows下跑的好好的.m程序拿到OS X愣是报错。

addpath_error

这可真是X了狗了,我找到报错的那行,是一条添加work directory的code,用的MATLAB自带的addpath函数,是这么写的:

addpath('.aok');

上看下看左看右看也没看出哪里有错,妹的莫非是系统bug?不能吧… 盯着这条code足有10秒,聪明如我突然就顿悟了,原来是OS X中路径的斜杠与Windows的斜杠方向正好是反的,简单改下就成了:

addpath('./aok');    % 不要告诉我你没看出区别在哪

然后就是见证奇迹的时刻了,have fun!

Mounty写NTFS移动硬盘时文件损坏的解决方案

感恩节期间在美帝新买了个MacBook Pro,拆封完毕第一件事情就是安装各类需要的软件。为了防止意外,就把好不容易找来的破解版安装包都做了个备份。当我想把这些备份安装包转移到移动硬盘的时候,万万妹想到,意外发生啦!意外发生啦!!意外发生啦!!!

移动硬盘是NTFS格式的,这是Windows操作系统最常见的文件系统格式之一,but苹果的OS X对它的支持做得并不是太好(NTFS是微软的协议,可能是商业竞争之类的原因,微软没授权给苹果)。具体表现在,Mac对于NTFS格式的移动硬盘,默认是只能读不能写的。为此,我下载了度娘推荐的Mounty。本以为故事到这里就是happy ending了,哪里知道这里才是恶梦的开始

通过Mounty备份到移动硬盘里的东西通通都打不开了,图标是灰色的,修改日期统一显示为1984年,双击显示正在被占用无法读取。(以下图片来源于网络)

file_crash
occupied

情急之下热插拔了一次,之后硬盘就不能被mount了

not_remountable

还好,我就是传说中的解决问题小能手。通过一早上的尝试和搜寻Google,终于找到了解决办法。

继续阅读

武侠梦

大学起的生活很快让看电视节目成了再没真正拾起的旧习。可有闲有幻想的中学时代里那些金庸剧中的刀光剑影,仍时不时闪回在生活的很多瞬间。

下班路上,会兴奋地抓起着雪,攥成球后飞掷向敌方头目(路牌和栏杆);跆拳道课后的回家途中,要是遇到一段人少的小巷,会情不自禁地突展手臂企图用六脉神剑的光影照亮前方;又或是赶上大风天肆虐,有多少次幻想着用自己的掌风与之相抵;出门晚了的话,走的一快,深呼吸间就开始尝试施展凌波微步,哦,施展不成功~基本身法等级不够,还没做拿武学图谱的任务呢。

2000年算得上是网游元年,《石器时代》、《传奇》等屈指可数的网络游戏招揽了绝大部分有志投身互联网娱乐行业的莘莘学子们。我那会儿就用《金庸群侠传online》(网金)杀过时间。初一下学期开始,玩了快两年,什么点卡、道具、外挂、代练能买的都买了。最后半年,第一次听到其他玩家劝说:“如胶似漆,莫做固陋井底之蛙;回头是岸,迈向精彩大千世间”时开始萌生了去意(这句话放在当年其实一点不突兀,很多玩家多少有点古诗文言文的情节。客观原因是古风古气的游戏作派,而且金庸老前辈是那会儿中学生心目中深厚文字功底的代表,主观原因是经常为了考试题目背对联、背古文)。直到有一次琢磨着要汇款1000给代练公司的时候,突然虎躯一震,意识到大势已去,网金的故事已经跑偏了,该撤了。得益于那段传奇经历,今天对更为粘人的页游手游都不太感冒。

如果说网金提供了在虚拟世界做大侠的平台,游戏外挂就称得上是现实世界中的让你梦想成真的绝世武功。那会儿的神行太保、金庸通天、网金游侠,网金也疯狂等外挂都能让你手中虚拟人物在地图上快速飞跃,自动练习伐木、采矿等铸造神兵利器的技能,自动遇敌战斗获取学点,自动跟掌门学习武功,跨越游戏的限制任意驰骋。耗时乏味的练级时间全部可以让外挂代劳。外挂能通过挂钩游戏进程后强制执行特定代码片段跳过部分耗时操作,或按照游戏协议发送网络封包给服务器,直接完成目标任务。外挂开发者对于游戏代码的逆向分析几乎达到了完备的状态,每个NPC的编号、武功、属性依赖、任务条件、地图、技能的练习方法、哪些游戏规则可以适当逾越、哪些就要严格遵守才能不被封号以及游戏和服务器的通信的协议等。虽然那会儿也想自己写一个或者绕过验证免费用人家的外挂,但实在知之甚浅、无从下手。

十五年过去了,网金居然仍在线运营,外挂们也是一路相伴。就在初冬的一个下午,我闪身回眸间,又望见了尘封十五年的往事。加上最近脚扭伤了,没法去上跆拳道课,就重温了一下网金。这次打算好好折腾一下外挂,跨越时空为那个小小少年圆个梦。


网金游侠是一个仍然活跃的外挂,最近一次更新是在2015-7-28。除了能够快速飞跃、自动战斗、自动练习技能等一系列必不可少的元素,它甚至支持玩家通过自行编写lua脚本的方式扩展外挂的功能,完成自定义任务。构成网金游侠的主要文件包括GMK3.0.exe的外挂主控程序,IME32.dll的内挂代码以及很多包含NPC、物品、地图相关编号的文档。

购买了外挂的玩家运行GMK3.0.exe,填写自己的游戏账号,利用外挂启动游戏的主程序时,外挂会将内挂代码IME32.dll远程注入到游戏内存。IME32.dll载入后会修改游戏相关代码,挂钩特定函数。玩家正式登陆自己的游戏账号后,IME32.dll会根据登陆后的账号判断玩家是否注册了外挂,如果是已经注册的玩家,外挂会在游戏中创建一个辅助窗口供外挂参数调整。如果没有注册,就不会有界面弹出来让玩家使用外挂控制游戏。


最初的绕过思路是打算彻底摸清账号的验证方法,然后进行代码patch或者修改发送的验证包或者伪造服务器返回验证通过的数据包。GMK3.0和IME32.dll的外在表现形式不同,但和外挂服务器通信验证的协议是一样的。通过带有StrongOD的插件的OD避开检测,挂钩GMK的send和recv函数是可以看到验证数据的真身的。发送验证和服务器回复都是单次通信。想来即便不能解析其含义,用合法包替换也肯定能搞定。后来试了几次,发现即便相同包重放,返回结果都不一样,看来至少是加入了秒级别的时间戳,不分析算法是没法伪造了。本以为时至今日已经可以轻易干掉外挂的验证了。残酷事实是,逆向分析的能力确实相比那会儿的零有了提升,但外挂的保护方式也在生根发芽,直到今天我仍然不具备正面进攻的本事。

正面突破时最大的困难还是无法还原Themida虚拟机指令,所以外挂验证账号的数据包过程无从知晓。

虽然绕过验证是完美的解决方案,但想使用外挂功能也并不是就这么一条路。外挂启动游戏后除了注入了IME32.dll,还顺带注入了lua.dll用于执行自定义的lua脚本。外挂允许lua脚本可以调用的API多达150个,如:

void CallBoss(int nBoss)
功 能:呼叫商人
参 数:nBoss 商人编号
返回值:
描 述: 需在商人附近
--------------------------------------------------------------
void XiaoDian(int nMaster,int nKunfu,int nPoint)
功 能:消点
参 数:nMaster:师傅,nKunfu:武功,nPoint:所消学点
返回值:
描 述: 
--------------------------------------------------------------
void Buy(int nItem,int nPrice,int nCount)功 能:购买物品
参 数:nItem:物品编号 ,nPrice:物品价格 ,nCount:购买个数
返回值:
描 述:需先呼叫NPC,并正确填写购买价格
--------------------------------------------------------------
int BatConfig(int npc,int count,int delay,int grid,int speed,int timeout)
功 能:配置战斗信息
参 数:
返回值:
描 述: 
--------------------------------------------------------------
void BatInit()
功 能:初始化战场
参 数:
返回值:
描 述: 
--------------------------------------------------------------
void Log(char *str) 
功 能:输出文字
参 数:字符串
返回值:
描 述: 输出文字信息
--------------------------------------------------------------
void MoveTo(int map,int x,int y) 
功 能:移动到其他地图
参 数:map:目标地图,x:坐标y:坐标
返回值:直到移动完成
描 述: 角色移动到(其他图中)指定的坐标X,Y

自己写一段lua调用这些API就能利用外挂自动化完成很多操作,比如下面的代码就能自动练武功:

local XDEvt = 2; -- 消点事件
local XDMap = 1343; -- 消点地点
local XDX = 234;
local XDY = 278;
local XDMaster = 39778; -- 师傅ID
local XDKunfuID = 8002; -- 武功ID
function DoXiaoDian()
  if RoleXD() < 100 then
    return;
  end 
  if RoleXD() <= 10 then
    Log("Point is 10,n");
  return; 
  end
  if RoleJing() <= 10 then
    MoveTo(66,555,555)
  DoReset(3);  
  end 
  if RoleMap() ~= XDMap then
    MoveTo(XDMap,XDX,XDY); -- 移动到目标点
  end 
  EventID(XDEvt); -- 呼叫师傅事件
  Delay(1000);
  VerifyCode(); -- 效验码
  Delay(1000);
  XiaoDian(XDMaster,XDKunfuID,1); -- #8001:基本刀法100
  Delay(2000);
end
DoXiaoDian();

通过强制修改内存的方法做了一些初步的尝试,lua的接口无需外挂验证账号也是可以照常使用的。而且lua.dll并没有使用Themida进行虚拟机级别的保护,稍加分析可以发现这就是lua源码直接编译的结果,只不过版本号被删掉了。遂从lua的官网把所有版本的bin都下载下来,然后和外挂使用的lua.dll进行文件大小、输出函数、代码片段的比对,基本可以把范围缩小为VS2010编译的lua 5.2。下载了5.2的源码后,自行编译一个lua.dll进行替换,外挂运行正常。

IME32.dll中只有和账户验证相关的敏感代码进行了虚拟机保护,直接dump内存得到的bin中,即便没有修复地址,用IDA分析时仍然可以找到初始化lua库以及注册自定义API的过程:

int sub_774FA140()
{
  int i; // [sp+0h] [bp-4h]@1
  for ( i = 0; (&off_775CA7E8)[8 * i]; ++i ) // 循环注册lua脚本使用的API
    sub_77515540(0x2F57100, (int)(&off_775CA7E8)[8 * i], (int)*(&off_775CA7EC + 2 * i));
  return sub_774F6F50(sub_774D9950);
}

char __thiscall sub_77515540(int this, int a2, int a3)
{
  int v3; // ST0C_4@1
  v3 = this;
  // 注册自定义的API供lua调用
  lua_pushcclosure(*(_DWORD *)(this + 8), a3, 0); 
  lua_setglobal(*(_DWORD *)(v3 + 8), a2);
  return 1;
}

其中off_775CA7E8就是API的列表,每个API的名称和入口函数地址都清晰可见

CODE:775CA7E8 off_775CA7E8    dd offset aMainpath     ; "MainPath"
CODE:775CA7EC off_775CA7EC    dd offset sub_774F6FA0  ;
CODE:775CA7F0                 dd offset aIsrun        ; "IsRun"
CODE:775CA7F4                 dd offset sub_774F6F70
CODE:775CA7F8                 dd offset aMainloop     ; "MainLoop"
CODE:775CA7FC                 dd offset sub_774F6FC0
CODE:775CA800                 dd offset aDelay_0      ; "Delay"
CODE:775CA804                 dd offset sub_774F7030
CODE:775CA808                 dd offset aWaitforevent ; "WaitForEvent"
CODE:775CA80C                 dd offset sub_774F6FE0
CODE:775CA810                 dd offset aLog_1        ; "Log"
CODE:775CA814                 dd offset sub_774F7090
CODE:775CA818                 dd offset aSetpkstate   ; "SetPKState"
CODE:775CA81C                 dd offset sub_774F9670
CODE:775CA820                 dd offset aBatconfig    ; "BatConfig"
CODE:775CA824                 dd offset sub_774F96B0
CODE:775CA828                 dd offset aBatinit      ; "BatInit"
CODE:775CA82C                 dd offset sub_774F9910
CODE:775CA830                 dd offset aBatdircall   ; "BatDirCall"
CODE:775CA834                 dd offset sub_774F9990
CODE:775CA838                 dd offset aBatsetid     ; "BatSetID"
CODE:775CA83C                 dd offset sub_774F9830
CODE:775CA840                 dd offset aBatsetkunfu  ; "BatSetKunfu"
...

如果打算分析外挂的工作原理,这些API入口函数是很好的切入点。为节省体力,写个wrapper就能够方便的调用这些接口使用外挂功能就好。

调用lua相关函数全程都要维持一个实例指针lua_State *L,只有使用外挂初始化后的lua_State指针才能调用那些诱人的内建API。好在lua库创建都要通过luaL_openlibs (lua_State *L)完成,挂钩它就能得到这个实例指针。既然有了lua源代码,想动手脚真是手到擒来。

在lua源码中添加以下代码片段:

lua_State *Handle_L;
LUALIB_API lua_State *luaL_getL() 
{
  return Handle_L;
}

LUALIB_API void luaL_setL(lua_State *L) 
{
  Handle_L = L;
}

LUALIB_API void runCMD(char *luafile) 
{
  lua_State *L = luaL_getL();
  luaL_dofile(L, luafile);
}

LUALIB_API void luaL_openlibs (lua_State *L) {
  const luaL_Reg *lib;
  luaL_setL(L); //记录实例指针
...

这样通过调用runCMD()就能随时让外挂执行我们自行编写的lua脚本,并且lua脚本也可以使用外挂注册的API函数,尽享外挂的功能。另外再写一个图形界面的程序,根据自己预定的功能增加一些按钮,按钮事件仅仅是把每个功能用lua脚本完成,然后写入文件custom.lua。再通过CreateRemoteThread的方式调用游戏内存中的runCMD(“custom.lua”),就能避开验证使用外挂的功能了。

Linux下运行Flash独立播放器

Adobe很早就停止了对Linux下Flash的功能更新,和最新的Flash 18相比,Linux下的Flash仍然停留在11.2。

即便如此,Flash 11.2日常播放个简单的动画还是绰绰有余的,点击下载Flash独立播放器

直接运行绝对报错,形式类似:

(flashplayer:23126): GLib-GObject-WARNING **: instance with invalid (NULL) class pointer
(flashplayer:23126): GLib-GObject-CRITICAL **: g_signal_handlers_disconnect_matched: assertion 'G_TYPE_CHECK_INSTANCE (instance)' failed

官方也没详细说明,后来在64位的ubuntu 14上折腾一阵,发现安装以下的依赖库就OK了

sudo apt-get install 
  libglib2.0-0:i386 
  libxt6:i386 
  libxcursor1:i386 
  libnss3:i386 
  libgtk2.0-0:i386 
  libcurl3:i386 
  libxcursor1:i386

复用IDA的分析结果

IDA Pro分析二进制程序时可以加载PDB,因而可以很容易查看符号信息。

利用这个原理,如果已知二进制应用加载了开源库,可通过自行编译开源库得到PDB,再经由IDA载入目标二进制程序,获得符号信息,辅助逆向分析。如果IDA加载PDB文件出错就换一个版本再试,我就出现6.6、6.8都加载不成功的PDB,6.5反而可以。

现在尝试让Flash复用Tamarin的符号:

  1. 用VS编译后,得到avm.pdb
  2. IDA分析avm.exe过后再载入PDB
  3. 使用IDB2SIG插件生成PAT文件
  4. sigmake -nflash_symbol avm.pat flash.sig
  5. 这一步会得到EXC格式的文件,进去删掉第一行注释后重新执行上述命令就得到了flash.sig
  6. IDA分析Flash过后,载入flash.sig就能看到符号信息了

编译选项很有讲究,如果Flash是VS2008编译的,那生成Tamarin时也应该用VS2008。比如,Win 8.1的Flash是VS2012编译的,或者开启了CFG选项的话,那Tamarin就同样方式编译,这样的sig才会有更高的匹配率。

大迁徙

最开始弄黑捷克是在07年前后(一个曾经属于ASP+ACCESS的天下),选的也是当时流行的PJBLOG博客系统。眼看着互联网淘金热潮,就也照猫画虎地把喜欢看的魔术有一集写一篇,还从网上找来很多热门网文摘抄凑数,就想着流量折算Google Adsense好套点美金出来。再后来经历过实名制,域名备案,数据库错乱等一系列暴风骤雨的洗礼,直到2012年才算是真正安顿下来。虽然FckEditor写日志每次都得删改格式去匹配说不清道不明的样式风格,但觉得平静的生活来之不易,就此收刀,隐匿于和平饭店。

忙乱时向往闲暇,做梦都在计划,可真有空坐下来,就傻傻地坐到起身,哦还有个梦呢。因为成长于乱世,习惯于自寻平静,就像堆栈的push和pop,在两者见往复平衡才能让程序正常执行。平静久了,一味地pop,反而是在挥霍明媚的青春。想到这里,独裁者起身,手指欧洲,对着镜子歇斯底里的吼叫,让日耳曼的铁蹄踏平那里。

PJBLOG到Wordpress的转换,至少得处理三件棘手的事儿:日志,评论,链接的转换。

日志和评论的转换参考了http://maie.name/511.html,里面提到了一个mt.asp的转换程序,可以将原日志从ACCESS拽出来,格式化为Wordpress支持的一种导入文件。好容易找到了一份mt.asp,发现导出时就是无响应,大概是空间商做了限制,post请求的响应过大就中止,可能是出于dos的考虑吧。修改了mt.asp,让输出写入文件,然后用ftp连上去再下载吧。修改的内容还包括通过BASENAME让Wordpress的日志编号和原文章编号一致,方便重定向。这样导入日志后,index.php/xxx/就对应原来的article.asp?id=xxx了。另外要装上Wp-postview插件,这样还可以把浏览次数也迁移过来。mt.asp使用前要修改里面的https://www.hhjack.com.cn/为自己的博客地址。分享一下我的修改版本:点击下载

当然这只是第一步,后面就是漫长的人肉改日志了,因为很多日志用了html方式编写,导入后就很多编码问题,只能一篇一篇的修改了。我是新老站点同时在线,格式有错乱的,就直接贴老html代码过来,然后再调整。时至今日,已经不打算博取流量广告淘金了,只想着踏踏实实记录生活点滴,所以陈旧,错乱摘抄,失效外链,广告代码,遇到就毫不体面的全部删掉。

链接的转换是保证搜索引擎以前收录的URL仍然可以指向到新系统的日志。也就是用.htaccess文件实现301重定向,我这空间是windows系统,就用iis_rewrite的httpd.conf来跳转。httpd.conf文件内容为:

[ISAPI_Rewrite]
3600 = 1 hour
CacheClockRate 3600
RepeatLimit 32

RewriteCond %{REQUEST_URI} ^/article.asp$
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/%1/? [R=301,L]

RewriteCond %{REQUEST_URI} ^/default.asp$
RewriteCond %{QUERY_STRING} ^tag=(.*)$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/tag/%1/? [R=301,L]

RewriteCond %{REQUEST_URI} ^/default.asp$
RewriteCond %{QUERY_STRING} ^cateID=7$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/category/call-others/? [R=301,L]
RewriteCond %{REQUEST_URI} ^/default.asp$
RewriteCond %{QUERY_STRING} ^cateID=12$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/category/sch00l-days/? [R=301,L]
RewriteCond %{REQUEST_URI} ^/default.asp$
RewriteCond %{QUERY_STRING} ^cateID=9$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/category/magicshow/? [R=301,L]
RewriteCond %{REQUEST_URI} ^/default.asp$
RewriteCond %{QUERY_STRING} ^cateID=10$
RewriteRule ^.*$ https://www.hhjack.com.cn/index.php/category/x-life/? [R=301,L]

由于日志导出过程就保证了序号的同步,所以只要定向article.asp?id=xxx到index.php/xxx即可,tag的定向方法类似。最后还要处理文章分类的定向,这个是从序号到名字,所以只能每类写一条,还好条目不多,不然定向会很耗时。

大框架就是这些,剩下的就是安装插件,调整样式和过去尽量相仿,修改字体,迁移附件等了。在虚拟机军事演习了两周,实战三天两夜,finally。