macOS内核拓展与用户态进程的通信实现(二)

前面已经讲解了基于KernControl API的通信实现,该实现相对简单,但有一些缺点。内核向应用层传输消息需调用ctl_enqueuedata接口,该接口实际将数据缓存至缓冲区,当需要瞬时大量传输消息时,缓冲区容量有限,将丢弃后来的数据。如果传输的数据有优先级,则需对ctl_enqueuedata接口进行二次封装,避免高优先级数据丢失。

IOKit Fundamentals 框架提供更加全面且方便的内核驱动API。IOService是大多数内核驱动的基类,提供驱动实例化相关的各项服务。IOUserClient是与用户态应用程序间通信的基类,通过继承并实现该类,可与客户端应用程序建立通信机制。IOSharedDataQueue是非常便于使用的内核与用户态进程进行数据交换的通用队列,用户可自行设置队列大小。基于IOKit中的IOService、IOUserClient、IOSharedDataQueue可方便的实现内核与用户进程的通信和数据传输。下面结合代码进行简单实现,更多代码细节和工程配置可参考NuwaStone项目。

IOService内核编程

由于本次编写的为内核拓展,仅需要IOService进行驱动的加载与卸载管理,这里的代码实现很简单,仅需重写start、stop方法。如有加载卸载时的自定义操作可在函数中实现。

DriverService.hpp

class DriverService : public IOService {
    OSDeclareDefaultStructors(DriverService);

public:
    // Called by the kernel when the kext is loaded
    bool start(IOService *provider) override;

    // Called by the kernel when the kext is unloaded
    void stop(IOService *provider) override;
    
private:
    void clearInstances();
};

IOUserClient内核编程

编写继承于IOUserClient的类后需重写如下方法,相关源文件实现请参考开源项目。registerNotificationPort和clientMemoryForType用于数据交换队列的配置,externalMethod配置对外函数调用接口,对外接口的函数原型如callYourMethod定义。

DriverClient.hpp

class DriverClient : public IOUserClient {
    OSDeclareDefaultStructors(DriverClient);
    
public:
    // Called as part of IOServiceOpen in clients.
    bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;

    // Called after initWithTask as part of IOServiceOpen.
    bool start(IOService *provider) override;

    // Called when this class is stopping.
    void stop(IOService *provider) override;

    // Called when a client manually disconnects (via IOServiceClose).
    IOReturn clientClose(void) override;

    // Called when a client dies.
    IOReturn clientDied(void) override;

    // Called during termination.
    bool didTerminate(IOService *provider, IOOptionBits options, bool *defer) override;

    // Called in clients with IOConnectSetNotificationPort. 用于数据传输
    IOReturn registerNotificationPort(mach_port_t port, UInt32 type, UInt32 refCon) override;

    // Called in clients with IOConnectMapMemory. 用于数据传输
    IOReturn clientMemoryForType(UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;

    // Called in clients with IOConnectCallScalarMethod. 设置对外通信调用接口
    IOReturn externalMethod(UInt32 selector, IOExternalMethodArguments *arguments, IOExternalMethodDispatch *dispatch, OSObject *target, void *reference) override;

    // 自定义对外调用方法
    static IOReturn callYourMethod(OSObject *target, void *reference, IOExternalMethodArguments *arguments);
};

IOKit客户端编程

连接内核拓展前需先进行加载,加载调用KextManagerLoadKextWithIdentifier或KextManagerLoadKextWithURL即可。内核拓展需在plist中配置IOService及IOUserClient类名,在拓展启动后可通过类名进行查找匹配。首先需查找注册了指定类名的内核驱动,代码如下:

func startProvider() -> Bool {
        guard let service = IOServiceMatching("your service name") else {
            return false
        }
        
        Logger(.Info, "Wait for kext to be connected.")
        waitForDriver(matchingDict: service)
        return true
    }

service存放匹配成功的驱动字典,然后需要创建通信端口和处理队列进行处理连接请求。处理连接请求时所持有的IOService对象需注意释放。调用IOServiceOpen接口即可建立连接,后面的IOConnectCallScalarMethod表示调用驱动对外接口进行连接测试。函数返回前需将用于连接请求处理的端口释放。通过IOConnectCallScalarMethod或IOConnectCallStructMethod可调用驱动对外接口,其中ScalarMethod仅可传输有限数量的常量,StructMethod则可传输自定义结构体类型,相关驱动配置可参照NuwaStone项目。

func processConnectionRequest(iterator: io_iterator_t) {
        repeat {
            // 持有的对象需进行释放
            let nextService = IOIteratorNext(iterator)
            guard nextService != 0 else {
                break
            }
            
            // 建立与内核驱动的连接
            var result = IOServiceOpen(nextService, mach_task_self_, 0, &connection)
            if result != kIOReturnSuccess {
                Logger(.Error, "Failed to open kext service [\(String.init(format: "0x%x", result))].")
                IOObjectRelease(nextService)
                break
            }
            
            // 调用驱动方法测试连接
            result = IOConnectCallScalarMethod(connection, kNuwaUserClientOpen.rawValue, nil, 0, nil, nil)
            if result != kIOReturnSuccess {
                Logger(.Error, "An error occurred while opening the connection [\(result)].")
                IOObjectRelease(nextService)
                break
            }
            
            IOObjectRelease(nextService)
            IONotificationPortDestroy(notificationPort)
            isConnected = true
            Logger(.Info, "Connected with kext successfully.")
        } while true
    }
    
func waitForDriver(matchingDict: CFDictionary) {
        var iterator: io_iterator_t = 0
        let selfPointer = Unmanaged.passUnretained(self).toOpaque()
        let notificationQueue = DispatchQueue(label: "your queue name")
        
        let appearedCallback: IOServiceMatchingCallback = { refcon, iterator in
            let selfPtr = Unmanaged<YourClassName>.fromOpaque(refcon!).takeUnretainedValue()
            selfPtr.processConnectionRequest(iterator: iterator)
        }
        
        notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
        IONotificationPortSetDispatchQueue(notificationPort, notificationQueue)
        IOServiceAddMatchingNotification(notificationPort, kIOMatchedNotification, matchingDict, appearedCallback, selfPointer, &iterator)
        processConnectionRequest(iterator: iterator)
    }

我们通过调用IOServiceOpen存放io_connect_t类型的对象建立了与内核驱动的连接,相应的,断开连接时需调用IOServiceClose接口。

func stopProvider() -> Bool {
        let result = IOServiceClose(connection)
        if result != KERN_SUCCESS {
            Logger(.Error, "Failed to close IOService [\(String.init(format: "0x%x", result))].")
            return false
        }
        
        connection = IO_OBJECT_NULL
        isConnected = false
        return true
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容