针对AR502H-V2工业物联网网关的分析( 三)

本文仅用于个人学习与技术研究,不涉及任何商业目的。目标软件为闭源系统,本文分析仅为静态学习,未进行任何侵权或破解行为。作者不承担任何使用后果。

继续上次的逆向。

main函数相对来说比较简短,不过大部分库函数我还是不认识,仅能凭借名字的英文缩写,随便找了一个看起来最熟悉的:j_DEVDB_LoadDevSo函数,dev一般指设备,db可能是数据库,load是加载,结合起来,大概可以推测它可能跟驱动/硬件的存储信息有关。

确定切入点后,就尝试查找它在哪个库里被实现,而vrp所依赖的so都在/mnt/squash/lib目录下。

grep -r会把所有的库全部查询一边,并且还会引入很多无关信息,于是,我干脆写了一个脚本,并对它进行了简单的交互式处理以便以后的复用。

libseekstr

此部分如果不感兴趣可跳过

#!/bin/sh
TARGET=$1
TARGET_RPATH=$2
RED='\033[0;91m'
GREEN='\033[0;92m'
YELLOW='\033[0;93m'

usage()
{
    echo "usage: libseekstr <target> <rpath>"
    echo "---target: the elf file to be analysed."
    echo "---rpath: the lib directory for the elf file."
    echo ""
    echo "Input 'q' or 'quit' to exit."
    echo "Tips: strings length is set to 3."
    exit 1
}

if [ $# -ne 2 ]
then
    usage
    exit 1
elif [ ! -f "$TARGET" ]
then
    echo "${RED}[!] Not found: ${TARGET}."
    exit 1
elif [ ! -d "$TARGET_RPATH" ]
then
    echo "${RED}[!] Not found: ${TARGET_RPATH}."
    exit 1
fi

TARGET_RPATH=${TARGET_RPATH%%/}

while true
do
    echo "${YELLOW}[+] Now handling with ${TARGET}."
    printf "${YELLOW}[+] Please input the string you want to search>"
    read -r str
    if [ -z "$str" ]
    then
        echo "${RED}[!] Need a str."
        continue
    fi
    case "$str" in 
    "quit")
        echo "${GREEN}[+] EXIT."
        exit 0
        ;;
    "q")
        echo "${GREEN}[+] EXIT." 
        exit 0
        ;;
    esac
    strings -n 3 "$TARGET" 2>/dev/null | grep -F "$str" | while read -r tmp_line
    do
        echo "${GREEN}[-] $tmp_line FROM $TARGET"
    done
    readelf -d "$TARGET" 2>/dev/null | grep NEEDED | awk -F'[' '{print $2}' | awk -F']' '{print $1}' | while read -r line
    do
        strings -n 3 "$TARGET_RPATH/$line" 2>/dev/null | grep -F "$str" | while read -r tmp_line
        do
            echo "${GREEN}[-] $tmp_line FROM ${TARGET_RPATH}/${line}."
        done
    done
done

实现了一个简单的交互式工具(因为遇到需要多次查询的情况,反复敲路径真的很费劲),通过读取命令行固定位置的target和rpath,对当前目录下的target文件执行readelf并解析NEEDED字段,然后去rpath路径下对所有NEEDED字段出现过的so以及target文件本身执行strings -n 3 查询str,并返回成功信息,同时屏蔽了readelf的报错

适合:

  • [Y] /lib下有好几百个库的情况,可以大大加快速度,避免grep -r跑飞的问题
  • [Y] 需要反复查询字符串,硬编码,特征字符串的场景

避免:

  • [N] 多个库之间有互相依赖关系(因为不是递归解析,只会查询target文件直接NEEDED的so文件)
  • [N] target文件来源于不同的文件夹(当然你可以新建一个文件夹并把所有库放在一起)

经过查询,该函数出现在libdevdb.so当中,经过简单的符号重命名,其伪代码大概是这样(python demo替代):

