Anjoy SSC338Q libtools.so

Extracted from firmware for Mstar MC800S-V3 camera module.

Rated "the most uses of grep per grep".

Reading /proc/%d/status, the long way around.

fn get_process_ppid(arg1: i32) -> i32
{
    let mut var_210: i32 = 0;
    let mut var_20c: ();
    memset(&var_20c, 0, 0xfc);
    let mut var_110: [i8; 0x100];
    sprintf(&var_110, "cat /proc/%d/status |grep PPid: |grep -v grep |grep -v sh|awk '{print $2}'", arg1);
    let r0_3: *mut FILE = popen(&var_110, "r");

    if r0_3 != 0 {
        let r0_5: u32 = fread(&var_210, 1, 0x100, r0_3);
        let mut r0_7: i32;
        let mut r1_1: i32;
        let mut r2_1: i32;
        r0_7 = pclose(r0_3);

        if r0_5 > 0 {
            /* tailcall */
            return sub_43c28(r0_5, nullptr, &var_210, r0_7, r1_1, r2_1);
        }
    }

    0
}

We have /sbin/login at home.

fn autologin() -> i32
{
    let r0: *mut passwd = getpwnam("root");

    if r0 == 0 {
        puts("get root password info failed!");
        return 0xffffffff;
    }

    let r5_1: uid_t = *(r0.byte_offset(8) as *mut i32);
    let r6_1: gid_t = *(r0.byte_offset(0xc) as *mut i32);
    let r0_2: *mut i8 = strdup(*(r0.byte_offset(0x14) as *mut i32));
    let r0_4: *mut i8 = strdup(*(r0.byte_offset(0x18) as *mut i32));
    printf("get root password info ok, dir = %s, shell = %s\n", r0_2, r0_4);
    chown("/dev/console", r5_1, r6_1);
    chown("/dev/tty", r5_1, r6_1);
    setregid(r6_1, r6_1);
    setegid(r6_1);
    setgid(r6_1);
    setreuid(r5_1, r5_1);
    seteuid(r5_1);
    setuid(r5_1);
    setenv("HOME", r0_2, 1);
    setenv("SHELL", r0_4, 1);
    setenv("USER", "root", 1);
    setenv("LOGNAME", "root", 1);
    chdir(r0_2);
    0
}

Found the ex-Windows developer.

fn GetFileFromFirmware(arg1: i32, arg2: i32, arg3: i32, arg4: i32, arg5: i32 @ r5, arg6: i32) -> i32
{
    // ...
}

// immediately after:
fn GetFileFromFirmwareEx(arg1: i32, arg2: *mut c_void, arg3: i32, arg4: *mut i8) -> i32
{
    // ...
    match arg3 {
        1 => {
            result = GetFileFromFirmware(result_1, arg4, *(arg2.byte_offset(0x14) as *mut u32),
                *(arg2.byte_offset(0x18) as *mut u32), *(arg2.byte_offset(0x1c) as *mut u32));
        }
        2 => {
            result = GetFileFromFirmware(result_1, arg4, *(arg2.byte_offset(0x120) as *mut u32),
                *(arg2.byte_offset(0x124) as *mut u32), *(arg2.byte_offset(0x228) as *mut u32));
        }
        3 | 5 | 6 | 7 | 9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf => {
            goto 'label_44ef4;
        }
        4 => {
            result = GetFileFromFirmware(result_1, arg4, *(arg2.byte_offset(0x22c) as *mut u32),
                *(arg2.byte_offset(0x230) as *mut u32), *(arg2.byte_offset(0x234) as *mut u32));
        }
        8 => {
            result = GetFileFromFirmware(result_1, arg4, *(arg2.byte_offset(0x238) as *mut u32),
                *(arg2.byte_offset(0x23c) as *mut u32), *(arg2.byte_offset(0x240) as *mut u32));
        }
        0x10 => {
            let result_2: i32 = GetFileFromFirmware(result_1, arg4, *(arg2.byte_offset(0x244) as *mut u32),
                *(arg2.byte_offset(0x248) as *mut u32), *(arg2.byte_offset(0x24c) as *mut u32));
            result = result_2;
            let result_3: i32 = result_2;
        }
    }
    // ...
}

