Magisk on T1

Smartisan T1是一款老机型了,时至今日,运行的仍然是Android 4.4的系统。以前研究过它的Bootloader解锁,如今打算回头再看看怎么把最新的root方案Magisk移植进来实现持久。

不同于supersu需要twrp的支持,Magisk提供了一种十分便捷的修改镜像的方法。只要把目标设备的boot.img拖出来,放到任何一台安装有Magisk Manager的手机上,就能进行重打包,包装成一个带有root功能的镜像。接下来,无论用什么方法,只要能把boot.img刷回boot分区就可以了。

临时root

T1升到最新版本后(2018年2月份的版本),为了把boot.img拖出来,并放回去,需要取得一个临时的root权限,我利用的是CVE-2017-8890,不过这不是重点。问题是后面的Magisk重打包。

Magisk原理

Magisk的重打包,从底层看,主要就是把/init替换成了它自己的magiskinit。当boot启动时,magiskinit首先从/.backup恢复原始的init文件。然后从magiskinit中释放出来magisk.bin和Manager的APK等后续建立root需要的文件。接着它会以init权限patch sepolicy,增加一个名为magisk的上下文环境,最后启动magiskd等待su的连接。

当su命令执行时,背后实际上是向magiskd请求root,magisk则会向Magisk Manager发起问询,等待用户确认后才赋予放行,最后magiskd启动一个shell,并把它重定向到su通过unix socket发送过来的输入输出句柄上。

T1带来的麻烦

现在最大的问题是Magisk Manager编译时只能运行在5.0以上的系统,4.4没有支持。如果抛弃magisk,单靠suid之不能建立起完整root的,4.4虽然selinux弱得很(通过/proc/self/attr/current就能任意修改自身上下文),但普通的用户进程其capability已经几乎全部丢弃,即便uid和gid都为0,能做的事情也有限。还是需要一套magisk这种劫持启动流程的方案才能从一开始就拿到init。

所以打算完全抛弃Magisk Manager,通过patch magisk自身的逻辑,实现任意进程执行su时无需校验就放行。

patched_boot.img的重打包

想要patch magisk,要修改的实际上是Manager重打包后的patched_boot.img。它当中的magiskinit包含了magiskd的代码,只要修改两个关键的跳转就算通过了。不过问题是这里每个环节都很繁琐。首先magiskinit的文件结构很特殊:

$ binwalk init

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ELF, 32-bit LSB executable, ARM, version 1 (SYSV)
262482        0x40152         Unix path: /vendor/etc/selinux/precompiled_sepolicy
262824        0x402A8         Unix path: /sys/fs/selinux/policy
262895        0x402EF         Unix path: /sys/fs/selinux/load
263576        0x40598         Unix path: /sys/fs/selinux/policy)
266224        0x40FF0         Unix path: /vendor/etc/selinux/plat_sepolicy_vers.txt
296964        0x48804         xz compressed data
301647        0x49A4F         xz compressed data
346435        0x54943         xz compressed data
376768        0x5BFC0         Unix path: /proc/device-tree/firmware/android

其中0x49A4F开始的这个区块就是magiskd也就是su_daemon。由于magiskinit要根据这些偏移解出来所需要的文件,修改的时候必须要保证文件大小精确一致。
0x49A4F 文件可以dd出来,然后unxz解开。修改二进制后,不能直接xz,xz命令和magisk用的结构稍微不太一样,需要使用python3的lzma的compress去压缩,也就是:

lzma.compress(magisk, preset=9, check=lzma.CHECK_NONE)

dd回去之后,还要进行ramdisk的重打包,这里同样有个文件大小要保持一致的问题。因为patched_boot.img已经和boot.img不一样了,末尾会增加一个xz,包含Manager的APK文件,虽然用不到,但虽已覆盖会导致初始化失败。

虽然ramdisk重打包有很多工具,但都无法和magisk的cpio保持一致,主要原因是普通的重打包都没有考虑到/.backup等隐藏目录的引入。所以更精确的做法是直接对cpio文件操作,找到init文件的位置,写入修改后的文件内容。对cpio进行gzip时同样还有问题,系统自带的gzip会加入过多的metadata导致文件比原来要大。我解决的方法是minigzip.py,去掉文件名相关的metadata,然后进行压缩,这样大小就和原来相同了,最后dd回patched_boot.img就可以了。再次开机启动就拿到了久违的su:

shell@msm8974sfo:/ # id -Z
uid=0(root) gid=0(root) context=u:r:magisk:s0

2019大计

专业方面

[-] 完成两个内核利用的编写

[-] 完成一到两个浏览器利用的编写

[-] 跟进已有研究的进展,形成一到两次会议议题

[-] 每周看至少两次微博或者twitter,关注业内的最新进展

[-] 阅读10篇左右学术论文,跟进学术界的研究进展

[-] 阅读至少一本专业书籍

[-] 每个月至少进行一次书面总结


生活方面

[-] 阅读5本书,生命不可承受之轻

[-] 出游三次

[-] 每周去健身房两到三次

[-] 跆拳道30次以上

[-] 每个月至少一篇博客

[-] 每个月至少一部电影

[-] 不再消磨于游戏视频

[-] 每个月至少冥想一次

[-] 高考题两次,考研数学题一次

[-] 每周弹琴两次以上

外星人起死回生指南

故事发生在爱丁堡一个阳光明媚的早晨,男主将一杯苏格兰现磨咖啡放在工位桌上,伸了个懒腰,准备开始一天的科(ban)研(zhuan)工作。

然而。。。

男主爱不释手的小外星人Alienware 13 R2却怎么也开不开机了,按下电源键没有任何反应,电源键那个外星人头也不亮了、风扇也不转了、硬盘也不响了。但是电源线上的蓝色LED灯是亮着的

昨天晚上关机时还好好的,也没有任何征兆,这才买了一年多,怎么就突然变砖了呢?

歪果的电脑维修出了名的不靠谱和昂贵,看来只能自力更生,展开自救了。经过一番问题分析、文献搜索和上手实践,本庸医竟将小外星人起死回生。以下给出整个治疗过程以供大家参考:

 

注:本方法不保证一定能拯救你的电脑,如果情况恶化,本人及本网站也谢绝负担任何责任。

据说,以下方法可以用于Alienware 13 R2 Alienware 15 R2 Alienware 17 R3这三个型号的突然开不开机的治疗

 

整个过程包括放静电和CMOS重置两个步骤:

(1)断开电源线,按住开关键10到15秒钟,以释放静电,完毕后插回电源线看看能不能开机了。如果还是不能,转到下一步。

(2)再次断开电源线,打开笔记本遮住内存条的那个后盖;

(3)将后盖内的电池供电线路的连接插销拔掉;

(4)移除内存条,此时你能看到几个金属触点(13 R2与15 R2和17 R3的触点略有不同,如下图)

          * 分支剧情1:对于Alienware 15 R2 and 17 R3,定位CLRP 1,用导体(比如全金属镊子或者曲别针)连通CLRP 1的两个触点

 

* 分支剧情2:对于Alienware 13 R2,定位CLRP 2,用导体(比如全金属镊子或者曲别针)连通CLRP 2的两个触点

 

本文男主的小外星人是13 R2型号的,导体连通CLRP 2两个触点成功会听到滴的一声巨响,其他型号的反应不确定。

 

听到滴的一声巨响后,连回电池供电线路,合上后盖,插回电源线,开机,看是不是有反应了如果有反应了,外星人头开关灯亮了但是还不能正常进入系统,请重复上述第(2)~(4)步3到5次。男主就是重复到第4次成功的。

 

 

 

 

中秋节

快一年的时间,每当想要停下来回头看看,就会望见远在家中前所未有的不安在慢条斯理地折磨着不忍心看着它的每一个家人。自骨折后,麻醉剂夺走了姥姥大半的行动时间,几个月后,消瘦的双腿再想撑起身体已经十分吃力。直到五月她的意识开始有些模糊,一天只有很少的时间可以正常思考和沟通。也是那会儿我第一次觉得无助,只能眼巴巴地看着我妈喂她吃饭喝水。甚至回家,这个我曾经只要有假期都要去做的事儿,突然也变得不忍心,试着去躲,好像不看它就没有了。