QQ20260902-142940.png

于是进一步追踪,逆向它内部出现的j_DEVDB_QueryBoardNameIndexByFileNum函数:

QQ20260902-145734.png

观察到有一个名为g_ulBoardDB_Adress的变量,对它进行交叉引用后,发现它是在DEVDB_LoadDB函数中被赋值,恰好,在vrp的主函数中,它同样出现过,并且比j_DEVDB_LoadDevSo出现时间更早:

QQ20260902-144710.png

该函数首先调用了ftok,针对/mnt/db生成了一个key值,随后调用shmget处理key,返回一个shared_id>,并借助它访问了shared_id对应的共享内存,定义一个变量g_ulDevDBSharedAddress,赋值为该段内>存的基地址,在这个基地址的基础上加0xB414,作为g_ulBoardDB_Adress[i]数组的起始地址,用

    for(i = 0;i <= 109; ++i)
    {
            g_ulBoardDB_Adress[i] = g_ulDevDBSharedAddress + 0x3C00 * i + 0xB414
    }

实现对g_ulBoardDB_Adress[i]的赋值,这里我们可以猜测出,应该有110个名为ulBoardDB的对象,每>个占据0x3c00字节大小的内存空间。

于是,我们知道,DEVDB_LoadDB函数中,将共享内存中的一段连续内存进行了按0x3c00大小进行了划分。

DEVDB_LoadDevSo函数中,另一个关键函数是DEVDB_QueryBoardValueByItem,此函数有大量的结构体赋值操作,不便用python demo表达,因此这里简述其实现:

该函数执行一个循环,根据g_ulBoardFileNum来决定循环次数;

它先对g_ulBoardDB_Adress做了校验,确定它与当前的tmp_g_ulBoardDB_Adress相匹配;

随后,它访问g_ulBoardDB_Adress[i]+4这一地址,从其中读取了一个数据,这里将它暂命名为count;

随后,再次执行循环,循环次数为count,是从g_ulBoardDB_Adress[i]+8这一地址开始,以每16个字节为一>个单位每个单位包含四个属性,分别对应offset_0,offset_4,offset_8,offset_12;

offset_0,offset_4分别存入v13,v17,接下来对v13和v17做判断,如果v13 == v6(0x20062),并且v17 == 1,就将offset_8存进v16,offset_12存进v11,最后以v16的大小,将g_ulBoardDB_Adress[i]+v11复制进提前准备好的,malloc的内存中。
于是可以修复两个结构体:

uk_element
{
        DWORD _v6
        DWORD flag
        DWORD size
        DWORD addr_offset
}
以及
ulBoardDB
{
        void * base_addr
        int count
        struct uk_element elements[10] (这里10是随便取的值,因为这个ulBoardDB无法被0x3c00-8整除,所以
它可能还含有其他结构)
        char padding[n]
}

总结一下,这一段的流程大概就是:

j_DEVDB_LoadDB制造一段共享内存,并把这段共享内存拆成前后两部分,后半部分又被拆成数个0x3c00大小的ulBoardDB对象;

而在DEVDB_LoadDevSo函数中,它malloc出一小块内存,然后调用j_DEVDB_QueryBoardNameIndexByFileNum和j_DEVDB_QueryBoardValueByItem,j_DEVDB_QueryBoardValueByItem对之前DEVDB_LoadDB制造出的共享内存的某个ulBoardDB对象进行头部解析,获取该对象里的element的数量,并即刻对它进行for循环暴力查找,找到element数组中第一个属性等于v6的元素,并精确地返回它在共享内存中的地址。

显然,它们之间还缺了一步:这个共享内存被创建以后,还需要一次赋值/加载,才能执行DEVDB_LoadDevSo的查询逻辑。

而在main函数中,DEVDB_LoadDevSo和j_DEVDB_LoadDB之间确实还有一个名为MON_LoadRecordReasonModules的函数,下次将对它展开深入研究。

评论