Why have one ICMP ping implementation if you could have two?

fn icmp_ping_old(arg1: *mut i8) -> i32
{
    let r0: i32 = socket(2, 3, 1);
    if r0 < 0 {
        DebugPrint(8, 7, "[%s:%u]error : socket\n", "icmp_ping_old", 0x14d);
        return 0;
    }

    // ...
}

// earlier:
fn icmp_ping(arg1: *mut i8, arg2: i32) -> pid_t
{
    // ...

    let r0_6: i32 = socket(2, 3, 1);
    if r0_6 < 0 {
        DebugPrint(8, 7, "[%s:%u]RAW socket created error\n", "icmp_ping", 0xe5);
        perror("RAW socket created error");
        *(mutex_ping as *mut u8) = 1;
        return 0;
    }

    // ...
}

But are you sure it will reboot?

fn delay_reboot_thread(arg1: *mut i32) -> i32
{
    let entry_r2: i32;
    printf("%s: reboot.\n", "delay_reboot_thread", entry_r2, "delay_reboot_thread");
    let r6: i32 = *(arg1 as *mut u32);
    free(arg1);
    DebugPrint(8, 7, "[%s:%u]reboot_thread start, system will reboot after %ds...\n\n", "delay_reboot_thread", 0xc95, r6);
    pthread_self();
    pthread_detach();
    system("killall procman");
    system("killall record_server");
    system("touch /mnt/nand/soft_reboot.flag");
    let mut r0_1: i32;

    if r6 < 0x1f {
        r0_1 = r6;
    } else {
        r0_1 = 0xa;
    }

    SLEEP_SECOND(r0_1);
    DebugPrint(8, 7, "[%s:%u]call reboot to reboot system...\n\n", "delay_reboot_thread", 0xca3);
    WatchDogForceReset();
    system("reboot -f");
    system("reboot -f");
    system("reboot -f");
    system("reboot -f");
    system("reboot -f");
    system("reboot -f");
    free(arg1);
    delay_reboot(3);
    0
}

Kill it!!! (There is a total of 51 instance of killall <proc> in this library, of which 23 are killall -9 <proc>.)