终于,中秋前我哥从南非回来了,中秋节时我也赶回家。那天飞机晚点四个小时,晚上八点多才进门,好在回来了,因为第二天下午姥姥走了。

那天下午我替我妈班,她一个上午都在试图喂姥姥吃点东西。我说话,但姥姥除了喉咙里无力的呼吸声别无回应,直到她伸手抚过我的脸和肩膀,最后握住我的手,竟然那么有力,好像生命最后的力量毫无遗憾地释放了。

呼吸从无力到停止,快一年的折磨终于离她而去。

那一天晚上开始,我和我哥在湖边走了很久,说了很多,后来两天也是。从未谈起的最后一刻到从前往事,家人关系,彼此的生活,苦恼欢笑,遗憾释然,一下长大许多。

最后的道别安静顺利,墓碑前没有外人,只有她一辈子牵肠挂肚的子女儿孙。她最后的生命换来一个真正意义的中秋节。

MacBook Air安装双系统

如果只能在linux windows osx当中选其一,兼顾了图形界面和命令行的osx当仁不让。

不过最近升级了512G的Mac,发觉得现阶段双系统才是正解,有些osx下不适合做的,就不勉强,火候一过事情就焦。

我选择的是osx + linux mint,折腾前,建议还是先准备好一个osx的安装u盘。由于涉及分区表操作,弄不好连系统的recovery都废,重装就在所难免了(我就是不小心把分区表弄坏了,只能重启进入internet版的recovery,不过提示下载失败,没能重装,最后还是在windows下制作的osx启动u盘)

安装过程主要参考的是这篇文章,用的是Macbook Air 2017。不过安装linux时,我并没有开启ext4文件系统加密,给osx留了100G,然后2G给/boot,剩下的给/

基本上安装过程不会有问题,装好以后才是麻烦的开始。

首先是键盘布局,进入linux以后:

然后选择Generic 105-key (Intl) PC,English(UK)。试了半天,唯一能让键盘布局正确的选项,否则~总是被映射为±

接下来是亮度调节,为了让mac的亮度调节快捷键工作,编辑/usr/share/X11/xorg.conf.d/20-intel.conf

还要检查一下~/.viminfo的权限是不是666,或者owner是当前用户,不然vim的配置文件会不起作用

接下来要配置L2TP的VPN,Mint默认没有提供,需要自己安装

重启后在网络管理里面就能添加vpn了,不过由于我的目标版本比较老,还需要额外配置一下

首先安装ike-scan扫描目标网络,然后把加密所用的算法填写到VPN的高级选项里,除此之外还要装一个strongswan-plugin-openssl,原因也是因为目标算法比较老。

拯救rm误删的文件

即便采用了定时备份,云盘同步,甚至GIT,却还总是不及随手的一个rm来的杀伤力大。

今天又又又遇到了这个问题,rm随手把刚刚写好的一个cpp文件给删了,一瞬间脚都软了,上周写了一周的ROP,劳民伤财且洋洋洒洒几百行。

Linux下有很多文件恢复类的软件,前序操作一般是umount磁盘,再备份磁盘,最后用这些软件扫描分区,找到误删文件。

只不过现在遇到的情况有点特别,ext4格式的磁盘位于虚拟机当中,整个分区差不多200G,编译Android代码用的。光找个空闲磁盘存放这么大的分区就足够苦恼了,如果还要对这么大的磁盘进行磁盘恢复,时间肯定久到不敢想。

毕竟这个cpp我还是定期备份的,相比之前备份的版本,只是增加了几百行代码而已,现在只要能找到增加的这几行,哪怕有些错乱,理论上再调整下格式就可以了,并不需要传统的文件恢复工具重磅来访。

首先,在分区中搜索新增文字片段中的关键字,关键字要稍微长一点避免重复,比如我的例子中:

通过上面的搜索,可以很快(十分钟以内)找到目标字符串所属偏移(好几组)。

接下来只要从目标分区取回文件内容片段:

到此,基本上text.save里存着的就是你的文件内容了,毕竟fgrep时输出的偏移好几组,dd时倒是可以换几个试试,看哪个输出的结果最好。

复现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范例中的选项填满就差不多了可用了,然后到顶层目录执行