当前位置:首页 > 芯闻号 > 充电吧
[导读]最近(终于)转Android了,2011年著名的zergrush是接触的第一个ROOT漏洞。虽然它已经过气了,只影响Android 2.2 - 2.3.6,但觉得还是有必要记录一下分析所得。市面上各种

最近(终于)转Android了,2011年著名的zergrush是接触的第一个ROOT漏洞。虽然它已经过气了,只影响Android 2.2 - 2.3.6,但觉得还是有必要记录一下分析所得。

市面上各种ROOT工具基本都包含zergrush,大多是开源的zergRush.c直接编译而来。已有的分析文章:

  tomken_zhang,漏洞 — zergRush,漏洞 — zergRush (补充)

  Claud, Android提权代码zergRush分析

分析内容集中在zergRush.c的代码结构上,对漏洞原理没有解析,或者错误地认为是栈溢出。其实CVE-2011-3874已经描述得很明白,这个漏洞的本质是"use after free"。

1. 栈溢出?No.

漏洞存在于/system/bin/vold这个root身份的系统程序。具体地,vold调用了libsysutils.so,真正有问题的是这个so。再具体地,问题出在/system/core/libsysutils/src/FrameworkListener.cpp的FrameworkListener::dispatchCommand方法。

它在栈上分配了一个固定大小的数组argv,


