1. 程式人生 > >Android 驅動程式Demo及流程

Android 驅動程式Demo及流程

很久前就想了解驅動程式的想法,這裡現做一個簡單的開始,從demo做起,看到Android驅動程式的基本執行流程,這對漏洞分析、檢測和挖掘都是必要的。同樣,本篇基本也是自己學習過程的記錄,無干貨。本篇大多數內容來自Linux裝置驅動之Ioctl控制

一、使用者層

不管是漏洞檢測,還是poc中,我們見到最多的函式就是ioctl()函式,這個函式就是使用者層呼叫核心程式的介面。

/*
fd:檔案描述符
cmd:控制命令
...:可選引數:插入*argp,具體內容依賴於cmd
*/
int ioctl(int fd,unsigned long cmd,...);

函式第一個引數檔案控制代碼,可以通過open()獲得,第二個引數是指令的值,和驅動程式裡的switch()裡的case值是對應的,第三個是可選引數,通常是一個指標,指向某個變數或者結構體。函式執行成功,返回0,出錯返回-1。

下面來看一個使用者層demo程式(程式碼取自Linux裝置驅動之Ioctl控制,如有冒犯請聯絡刪除),和後面驅動程式是對應的,首先定義標頭檔案。

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#include <linux/ioctl.h>

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*預設的mem的主裝置號*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*裝置數*/
#endif
/*mem裝置描述結構體*/
struct mem_dev                                     
{                                                        
  char
*data; unsigned long size; }; /* 定義幻數 */ #define MEMDEV_IOC_MAGIC 'k' /* 定義命令 */ #define MEMDEV_IOCPRINT _IO(MEMDEV_IOC_MAGIC, 1) #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int) #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int) #define MEMDEV_IOC_MAXNR 3
#endif /* _MEMDEV_H_ */ #ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif

使用者層程式碼:

#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#include "memdev.h"  /* 包含命令定義 */

int main()
{
    int fd = 0;
    int cmd;
    int arg = 0;
    char Buf[4096];


    /*開啟裝置檔案*/
    fd = open("/dev/memdev0",O_RDWR);
    if (fd < 0)
    {
        printf("Open Dev Mem0 Error!\n");
        return -1;
    }

    /* 呼叫命令MEMDEV_IOCPRINT */
    printf("<--- Call MEMDEV_IOCPRINT --->\n");
    cmd = MEMDEV_IOCPRINT;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCPRINT fail\n");
            return -1;
    }


    /* 呼叫命令MEMDEV_IOCSETDATA */
    printf("<--- Call MEMDEV_IOCSETDATA --->\n");
    cmd = MEMDEV_IOCSETDATA;
    arg = 2007;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCSETDATA fail\n");
            return -1;
    }


    /* 呼叫命令MEMDEV_IOCGETDATA */
    printf("<--- Call MEMDEV_IOCGETDATA --->\n");
    cmd = MEMDEV_IOCGETDATA;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCGETDATA fail\n");
            return -1;
    }
    printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);    

    close(fd);
    return 0;    
}

可以看到,程式呼叫了3次ioctl()函式,分別傳遞了3個cmd值,所以,我們後面可以看到,在驅動程式對應的ioctl函式裡應該至少有這3個值對應的case。

據此,可以看到在使用者層呼叫ioctl()函式十分簡單,大致流程其實就是引數構造過程,fd引數構造很簡單,直接獲取open()對應的裝置檔案即可獲得控制代碼。剩下第二個第三個引數的構造,實際上比較麻煩。首先,我學習這個不是為了開發,而是和漏洞相關的工作。那麼,這裡就有幾個問題:

1) 我們需要呼叫的驅動功能可能沒有原始碼,那麼如何構造原始碼引數2?
2)即使存在原始碼,我們如果要做批量的fuzz,如何比較通用的構造引數2?

引數3是個指標,可能指向一個結構體,那麼引數3構造同樣存在上面的兩個問題,而且更加不好解決。即使是寫漏洞檢測程式碼或者漏洞poc,找到這些結構體依賴也是一件體力活。

二、驅動層