fn KillProcessForFirmware(arg1: i32, arg2: i32) -> i32
{
    mysystem("killall -9 watchdog.sh");
    killprocess("procman");
    mysystem("killall -9 AlarmProxy");
    mysystem("killall -9 AlarmService");
    let mut var_50: i32 = 0;
    let mut var_4c: ();
    memset(&var_4c, 0, 0x3c);
    read_file_to_string("/etc/flag.customize", &var_50, 0x40);

    if strstr(&var_50, "_CWYY") != 0 {
        mysystem_with_param("%s/upgrade_killall.sh", "/tmp/oem");
        mysystem("killall skyapp");
        mysystem("killall skfactory");
        mysystem("killall netevent");
    }

    if arg2 != 3 {
        mysystem("killall web_server");
        mysystem("killall h5live_server");

        if arg2 != 7 {
            mysystem("killall hik_server");

            if arg2 != 8 {
                mysystem("killall juanvision");
            }
        }
    } else {
        mysystem("killall h5live_server");
        mysystem("killall hik_server");
        mysystem("killall juanvision");
    }

    mysystem("touch /tmp/not.check.stream.exist");
    mysystem("killall media_server");

    if access("/opt/ch/lprAttendance", 0) != 0xffffffff {
        DebugPrint(8, 7, "[%s:%u]lprAttendance found,  kill it!!!\n\n", "KillProcessForFirmware", 0x256);
        mysystem("killall lprAttendance");
    }

    if access("/opt/ch/fire_server_aiwinn", 0) != 0xffffffff {
        DebugPrint(8, 7, "[%s:%u]fire_server_aiwinn found,  kill it!!!\n\n", "KillProcessForFirmware", 0x25c);
        mysystem("killall fire_server_aiwinn");
    }

    mysystem("killall record_server");
    mysystem("sync");

    if arg2 != 9 {
        mysystem("killall danale_server");
        mysystem("killall goolink_server");
        mysystem("killall skyworth_server");
        mysystem("killall ac18pro_server");
        killprocess("p2p_server");
        mysystem("killall rtmp_publish");
        mysystem("killall rtsp_server");
        mysystem("killall hank_server");
        mysystem("killall -9 cubo_main");
        mysystem("killall -9 qrc_reader");
        usleep(0x2dc6c0);
        killprocess("media_server");

        if access("/opt/ch/mobile_server", 0) != 0xffffffff {
            'label_547de:
            DebugPrint(8, 7, "[%s:%u]mobile_server found,  kill it!!!\n\n", "KillProcessForFirmware", 0x291);
            killprocess("mobile_server");
        }

        if arg2 != 0xc {
            goto 'label_5468a;
        }

        killprocess("comm_server");
        killprocess("ipvs_adaptor");
        killprocess("gb28181_server");
    } else {
        mysystem("killall rtmp_publish");
        mysystem("killall rtsp_server");
        mysystem("killall hank_server");
        mysystem("killall -9 cubo_main");
        mysystem("killall -9 qrc_reader");
        usleep(0x2dc6c0);
        killprocess("media_server");

        if access("/opt/ch/mobile_server", 0) != 0xffffffff {
            goto 'label_547de;
        }

        'label_5468a:

        if access("/opt/ch/anko_adaptor", 0) != 0xffffffff {
            DebugPrint(8, 7, "[%s:%u]anko_adaptor found,   kill it!!!\n\n", "KillProcessForFirmware", 0x29b);
            killprocess("anko_adaptor");
        }

        if access("/opt/ch/hb_server", 0) != 0xffffffff {
            DebugPrint(8, 7, "[%s:%u]hb_server found,  kill it!!!\n\n", "KillProcessForFirmware", 0x2a2);
            killprocess("hb_server");
        }

        if arg2 != 6 {
            killprocess("comm_server");

            if arg2 != 0xa {
                killprocess("ipvs_adaptor");
                killprocess("gb28181_server");
            }
        } else {
            killprocess("ipvs_adaptor");
            killprocess("gb28181_server");
        }
    }

    let r0_13: i32 = get_system_totalmem();
    let r0_14: i32 = get_system_freemem();
    DebugPrint(8, 7, "[%s:%u]freemem=%d kB, totalmen=%d kB.\n\n", "KillProcessForFirmware", 0x2bc, r0_14, r0_13);
    let mut cond:0: bool = r0_14 < 0x3000;

    if r0_14 >= 0x3000 {
        cond:0 = r0_13 < &__elf_header;
    }

    if !cond:0 {
        return 0;
    }

    mysystem("echo 3 > /proc/sys/vm/drop_caches");
    let r0_18: i32 = get_system_freemem();
    usleep(0xf4240);
    DebugPrint(8, 7, "[%s:%u]freemem is %d kB\n\n", "KillProcessForFirmware", 0x2c2, r0_18);
    0
}

Covering all the bases.

fn GetCrcValue(arg1: *mut i8, arg2: *mut c_void) -> i32 {
    // ...
    r2 = *((&data_8ba08).byte_offset((r3_1 as u32 ^ r2 as u8 as u32) << 2) as *mut u32) ^ r2 >> 8;
    // ...
}

fn GetCrcValue1(arg1: i32, arg2: *mut c_void, arg3: *mut c_void) -> i32 {
    // Same polynomial as in `GetCrcValue`.
}

fn GetCrcValue_ex(arg1: *mut i8, arg2: *mut c_void, arg3: i32) -> i32 {
    // Same polynomial as in `GetCrcValue`.
}

fn GetCrcValue_exex(arg1: i32, arg2: *mut i8, arg3: *mut c_void) {
    // Same polynomial as in `GetCrcValue`.
}

fn GetFileCrc(arg1: i32, arg2: *mut i32) -> i32 {
    // Does not call any of the above. Same polynomial.
}

fn GetFileCrc1(arg1: i32, arg2: i32, arg3: i32, arg4: *mut i32) -> i32 {
    // Does not call any of the above either. Same polynomial.
}

fn GetFileMd5(arg1: i32, arg2: *mut i8, arg3: u32) -> i32 {
    // More secure?
}

