Unicorn模拟执行RTOS固件

unicorn是一种指令级cpu模拟器,可以支持多架构二进制程序的执行。由于RTOS固件通常难以进行调试,因此采用通过unicorn模拟器的方式来完成模拟调试。

实验对象

Fast 1900R设备,RTOS,ARM架构

测试漏洞

CVE-2022-26987

漏洞位置:

tWlanTask函数中接受最大长度为3072的输入,随后调用MmtAtePrase函数将输入复制给长度548的栈上变量。导致存在栈溢出漏洞。

tWlanTask函数:

MmtAtePrase函数:

其中spliter相当于strcpy函数,将分隔符'\n'前的字符串复制给v11栈上变量。

模拟片段

  1. 使用binwalk,010Editor等工具查看该固件的加载基址。该固件为0x40205000,常见RTOS固件的加载基址如何确定可见另一篇文章

  2. 使用IDA查看tWlanTask函数的加载函数栈:

    查看调用tWlanTask函数的apps_init

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    int apps_init()
    {
    int v0; // r0
    int v1; // r0
    const char *v2; // r0
    unsigned int v3; // r0
    bool v4; // cc
    _DWORD v6[4]; // [sp+2Ch] [bp-28h] BYREF
    int v7[6]; // [sp+3Ch] [bp-18h] BYREF

    memset(1079933252, 0, 8236);
    v0 = apps_wpsInit(1079933516);
    apps_upnpInit(v0);
    v7[0] = 1;
    v1 = socket(2, 2, 17);
    MEMORY[0x405E7944] = v1;
    if ( v1 == -1 )
    {
    if ( (MEMORY[0x4061657C] & 2) != 0 )
    {
    v2 = "Unable to create wlan event socket\n";
    LABEL_12:
    my_printf(v2);
    }
    }
    else if ( setsockopt(v1, 0xFFFF, 512, v7, 4) >= 0 )
    {
    memset(v6, 0, sizeof(v6));
    v6[0] = 604242448;
    v6[1] = 0;
    if ( bind(MEMORY[0x405E7944], v6, 16) < 0 && (MEMORY[0x4061657C] & 2) != 0 )
    {
    v2 = "Unable to bind wlan event socket\n";
    goto LABEL_12;
    }
    }
    else if ( (MEMORY[0x4061657C] & 2) != 0 )
    {
    v2 = "Unable to setsockopt to wlan event socket\n";
    goto LABEL_12;
    }
    apps_ateInitSock(1079937388);
    apps_wpsInitSock(1079933516);
    apps_wssInit(1079937384);
    bzero(1079933260, 256);
    MEMORY[0x405E7948] = 0;
    if ( MEMORY[0x405E7944] > 0 )
    MEMORY[0x405E7948] = MEMORY[0x405E7944];
    v3 = MEMORY[0x405E88CC];
    *(_DWORD *)(4 * (MEMORY[0x405E7944] >> 5) + 0x405E794C) |= 1 << (MEMORY[0x405E7944] & 0x1F);
    v4 = (int)v3 <= MEMORY[0x405E7948];
    *(_DWORD *)(4 * (v3 >> 5) + 0x405E794C) |= 1 << (v3 & 0x1F);
    if ( !v4 )
    MEMORY[0x405E7948] = v3;
    if ( MEMORY[0x405E896C] > MEMORY[0x405E7948] )
    MEMORY[0x405E7948] = MEMORY[0x405E896C];
    *(_DWORD *)(4 * (MEMORY[0x405E896C] >> 5) + 0x405E794C) |= 1 << (MEMORY[0x405E896C] & 0x1F);
    taskSpawn("tWlanTask", 160, 0, 0x4000, tWlanTask, 1079933252, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    regAppList(4, wlanTmpHandle);
    return 0;
    }

    其创建了监听在1060端口的udp连接socket,并使用taskSpawn函数创建该任务,其中定义了该函数栈大小为0x4000。

  3. 使用unicorn加载该固件到内存:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from unicorn import *
    from unicorn.arm_const import *
    from pwn import *

    fast = Uc(UC_ARCH_ARM, UC_MODE_ARM)
    BASE_ADDR = 0x40200000
    BASE_SIZE = 0x300000
    STACK_ADDR = 0x40500000
    STACK_SIZE = 0x400000

    fast.mem_map(BASE_ADDR, BASE_SIZE)
    fast.mem_map(STACK_ADDR, STACK_SIZE)
    fast.mem_write(BASE_ADDR, read('./Fast FAC 1900R/FAC1900R千兆版 V1.0升级软件20190827_2.0.2/data_10400'))
    fast.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE-1)

    def hook_code(mu, address, size, user_data):
    print(">>> Traceing instruction at 0x%x, instruction size = 0x%x" %(address, size))


    func_source = 0x402CE868
    func_ret = 0x402CE8D0
    fast.hook_add(UC_HOOK_CODE, hook_code)
    fast.emu_start(func_source, func_ret)
  4. 使用capstone库进行反汇编辅助调试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from capstone import *
    from capstone.arm import *

    def bytesToOpcodeStr(curBytes):
    opcodeByteStr = ''.join('{:02X} '.format(eachByte) for eachByte in curBytes)
    return opcodeByteStr

    def hook_code(mu, address, size, user_data):
    # print(">>> Traceing instruction at 0x%x, instruction size = 0x%x" %(address, size))
    opcodeBytes = mu.mem_read(address, BYTES_PER_LINE)
    opcodeByteStr = bytesToOpcodeStr(opcodeBytes)
    decodedInsnGenerator = cs.disasm(opcodeBytes, address)
    # if gSingleLineCode:
    for eachDecodedInsn in decodedInsnGenerator:
    eachInstructionName = eachDecodedInsn.mnemonic
    info("--- 0x%08X: %s -> %s\t%s", address, opcodeByteStr, eachInstructionName, eachDecodedInsn.op_str)

漏洞复现

从source点模拟执行到sink点,hook recvfrom函数,当执行该函数时,向目标buffer里填充payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def hook_recvfrom(mu, address, size, user_data):
if address == 0x402CE880:
info("recvfrom hooked")
target_addr = mu.reg_read(UC_ARM_REG_R1)
size = mu.reg_read(UC_ARM_REG_R2)
info(f"ret_addr: {hex(mu.reg_read(UC_ARM_REG_LR))}")
if size > len(user_data):
size = len(user_data)
mu.mem_write(target_addr, user_data[:size])
info(f"write {size} bytes to {hex(target_addr)}")
info(f"target_addr: {mu.mem_read(target_addr, size)}")
# ret
mu.reg_write(UC_ARM_REG_PC, address+4)
mu.reg_write(UC_ARM_REG_R0, size)

该漏洞利用方法为利用ROP调用strncpy(group_addr, passwd_addr, 0x21)函数,从而实现泄露passwd目的。

passwd_addr可以在httpGetPassword中获取到,在模拟中我们可以将该地址map为有效密码0KcgeXhc9TefbwK

然后通过hook recvfrom 函数写入payload,获取到目标密码。