在使用者層呼叫了ioctl函式之後,核心裡面對應的ioctl函式會被呼叫,我們暫時不去關心這中間的呼叫鏈。我們把重點放在核心層的ioctl函式以及驅動程式的執行流程上。

首先,驅動程式不想我們常見的c語言函式,不是以main()作為函式的入口的。取而代之,在驅動程式中兩個特殊的函式:

module_init(initFunc);
module_exit(exitFunc);

module_init定義驅動被載入時的行為,在此函式應該完成一些初始化操作,通過“insmod 模組檔名”命令安裝時,次函式會被呼叫。module_exit定義驅動被解除安裝時的行為,次函式函式中完成一些“善後工作”,通過“rmmod 模組檔名”來解除安裝模組時,函式會被呼叫。所以一個最簡單的helloworld類似這樣:

int text_init(void){
    printk("<0>Hello World!");
    return 0;
}

void text_cleanup(void){
   printk("<0>Goodbye World!");
}

module_init(text_init);            //註冊載入時執行的函式
module_exit(text_cleanup);     //註冊解除安裝時執行的函式

顯然,這樣的helloworld定義行為太少,還有很多工作沒做,比如使用者層如何呼叫此驅動。前面我們知道,我們呼叫驅動程式是通過裝置檔案的形式的,接下來先介紹一些字元裝置驅動程式的知識,linux裝置驅動第三篇:如何寫一個簡單的字元裝置驅動?這篇文章內容不錯。

1. 主裝置號與此裝置號

這裡寫圖片描述

主裝置號表示具體的驅動程式,次裝置號由核心使用,表示具體的裝置檔案。例如,虛擬控制檯和串列埠終端有驅動程式4管理,而不同的終端分別有不同的次裝置號。

1.1 裝置號資料結構

核心中,dev_t用於描述裝置標號,為32位的int型別,前12位表示主裝置號,後20位表示此裝置號。dev_t和主裝置號、次裝置號可以通過linux中一些巨集方面的轉換。

//獲取主裝置號
MAJOR(dev_t dev);
//獲取次裝置號
MINOR(dev_t dev);
//轉換為dev_t
MKDEV(int major, int minor);

1.2 分配和釋放裝置號

int register_chrdev_region(dev_t first, unsigned int count, const char name);

first是要分配的裝置編號範圍的起始值。count是連續裝置的編號的個數。name是和該裝置編號範圍關聯的裝置名稱,他將出現在/proc/devices和sysfs中。此函式成功返回0,失敗返回負的錯誤碼。

此函式是在已知主裝置號的情況下使用,在未知主裝置號的情況下,我們使用下面的函式:

int alloc_chrdev_region(dev_t dev, unsigned int firstminor, unsigned int count, const char *name);

dev用於輸出申請到的裝置編號,firstminor要使用的第一個次裝置編號。

在不使用時需要釋放這些裝置編號,已提供其他裝置程式使用:

void unregister_chrdev_region(dev_t dev, unsigned int count);

此函式多在模組的清除函式中呼叫。

以上完成裝置編號註冊。

2. 重要資料結構

2.1 檔案操作file_operations

file_operations資料結構定義在 <linux/fs.h>, 是一個函式指標的集合,裝置所能提供的功能大部分都由此結構提供。這個資料結構裡有大量指標,對應到檔案的操作,部分實現即可。

static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .open = mem_open,
  .release = mem_release,
  .ioctl = memdev_ioctl,
};

2.2 檔案結構file

file資料介面定義於<linux/fs.h>,其中幾個重要的結構體成員如下:

struct file_operations *f_op:就是上面剛剛介紹的檔案操作的集合結構。

mode_t f_mode:檔案模式確定檔案是可讀的或者是可寫的(或者都是), 通過位 FMODE_READ 和 FMODE_WRITE.

loff_t f_pos:當前讀寫位置. loff_t 在所有平臺都是 64 位。驅動可以讀這個值, 如果它需要知道檔案中的當前位置, 但是正常地不應該改變它。

unsigned int f_flags:這些是檔案標誌, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驅動應當檢查 O_NONBLOCK 標誌來看是否是請求非阻塞操作。