Keeping the right things secret.

fn ReadSerialNumber() -> i32
{
    /* tailcall */
    ReadEncriptData()
}

fn ReadEncriptData(arg1: i32) -> i32
{
    let lr: i32;
    let var_4: i32 = lr;
    let r0: i32 = ReadEncriptDataFromSoft();

    // ...
}

fn ReadEncriptDataFromSoft(arg1: i32) -> i32
{
    // ...
    if flash_sect_rw(0, GET_SN_MTD_BLOCK(), 0x1000, 0x1f, var_c0) == 0 {
        // ...
        CheckSoftSNData(r4_2.byte_offset(-0x40), 0x20, arg1, 0x10, var_c0)
        // ...
    }
    // ...
}

fn CheckSoftSNData(arg1: *mut i8, arg2: u32, arg3: *mut i32) -> i32
{
    let mut var_238: i32;
    __builtin_memset(&var_238, 0, 0x28);
    let mut var_210: i32;
    hexStrToUInt(arg1, arg2, &var_210);
    let mut var_214: i32;
    let mut i: *mut i8 = ((&var_214).byte_offset(3) as *mut u32);
    let mut r1: i32 = 1;
    let mut var_204: i32;

    do {
        i = &i[1];

        if *(i as *mut u8) as u32 != 0 {
            r1 = 0;
        }
    } while i != ((&var_204).byte_offset(3) as *mut u32);

    if r1 != 1 {
        let r3: i32 = var_204;
        var_238 = var_210;
        let var_20c: i32;
        let var_234_1: i32 = var_20c;
        let var_208: i32;
        let var_230_1: i32 = var_208;
        let var_22c_1: i32 = r3;
        let mut expanded_key: [u32; 0x40];
        crypto_aes_expand_key(&expanded_key, key_soft, 0x20);
        let mut var_224: i32;
        aes_decrypt_(&expanded_key, &var_224, &var_238);
        let mut r3_1: i32 = 'OSJA';
        let var_21c: i32;

        if var_21c == 'OSJA' {
            r3_1 = 'NS';
            let var_218: i32;

            if var_218 as u16 as u32 == 'NS' {
                *(arg3 as *mut u32) = var_224;
                let var_220: i32;
                arg3[1] = var_220;
                arg3[2] = var_21c;
                arg3[3] = var_218;
                return 0;
            }
        }

        printf("[%s:%d] SN check failed.\n\n", "CheckSoftSNData", 0x1c0, r3_1);
    }

    0xffffffff
}

The serial number key is, of course, hardcoded; also due to a type mismatch (it's allocated as uint32_t[0x20], but read as uint8_t[0x20]), three quarters of it are zeroes.

key_soft:
                        be 00 00 00 30 00 00 00          ....0...
41 00 00 00 33 00 00 00 52 00 00 00 91 00 00 00  A...3...R.......
85 00 00 00 99 00 00 00 1c 00 00 00 1f 00 00 00  ................
df 00 00 00 2e 00 00 00 43 00 00 00 50 00 00 00  ........C...P...
23 00 00 00 79 00 00 00 39 00 00 00 29 00 00 00  #...y...9...)...
11 00 00 00 02 00 00 00 3e 00 00 00 45 00 00 00  ........>...E...
59 00 00 00 60 00 00 00 ff 00 00 00 ac 00 00 00  Y...`...........
99 00 00 00 55 00 00 00 66 00 00 00 88 00 00 00  ....U...f.......
3f 00 00 00 fe 00 00 00                          ?.......

Every important function must be clearly named and exported. (It does not appear to be referenced anywhere else in the firmware.)

fn GetCopyPrevention__(arg1: i32, arg2: *mut i8, arg3: i32)
{
    if arg3 <= 0x20 {
        return;
    }

    let mut var_118: [i8; 0x80];
    memset(&var_118, 0, 0x80);
    let mut var_98: [i8; 0x84];
    memset(&var_98, 0, 0x80);
    sprintf(&var_98, "%s%s", arg1, "cham.li@anjvision.com");
    our_md5_encode(&var_118, &var_98, strlen(&var_98));
    strcpy(arg2, &var_118);
}