中秋节

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

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

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

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

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

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

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以后:

sudo dpkg-reconfigure keyboard-configuration

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

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

Section "Device"
        Identifier  "card0"
        Driver      "intel"
        Option      "Backlight"  "intel_backlight"
        BusID       "PCI:0:2:0"
EndSection

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

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

sudo add-apt-repository ppa:nm-l2tp/network-manager-l2tp
sudo apt-get update
​sudo apt-get install network-manager-l2tp network-manager-l2tp-gnome

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

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

拯救rm误删的文件

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

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

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

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

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

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

fgrep -a -b 'memcpy(mem + 0xA000' /dev/sda3
fgrep: 搜索字符串为文本,而不是正则表达式,会比grep快很多
-a: 搜索时把目标文件当做文本而不是二进制,否则只会输出Binary File Matched字样,而不打印结果
-b: 搜索结果会显示目标字符串所在的文件偏移

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

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

dd if=/dev/sda3 of=text.save bs=1 skip=offset_from_fgrep count=8000
skip: 调到fgrep所指的文件偏移附近开始转储文件内容

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

下载编译AOSP

下载


Android的源码下载编译可以算是一个持久性老大难问题了,如果是在国内弄,更是涉及政治,经济,历史,文化的方方面面:-)

除了安装Google文档里的依赖库,源码本身的下载编译才是重头戏,我目前比较试过行之有效的方法如下:

首先,从清华开源站下载最新的源码tar包,解压后,修改.repo/manifests.git/config文件

url = git://mirrors.ustc.edu.cn/aosp/platform/manifest

然后,repo init -b xxx到你要的分支,再repo sync就可以了

选择两个站是因为,清华有tar包,可以显著提升下载速度,但至少按照他们的文档来看,仅支持http方式的git同步。相比ssh的git在repo sync阶段要慢且非常容易中断,所以下载好的.repo要修改url指向科大的源。

再来就是repo sync总是提示中断,下载错误,无论你的网络有多好,同步半个小时左右就会出现这个问题。对于这一点也确实试了所有灵丹妙药,最后发现无非就是限制线程数量,然后反复同步,为此专门写了一个脚本自动反复单线程repo sync,想要停止的话,touch stopme即可

import os
while(1):
  if os.system("repo sync -j1") == 0:
    break
  if os.path.exists("stopme"):
    break

编译


至于编译一般来说问题不大,无非就是Java版本可能出些问题,或者堆大小要调高之类的,编译开始前就先设置一下堆的大小:

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"
./prebuilts/sdk/tools/jack-admin kill-server
./prebuilts/sdk/tools/jack-admin start-server

刷机


由于编译好的rom并不完整,仅包含boot.img,system.img等基本信息,可能和手机内的其他模块如modem,radio,bootloader等冲突,最好先用同版本或接近版本的factory image刷一次,然后再到编译好的rom目录执行

fastboot flashall -w

如果刷机以后系统无法启动,可能是bootloader版本不匹配导致的,从官网下载同版本rom后,先./flash-all一下就好了。

复现CVE-2017-12858

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

lib/zip_dirent.c:581
 
     if (!_zip_dirent_process_winzip_aes(zde, error)) {
-	if (!from_buffer) {
-	    _zip_buffer_free(buffer);
-	}
 	return -1;
     }

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

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

lib/zip_dirent.c:570

if (!from_buffer) {
        _zip_buffer_free(buffer);
    }

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

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

第一次要求from_buffer = 0;

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

达成第一个条件

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

_zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, bool local, zip_error_t *error)
{
    zip_uint8_t buf[CDENTRYSIZE];
    zip_uint16_t dostime, dosdate;
    zip_uint32_t size, variable_size;
    zip_uint16_t filename_len, comment_len, ef_len;

    bool from_buffer = (buffer != NULL); 

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

_zip_checkcons(zip_t *za, zip_cdir_t *cd, zip_error_t *error)
{
    zip_uint64_t i;
    zip_uint64_t min, max, j;
    struct zip_dirent temp;
    ...
        if (_zip_dirent_read(&temp, za->src, NULL, true, error) == -1) {
            _zip_dirent_finalize(&temp);
            return -1;
        }
...

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

_zip_find_central_dir(zip_t *za, zip_uint64_t len)
{
    ...
    cdir = NULL;
    match = _zip_buffer_get(buffer, 0);
    while ((match=_zip_memmem(match, _zip_buffer_left(buffer)-(EOCDLEN-4), (const unsigned char *)EOCD_MAGIC, 4)) != NULL) {
        _zip_buffer_set_offset(buffer, (zip_uint64_t)(match - _zip_buffer_data(buffer)));
        if ((cdirnew = _zip_read_cdir(za, buffer, (zip_uint64_t)buf_offset, &error)) != NULL) {
            if (cdir) {
                if (best <= 0) {
                    best = _zip_checkcons(za, cdir, &error);
                }

                a = _zip_checkcons(za, cdirnew, &error);
                if (best < a) {
                    _zip_cdir_free(cdir);
                    cdir = cdirnew;
                    best = a;
                }
                else {
                    _zip_cdir_free(cdirnew);
                }
            }
            else {
                cdir = cdirnew;
                if (za->open_flags & ZIP_CHECKCONS)
                    best = _zip_checkcons(za, cdir, &error);
                else {
                    best = 0;
                }
            }
            cdirnew = NULL;
        }
...

幸运的是,所有的_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函数,代码如下:

_zip_dirent_process_winzip_aes(zip_dirent_t *de, zip_error_t *error)
{
    zip_uint16_t ef_len;
    zip_buffer_t *buffer;
    const zip_uint8_t *ef;
    bool crc_valid;
    zip_uint16_t enc_method;

    if (de->comp_method != ZIP_CM_WINZIP_AES) {
        return true;
    }

发现只要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_dirent_init(zde);
    if (!local)
        zde->version_madeby = _zip_buffer_get_16(buffer);
    else
        zde->version_madeby = 0;
    zde->version_needed = _zip_buffer_get_16(buffer);
    zde->bitflags = _zip_buffer_get_16(buffer);
    zde->comp_method = _zip_buffer_get_16(buffer);

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

gcc编译轶事

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

#include <stdio.h>
#include <stdlib.h>
#include "vfile.h"
#include "jpeglib.h"

int main(int argc, char *argv[])
{
    char *pFileName = argv[1];
    if (NULL == pFileName)
        return 0;
    VFILE *m_pInFile = new TDiskFile(pFileName, "rb");
    if (NULL == m_pInFile)
    {
        printf("error open\n");
        return 0;
    }
    struct jpeg_decompress_struct m_jds;
    jpeg_CreateDecompress(&m_jds, 62, 432);
    return 1;
}

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

$ arm-linux-gnueabi-g++ -o wrapper wrapper.cpp vfile.cpp -lJpeg
/usr/arm-linux-gnueabi/lib/libBasic.so: undefined reference to `dlopen'
/usr/arm-linux-gnueabi/lib/libBasic.so: undefined reference to `dlclose'
/usr/arm-linux-gnueabi/lib/libBasic.so: undefined reference to `dlerror'
/usr/arm-linux-gnueabi/lib/libBasic.so: undefined reference to `dlsym

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

$ arm-linux-gnueabi-readelf -d /usr/arm-linux-gnueabi/lib/libJpeg.so 
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libBasic.so]
0x00000001 (NEEDED)                     Shared library: [libstdc++.so.6]
0x00000001 (NEEDED)                     Shared library: [libm.so.6]
0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

$ arm-linux-gnueabi-readelf -d /usr/arm-linux-gnueabi/lib/libBasic.so 
0x00000001 (NEEDED)                     Shared library: [libpthread.so.0]
0x00000001 (NEEDED)                     Shared library: [libts_ipc_client_new.so]
0x00000001 (NEEDED)                     Shared library: [libiconv.so.2]
0x00000001 (NEEDED)                     Shared library: [libzmq.so.1]
0x00000001 (NEEDED)                     Shared library: [libuuid.so]
0x00000001 (NEEDED)                     Shared library: [libstdc++.so.6]
0x00000001 (NEEDED)                     Shared library: [libm.so.6]
0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

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

$ arm-linux-gnueabi-g++ -o wrapper wrapper.cpp vfile.cpp -lJpeg -lBasic -ldl
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/lib/../lib/libBasic.so: undefined reference to `dlopen'
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/lib/../lib/libBasic.so: undefined reference to `dlclose'
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/lib/../lib/libBasic.so: undefined reference to `dlerror'
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/lib/../lib/libBasic.so: undefined reference to `dlsym'

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

复现

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

#include <stdio.h>
#include <dlfcn.h>
void foo2(void)
{
    void *hd = dlopen("test.so", RTLD_LAZY);
    puts("Hello, I'm shared library2");
}

两种编译方法结果如下:

$ arm-linux-gnueabi-gcc -shared -o sm2.so sm2.c
$ arm-linux-gnueabi-readelf -d sm2.so
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libc.so.6]
$ arm-linux-gnueabi-gcc -shared -o sm2.so sm2.c -ldl
$ arm-linux-gnueabi-readelf -d sm2.so
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libdl.so.2]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

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

#include <stdio.h>
extern void foo2(void);
void foo(void)
{
    foo2();
    puts("Hello, I'm shared library1");
}

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

$ arm-linux-gnueabi-gcc -shared -o sm.so sm.c -lsm2
$ arm-linux-gnueabi-readelf -d sm.so
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libsm2.so]
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

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

#include <stdio.h>
#include <dlfcn.h>
extern void foo(void);
void main()
{
    foo();
}
$ arm-linux-gnueabi-gcc -o test test.c -lsm -lsm2 -ldl
/usr/lib/gcc-cross/arm-linux-gnueabi/5/../../../../arm-linux-gnueabi/lib/../lib/libsm2.so: undefined reference to `dlopen'
collect2: error: ld returned 1 exit status

但如果test.c改为:

#include <stdio.h>
#include <dlfcn.h>
extern void foo(void);
void main()
{
    void *hd = dlopen("any.so", RTLD_LAZY);
    foo();
}

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

结论

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

$ arm-linux-gnueabi-gcc -shared -o sm.so sm.c 
$ arm-linux-gnueabi-readelf -d sm.so 
Tag        Type                         Name/Value
0x00000001 (NEEDED)                     Shared library: [libc.so.6]

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

OSX下安装mfoc

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

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

brew install libtool
brew install automake
brew install autoconf
brew install libusb
brew install pkg-config
brew install glib

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

git clone https://github.com/nfc-tools/libnfc
cd libnfc
autoreconf -vis
./configure --with-drivers=acr122_pcsc
make install

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

git clone https://github.com/nfc-tools/mfoc
cd mfoc
autoreconf -vis
./configure
make install

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

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

csrutil disable

接下来保证pcscd服务正常运行,在主系统增加一个kext

/System/Library/Extensions/DUMMY.kext/Contents/Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!-- This is a dummy driver which binds to ACR122U. It -->
<!-- contains no actual code; its only purpose is to -->
<!-- prevent Apple's USBHID driver from exclusively -->
<!-- opening the device. -->
<plist version="1.0">
   <dict>
      <key>CFBundleDevelopmentRegion</key>
      <string>English</string>
      <key>CFBundleIconFile</key>
      <string />
      <key>CFBundleIdentifier</key>
      <string>com.ACR122U.dummy.kext</string>
      <key>CFBundleInfoDictionaryVersion</key>
      <string>6.0</string>
      <key>CFBundlePackageType</key>
      <string>KEXT</string>
      <key>CFBundleSignature</key>
      <string>????</string>
      <key>CFBundleVersion</key>
      <string>1.0.0d1</string>
      <key>IOKitPersonalities</key>
      <dict>
         <!-- The ACR122U USB interface -->
         <key>ACR122U</key>
         <dict>
            <key>CFBundleIdentifier</key>
            <string>com.ACR122U.dummy.kext</string>
            <key>IOClass</key>
            <string>com.ACR122U.dummy.kext</string>
            <key>IOProviderClass</key>
            <string>IOUSBInterface</string>
            <key>bConfigurationValue</key>
            <integer>1</integer>
            <key>bInterfaceNumber</key>
            <integer>0</integer>
            <key>idProduct</key>
            <integer>XXXXXXXXXXXXXXXXXXXXX</integer>
            <key>idVendor</key>
            <integer>XXXXXXXXXXXXXXXXXXXXX</integer>
         </dict>
      </dict>
      <key>OSBundleLibraries</key>
      <dict>
         <key>com.apple.iokit.IOUSBFamily</key>
         <string>1.8</string>
      </dict>
   </dict>
</plist>

其中idProduct和idVendor可以通过

system_profiler SPUSBDataType

获得,最后执行

sudo kextload -verbose /System/Library/Extensions/DUMMY.kext

加载kext

编译TWRP

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

repo init -u git://github.com/lj50036/platform_manifest_twrp_omni.git -b twrp-6.0
repo sync

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

source build/envsetup.sh
mka recoveryimage

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,然后更新路由器以后,网速就回来了。

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

Android下的permission和gid

Android是在linux基础上构建的,权限的管理即要依赖apk中的permission,也要考虑和linux的uid/gid的方式结合。虽然很多操作可以在java层完成,但诸如设备文件的访问,又要回归到传统的uid/gid管理模式,比如设备文件

crw-rw---- bluetooth net_bt_stack 204,  68 2016-12-07 13:24 ttyAMA4

net_bt_stack组对设备文件是可以操作的,如果查看/system/etc/permissions/platform.xml

<permission name="android.permission.BLUETOOTH_ADMIN" >
    <group gid="net_bt_admin" />
</permission>

<permission name="android.permission.BLUETOOTH" >
    <group gid="net_bt" />
</permission>

<permission name="android.permission.BLUETOOTH_STACK" >
    <group gid="net_bt_stack" />
</permission>

<permission name="android.permission.NET_TUNNELING" >
    <group gid="vpn" />
</permission>

<permission name="android.permission.INTERNET" >
    <group gid="inet" />
</permission>

<permission name="android.permission.READ_LOGS" >
    <group gid="log" />
</permission>

<permission name="android.permission.WRITE_MEDIA_STORAGE" >
    <group gid="media_rw" />
    <group gid="sdcard_rw" />
</permission>
...

可以看到,如果apk申请了android.permission.BLUETOOTH_STACK权限,它的进程将具备linux的uid/gid管理体系下的net_bt_stack组权限

运行该apk,然后查看/proc/pid/status,Groups中也直接阐明了这一点

State:  S (sleeping)
Tgid:   13412
Pid:    13412
PPid:   15316
TracerPid:      0
Uid:    10135   10135   10135   10135
Gid:    10135   10135   10135   10135
FDSize: 64
Groups: 3001 3002 3008 9997 50135

但这并不意味着,直接su app_id得到的shell具备该gid。Android下的su并不是基于/etc/passwd等文件实现的完整权限切换,仅保留了uid的信息。不过Android提供的run-as可以完整切换权限,得到具备该gid的shell