复现CVE-2017-12858

libzip历史漏洞非常少,一只手能数完。CVE-2017-12858是二次释放导致的漏洞,其补丁代码非常简单:

即删掉了一段释放内存的代码。

如此看来二次释放中其中一次应该来自于此,那另一次呢,发现距离它不远处就有:

显然,问题的本因是_zip_buffer_free对同一buffer进行了前后两次释放。

不过两次释放都有各自的条件:

第一次要求from_buffer = 0;

第二次要求_zip_dirent_process_winzip_aes返回值为0。

 

达成第一个条件

两次调用位于_zip_dirent_read,函数开头有对from_buffer的赋值:

即传入的buffer如果为空,from_buffer就置0。查看libzip的源码,调用_zip_dirent_read时buffer为空的仅一处:

解析zip文件时,要保证进入_zip_checkcons函数,才能传入空buffer到_zip_dirent_read,继续寻找_zip_checkcons的调用位置:

幸运的是,所有的_zip_checkcons调用都集中在此,并且用zipcmp test.zip test.zip或其他方式调用libzip解析zip时,这个while循环体路径是一定会被执行的。现在只要保证while循环时进入_zip_checkcons即可。简单尝试后发现else分支里za->open_flags无论如何都无法包含ZIP_CHECKCONS标记,因而只能诉诸 if(cdir) 成立。cdir初始值为NULL,但在循环末尾有cdir = cdirnew的赋值。所以循环必须执行两次以上,并且第一次cdirnew要被_zip_read_cdir成功赋值,第二次循环时cdir才不为空。

要控制循环,得先搞清楚zip的解析流程。ZIP文件本身由多个PK\x\y的header组成。其中x和y取值不同,header的作用也不一样。一般文件和目录的描述header中x=3,y=4,而文件末尾标记x=5,y=6。另外位于central directory的x=1,y=2。循环时,zip_memmem函数用于定位zip文件的末尾标记EOCD_MAGIC(PK\5\6)。定位后,_zip_read_cdir会从该标记往前找到central directory尝试读取zip中存放的文件内容。

我尝试用winrar构造的zip文件,全都是几个PK\3\4后面接着几个PK\1\2然后就是PK\5\6收尾了。对于这样的zip文件,while循环走一轮就结束,没机会进入_zip_checkcons。如果手动伪造一个PK\5\6添加到末尾,虽然循环会走第二次,但第二次_zip_read_cdir读取失败,仍然走不下去。

最后尝试将一个普通的zip追加到自身末尾,cat xx.zip >> xx.zip,while循环走二轮且_zip_read_cdir返回值也正常了。之后调试跟踪_zip_checkcons,发现已经可以进入第一段释放代码了。

 

达成第二个条件

接下来要让_zip_dirent_process_winzip_aes返回0,进行第二次释放。跟进_zip_dirent_process_winzip_aes函数,代码如下:

发现只要de->comp_method为ZIP_CM_WINZIP_AES,后面的代码会有多次校验,有很大几率返回false,导致第二次释放。所以当务之急是将de->comp_method控制为ZIP_CM_WINZIP_AES(0x63)。回到调用_zip_dirent_process_winzip_aes的函数_zip_dirent_read中,在函数开头可以看到相关赋值:

调试定位后,发现只要将zip文件第9个字节改为0x63,zde->comp_method就会变为ZIP_CM_WINZIP_AES了,并且后续_zip_dirent_process_winzip_aes调用果然返回0,导致第二次释放。

gcc编译轶事

手头有台车机,想在上面写个程序,调用它自带的图形库来处理图片。图片库为libJpeg.so,测试程序大致如下:

jpeglib.h里面extern了jpeg_CreateDecompress,该函数来自libJpeg.so,所以如果要编译这段程序,想来正确的命令应该是(交叉编译,用到arm-linux-gnueabi-g++),提示的编译错误信息为:

蹊跷的是,错误信息出在libBasic.so,查看libJpeg.so和libBasic.so的依赖关系:

libJpeg.so从libBasic.so导入了函数,但编译器尝试链入libBasic.so时,由于缺少对dlopen等函数的定义导致错误。查看libBasic.so,虽然内部使用了dlopen,但导入库并没有引入dlopen所属的libdl.so,所以下面的命令仍然失败:

最后的解决方法是在wrapper.cpp中自己手动写入dlopen的调用代码,强行引入libdl.so,之后用上面的命令行就编过了。

 

复现

一开始以为是libBasic.so文件被做了手脚。实际上,编译sharedlibrary时既可以用-l显式指明导入库,也可以什么都不写,这样编写好的so就不含有导入库信息了。比如sm2.c代码如下:

两种编译方法结果如下:

也就是说如果有dlopen的调用操作,编译时是否有-ldl都可以编译通过,但产生的文件是不一样的。接下来再写一个sm.c调用sm2:

如果要构造libJpeg.so和libBasic.so相同的情形,编译sm的命令为:

之后写个调用程序,尝试调用sm.so就会复现这个问题:

但如果test.c改为:

上面的命令行就能编译过了。

 

结论

除了上面的方法,还可以在编译sm.so的时候,也选择不链入sm2:

这样即使不修改test.c,使用上述命令行也可以编译成功。值得注意的是,这种情况下-lsm -lsm2 -ldl的顺序是不能乱的,其他组合都不能编过。这样看来,很可能是开发者习惯不同,有人编译so时选择导入了其他第三方库,有人没有,这种不统一最后只能由可执行程序强行引入相关函数来修复。