void FrameworkListener::dispatchCommand(SocketClient *cli, char*data) {
    FrameworkCommandCollection::iterator i;
    intargc =0;
    char*argv[FrameworkListener::CMD_ARGS_MAX];
    chartmp[255];
    char*p = data;
    char*q = tmp;
    bool esc =false;
    bool quote =false;
    intk;

 

 FrameworkListener::CMD_ARGS_MAX = 16。但后面填充argv数组时,代码并没有检查是否发生了越界。


if (!quote && *q == ' ') {
      *q ='';
      argv[argc++] = strdup(tmp);
      memset(tmp,0, sizeof(tmp));
      q = tmp;
      continue;
  }

 

让argv越界是很容易的,越界的数据会被写入argv下面的tmp数组中。zergRush实际上只是向argv中填充了CMD_ARGS_MAX + 2个char *,越界了8字节而已,255字节的tmp数组完全接得住,并没有破坏dispatchCommand的栈。其实,就算dispatchCommand的栈溢出了,也没什么事,因为它被编译进了__stack_chk,一旦返回地址改变,会异常结束。所以,这个漏洞不是“栈溢出”。

2. free(任意地址)

用户程序向system/bin/vold发送的数据会到达FrameworkListener::onDataAvailable。onDataAvailable会从数据中提取命令字符串,并调用dispatchCommand处理命令。用户数据可能包含多条命令,每条命令是一个以''结尾的字符串,由名称和若干参数构成,名称和参数由空格分隔。例如,

"cmd1 arg11 arg12cmd2 arg21 arg22 arg23"

这样的数据进入onDataAvailable后,会提取出2条命令字符串:"cmd1 arg11 arg12" 和 "cmd2 arg21 arg22 arg23",对每条命令调用一次dispatchCommand。dispatchCommand接收到命令字符串后,会进一步解析出命令名称和参数,并存入argv数组。比如,对于"cmd1 arg11 arg12",dispatchCommand完成解析后,


argv[0] =="cmd1";
argv[1] =="arg11";
argv[2] =="arg12"

名称和每个参数,最初都是解析到tmp中,argv的每个字符串指针都是strdup(tmp)出来的,在dispatchCommand返回前要free掉,


int j;
for (j = 0; j < argc; j++)
    free(argv[j]);



这是一个很正常的逻辑,但在argv数组越界,与tmp发生交叠的情况下,有意思的事情就发生了。假设有一条命令带有17个参数,

"cmd p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 p16 x78x56x34x12"

 解析后,argv数组的内容是:

argv[0] =="cmd";
argv[1] =="p1";
argv[2] =="p2";
argv[3] =="p3";
argv[4] =="p4";
argv[5] =="p5";
argv[6] =="p6";
argv[7] =="p7";
argv[8] =="p8";
argv[9] =="p9";
argv[10] =="p10";
argv[11] =="p11";
argv[12] =="p12";
argv[13] =="p13";
argv[14] =="p14";
argv[15] =="p15";
argv[16] ==0x12345678;
argv[17] =="x78x56x34x12"



argv数组大小只有16项,argv[16]实际上是tmp数组的前4个字节,而最后一个参数是"x78x56x34x12",它被解析出来后缓存到tmp里,这样tmp的前4字节就成了0x78,0x56, 0x34, 0x12 ,因此argv[16]==0x12345678。之后,argv[16]被free,即执行了free(0x12345678)。我们可以任意控制参数内容,即可实现:free(任意地址)。

3. 指针复用和vtable覆盖

能够free掉任意指针了,如何实现exploit呢?最简单的办法是free掉一个带vtable的C++对象指针,然后重用这个指针,从而控制vtable。很幸运的是,dispatchCommand的代码逻辑支持这么做。它会对比接收到的命令和已注册的FrameworkCommand,如果匹配,就去运行匹配的FrameworkCommand。

 

for (i = mCommands->begin(); i != mCommands->end(); ++i) {
        FrameworkCommand *c = *i;
 
        if(!strcmp(argv[0], c->getCommand())) {
            if(c->runCommand(cli, argc, argv)) {
                SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
            }
            gotoout;
        }
    }


runCommand是FrameworkCommand的virtual方法,FrameworkCommand对象指针就是要free掉然后复用的目标。mCommands是FrameworkListener的成员,它包含了若干FrameworkCommand对象指针,如果知道了FrameworkListener *this,寻址到FrameworkCommand不在话下,这也是zergRush.c的heap_oracle() 做的事情。

假设我们成功free掉了一个FrameworkCommand指针,怎样复用这个指针呢?只需要再调用dispatchCommand解析一条命令,在解析过程中,第一次strdup(tmp)返回的就是这个指针,并且已经把tmp字符串复制到这个地址了。即:命令的前4个字节已经成为这个FrameworkCommand对象的vtable,实现了vtable的任意指定。观察上面C++代码对应的汇编:

.text:000029F6                 LDR.W           R6, [R8]
.text:000029FA                 MOV             R0, R10 ; s1
.text:000029FC                 LDR             R1, [R6,#4] ; s2
.text:000029FE                 BLX             strcmp
.text:00002A02                 CBNZ            R0, loc_2A38
.text:00002A04                 LDR             R0, [R6]           ;R0 = vtable
.text:00002A06                 MOV             R1, R5
.text:00002A08                 MOV             R2, R4
.text:00002A0A                 MOV             R3, R7
.text:00002A0C                 LDR.W           R12, [R0,#8]       ;R12 = runCommand
.text:00002A10                 MOV             R0, R6
.text:00002A12                 BLX             R12                ;Call runCommand
.text:00002A14                 CBZ             R0, loc_2A50
.text:00002A16                 LDR             R5, [R6,#4]
.text:00002A18                 BLX             __errno
.text:00002A1C                 LDR             R0, [R0] ; errnum
.text:00002A1E                 BLX             strerror
.text:00002A22                 LDR             R2, =(aFrameworkliste -0x2A2E)
.text:00002A24                 LDR             R3, =(aHandlerSErrorS -0x2A30)
.text:00002A26                 MOVS            R1, #5
.text:00002A28                 STR             R5, [SP,#0x484+var_484]
.text:00002A2A                 ADD             R2, PC  ;"FrameworkListener"
.text:00002A2C                 ADD             R3, PC  ;"Handler '%s' error (%s)"
.text:00002A2E                 STR             R0, [SP,#0x484+var_480]
.text:00002A30                 MOVS            R0, #3
.text:00002A32                 BLX             __android_log_buf_print
.text:00002A36                 B               loc_2A50



调用runCommand,实际是调用了[vtable + 8]。比如第二次传给dispatchCommand的命令是"AAAA blablabla",vtable会被覆盖成0x41414141,PC将被指向 [0x41414149]。至此,已经成功的控制了指令流,剩下的就是构造攻击数据的奇淫技巧了,比如ROP等,和漏洞本身没有直接关系了。zergRush.c成功地调用system()执行任意命令,达到了ROOT的目的。

4. 总结

CVE-2011-3874这个漏洞源于栈上的数组越界,用户程序通过向/system/bin/vold发送一段包含2条特殊命令的数据,可以控制root身份的vold执行system(),借以提权。用户数据中的第1条命令会free掉一个FrameworkCommand对象;而第2条命令会重新占用对象地址,进而覆盖vtable实现控制指令流。

在Android 2.2-2.3.6没有ASLR的前提下,漏洞利用过程只有2个必要数据无法直接获取:dispatchCommand的this指针和栈地址SP。不幸(幸运)的是,强大的logcat填上了这个坑。有了logcat,即使有ASLR,对这个漏洞也没有防护能力。




没有进行补丁的源码


void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
    FrameworkCommandCollection::iterator i;
    int argc = 0;
    char *argv[FrameworkListener::CMD_ARGS_MAX];
    char tmp[CMD_BUF_SIZE];
    char *p = data;
    char *q = tmp;
    char *qlimit = tmp + sizeof(tmp) - 1;
    bool esc = false;
    bool quote = false;
    int k;
    bool haveCmdNum = !mWithSeq;

    memset(argv, 0, sizeof(argv));
    memset(tmp, 0, sizeof(tmp));
    while(*p) {
        if (*p == '\') {
            if (esc) {
                if (q >= qlimit)
                    goto overflow;
                *q++ = '\';
                esc = false;
            } else
                esc = true;
            p++;
            continue;
        } else if (esc) {
            if (*p == '"') {
                if (q >= qlimit)
                    goto overflow;
                *q++ = '"';
            } else if (*p == '\') {
                if (q >= qlimit)
                    goto overflow;
                *q++ = '\';
            } else {
                cli->sendMsg(500, "Unsupported escape sequence", false);
                goto out;
            }
            p++;
            esc = false;
            continue;
        }

        if (*p == '"') {
            if (quote)
                quote = false;
            else
                quote = true;
            p++;
            continue;
        }

        if (q >= qlimit)
            goto overflow;
        *q = *p++;
        if (!quote && *q == ' ') {
            *q = '';
            if (!haveCmdNum) {
                char *endptr;
                int cmdNum = (int)strtol(tmp, &endptr, 0);
                if (endptr == NULL || *endptr != '') {
                    cli->sendMsg(500, "Invalid sequence number", false);
                    goto out;
                }
                cli->setCmdNum(cmdNum);
                haveCmdNum = true;
            } else {              
                argv[argc++] = strdup(tmp);
            }
            memset(tmp, 0, sizeof(tmp));
            q = tmp;
            continue;
        }
        q++;
    }

    *q = '';  
    argv[argc++] = strdup(tmp);
#if 0
    for (k = 0; k < argc; k++) {
        SLOGD("arg[%d] = '%s'", k, argv[k]);
    }
#endif

    if (quote) {
        cli->sendMsg(500, "Unclosed quotes error", false);
        goto out;
    }

    if (errorRate && (++mCommandCount % errorRate == 0)) {
        /* ignore this command - let the timeout handler handle it */
        SLOGE("Faking a timeout");
        goto out;
    }

    for (i = mCommands->begin(); i != mCommands->end(); ++i) {
        FrameworkCommand *c = *i;

        if (!strcmp(argv[0], c->getCommand())) {
            if (c->runCommand(cli, argc, argv)) {
                SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
            }
            goto out;
        }
    }
    cli->sendMsg(500, "Command not recognized", false);
out:
    int j;
    for (j = 0; j < argc; j++)
        free(argv[j]);
    return;

overflow:
    LOG_EVENT_INT(78001, cli->getUid());
    cli->sendMsg(500, "Command too long", false);
    goto out;
}





本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