void *private_data:open 系統呼叫設定這個指標為 NULL, 在為驅動呼叫 open 方法之前. 你可自由使用這個成員或者忽略它; 你可以使用這個成員來指向分配的資料, 但是接著你必須記住在核心銷燬檔案結構之前, 在 release 方法中釋放那個記憶體. private_data 是一個有用的資源, 在系統呼叫間保留狀態資訊, 我們大部分例子模組都使用它。

2.3 node結構

inode 結構由核心在內部用來表示檔案. 因此, 它和代表開啟檔案描述符的檔案結構是不同的。可能有代表單個檔案的多個開啟描述符的許多檔案結構, 但是它們都指向一個單個 inode 結構。

3. 字元裝置的註冊

核心中使用結構體 struct cdev來描述字元裝置。

有 2 種方法來分配和初始化一個這些結構. 如果你想在執行時獲得一個獨立的 cdev 結構, 你可以為此使用這樣的程式碼:

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;

更常使用的方法來分配和初始化:

void cdev_init(struct cdev cdev, struct file_operations fops);

一旦 cdev 結構建立, 最後的步驟是把它告訴核心:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count)

這裡, dev 是 cdev 結構, num 是這個裝置響應的第一個裝置號, count 是應當關聯到裝置的裝置號的數目. 常常 count 是 1。

從系統去除一個字元裝置, 呼叫:

void cdev_del(struct cdev *dev);

具備了基本的知識後,再看驅動程式碼就簡單多了:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#include "memdev.h"
static int mem_major = MEMDEV_MAJOR;//=0

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp; /*裝置結構體指標*/

struct cdev cdev; 

/*struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size;       
};
*/

/*檔案開啟函式*/
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;

    /*獲取次裝置號*/
    int num = MINOR(inode->i_rdev);

    if (num >= MEMDEV_NR_DEVS) 
            return -ENODEV;
    dev = &mem_devp[num];

    /*將裝置描述結構指標賦值給檔案私有資料指標*/
    filp->private_data = dev;

    return 0; 
}

/*檔案釋放函式*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*IO操作*/
int memdev_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

    int err = 0;
    int ret = 0;
    int ioarg = 0;

    /* 檢測命令的有效性 */
    if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 
        return -EINVAL;
    if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 
        return -EINVAL;

    /* 根據命令型別,檢測引數空間是否可以訪問 */
    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
    if (err) 
        return -EFAULT;

    /* 根據命令,執行相應的操作 */
    switch(cmd) {

      /* 列印當前裝置資訊 */
      case MEMDEV_IOCPRINT:
          printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
        break;

      /* 獲取引數 */
      case MEMDEV_IOCGETDATA: 
        ioarg = 1101;
        ret = __put_user(ioarg, (int *)arg);
        break;

      /* 設定引數 */
      case MEMDEV_IOCSETDATA: 
        ret = __get_user(ioarg, (int *)arg);
        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
        break;

      default:  
        return -EINVAL;
    }
    return ret;

}

/*檔案操作結構體*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .open = mem_open,
  .release = mem_release,
  .ioctl = memdev_ioctl,
};

/*裝置驅動模組載入函式*/
static int memdev_init(void)
{
  int result;
  int i;

  dev_t devno = MKDEV(mem_major, 0);

  /* 靜態申請裝置號*/
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /* 動態分配裝置號 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  

  if (result < 0)
    return result;

  /*初始化cdev結構*/
  cdev_init(&cdev, &mem_fops);
  cdev.owner = THIS_MODULE;
  cdev.ops = &mem_fops;

  /* 註冊字元裝置 */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

  /* 為裝置描述結構分配記憶體*/
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /*申請失敗*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, sizeof(struct mem_dev));

  /*為裝置分配記憶體*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  }

  return 0;

  fail_malloc: 
  unregister_chrdev_region(devno, 1);

  return result;
}

/*模組解除安裝函式*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*登出裝置*/
  kfree(mem_devp);     /*釋放裝置結構體記憶體*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放裝置號*/
}

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);