OSX下安装mfoc

这次重装系统以后,打算尽可能不用虚拟机了,所有工具能移植的移植,不能移植的就找替代品,全部原生使用。brew可以安装大部分常用工具,个别brew更新不及时的工具就需要自己下载编译,比如mfoc

首先安装编译mfoc所需的环境工具:

然后下载libnfc,并编译安装:

如果一切顺利就可以下载安装mfoc了,方法类似:

为了正常运行mfoc以及nfc-list,osx系统还需要做一些额外的配置,否则会出现nfc-list找不到设备之类的情况

首先要关闭sip,开机按command+R进入恢复模式,然后执行

接下来在主系统增加一个kext,/System/Library/Extensions/DUMMY.kext/Contents/Info.plist,保证pcscd服务正常运行

其中idProduct和idVendor可以通过

获得,最后执行

加载kext

编译TWRP

如果想要为新的手机移植一套可用的TWRP,除了直接修改相似机型的recovery.img外,更灵活的方式是下载源码,自行编译。比如想要为Smartisan T2定制一套TWRP,首先下载源码:

然后进入device,建立两级目录smartisan/SM801,再把TWRP提供的范例下载到SM801中。用mkbootimg_tools工具解包官方的recovery.img,将kernel和dt.img放到SM801,fstab.qcom放到recovery/root,recovery.fstab放到recovery/root/etc,其他还有什么特殊的文件也类似处理。

之后就可以开始填写mk配置文件了,这部分可以按照同类手机填写,将android_device_vendor_codename范例中的选项填满就差不多了可用了,然后到顶层目录执行

 

RT-AC66U如何应对200M宽带

RT-AC66U用着一直不错,升级到200M的电信宽带后,Wifi 5G连接下载速度能上20M/s,但2016年末无意中发现速度已经只剩下10M/s了。

网上很多人也反映了类似情况,特别是新购买该产品的用户。大部分人认为ac66u本来就不具备承载200M宽带的能力,不过我是见过世面的人(目睹过它曾经的高速年华),所以当然是不相信这一点的。要说老化的话,也未免老化的太快了点吧,才刚一年而已。

经过光猫,网线,路由器lan口多条路径的单一变量法的测试后发现,这好像是华硕的阴谋~故意把网速限制了(当然,也可能是路由器功能多到CPU受不了,妥协了网速),逼着你买新的ac66u b1双核或者ac88u之类的。这种伎俩windows 7,windows 10也都用过,就是故意把老系统拖慢,彰显新系统的优势,好让你升级。

总之只要切回老版本操作系统就可以了。下载10月28日的固件3.0.0.4.380_3264,然后更新路由器以后,网速就回来了。

路由器的核心还是速度啊,里面其实花花绿绿的功能暂时都是昙花一现,用不上。

osx下编译android内核

如果是在linux系统下编译android的内核,基本不会有什么大的问题,但osx就稍微顽皮一些。

以nexus 5的内核编译为例,首先下载编译内核用的arm-eabi-gcc工具:

然后下载内核源代码

之后切换代码到需要的branch

以上都是常规步骤,针对osx还有一些必须的改动:

  • 增加两个头文件elf.h和features.h到内核源码的scripts/mod下面,头文件下载
  • 修改scripts/mod/mk_elfconfig.c和scripts/mod/modpost.h两个文件,将<elf.h>改成 “elf.h”
  • 将scripts/recordmcount.c中的<elf.h>修改为 “mod/elf.h”
  • 修改kernel/timeconst.pl,将defined(@array)的修改为@array

最后再编译即可:

 

逆向ARM64内核zImage

主流的旗舰Android手机已经尽数升级到64位,相应的,内核镜像zImage也发生了改变。如果想要用IDA Pro逆向分析arm64的手机内核,特别是完成内核符号的加载,着实需要折腾一番功夫。

从/dev/block或ROM包中提取boot.img,然后用abootimg -x解开得到zImage

如果zImage是gzip压缩的,就gzip -d解压得到kernel

以上两部都是常规项目,下面重点是要从kernel中提取本应显示在/proc/kallsyms下的内核符号,这样IDA Pro加载分析时才更得心应手。参考Bits, Please!的文章中32位的kernel符号提取方法,可以很快想到64位的解决方案:

首先要知道内核加载时的虚拟地址,一种投机的方法是,手机开机后执行:

由于现在手机还没有开启KASLR,所以基地址基本上总是0xffffffc000080000,有了这个地址就可以从kernel中找到symbol table了。内核导出的前两个符号stext,_text等总是指向0xffffffc000080000,所以搜索连续的两个0xffffffc000080000就能找到symbol table。之后按照Bits, Please!的方法就可以导出所有符号了,唯一要注意的是32位到64位,地址长度变成了8字节,内存对齐也从0x10变成了0x100。修改原来的Python脚本,开发了一个arm64解析符号的脚本:

输出的符号会按照/proc/kallsyms打印出来,同时会写入当前目录syms文件。接下来就是让IDA Pro识别syms文件了,我的做法是针对每个符号尝试给特定地址重命名,如果失败就undefine以后再试一次,对于代码段的函数都重新makecode一次:

 

 

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

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

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

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

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

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

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

变革时别分神

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

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

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

精彩的部分来了,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一溜号差点成了绝唱。

总结一下技术细节:

 

Linux下编译WebKit和JSC

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

下载代码

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

或者从webkitgtk直接下载tar.xz

准备编译环境

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

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

编译

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

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