草庐IT

ios - 在iOS(越狱设备)中完全隐藏电话

coder 2024-01-15 原文

我想在 ios 中完全隐藏一个电话。我的首要任务是在 ios 7(目前是最新的 ios 版本!)上执行此操作,但我想知道如何在 ios 6 及以下版本上隐藏电话(如果可能)。我发现了一些函数可以执行此操作,例如连接到 SBUIFullscreenAlertAdapter 类的 initWithAlertController 方法中。感谢 creker 在 this link我找到了另一种更好的 Hook 方法。问题是当电话未锁定或电话锁定时它仍然有一个调用栏,电话显示它正在通话中。以下是屏幕截图: link to image

我想知道处理这个hook的方法是什么?为了实现我想要的目标,还有什么我应该知道的吗?

为了删除通话结束后我想到的任何其他痕迹,我从它的数据库中删除了通话记录。有没有更好的办法?

最佳答案

我会尝试发布尽可能多的代码,但它不会从头开始工作。我使用自己的 macroses 生成 Hook ,因此您必须重写它们才能使用您的代码。我将使用伪函数 IsHiddenCall 来确定给定的调用是否是我们的隐藏调用(简单的电话号码检查)。这是为了简化代码。您显然必须自己实现。还会有其他伪函数,但它们的实现非常简单,从它们的名字就可以看出。这不是一个简单的调整,所以请耐心等待。

此外,代码是非 ARC 的。

基本上,我们会挂接所有可能会告诉 iOS 有电话来电的信息。

iOS 7

让我们从 iOS 7 开始,因为它是目前 iOS 的最后一个版本,隐藏调用的实现比 iOS 6 及更低版本更简单。

我们需要的几乎所有东西都位于私有(private) TelephonyUtilities.framework 中。在 iOS 7 中,Apple 几乎将与电话相关的所有内容都移到了该框架中。这就是它变得更简单的原因 - 所有其他 iOS 组件都使用该框架,因此我们只需要 Hook 它一次,而无需在每个 iOS 守护进程和可能会处理电话调用的框架中四处寻找。

所有方法都 Hook 在两个进程中 - SpringBoard 和 MobilePhone(电话应用程序)。 Bundle ID 分别是 com.apple.springboardcom.apple.mobilephone

这是我在两个进程中挂接的 TelephonyUtilities.framework 方法的列表。

//TUTelephonyCall -(id)initWithCall:(CTCallRef)call
//Here we return nil in case of a hidden call. That way iOS will ignore it
//as it checks for nil return value.
InsertHookA(id, TUTelephonyCall, initWithCall, CTCallRef call)
{
    if (IsHiddenCall(call) == YES)
    {
        return nil;
    }

    return CallOriginalA(TUTelephonyCall, initWithCall, call);
}

//TUCallCenter -(void)handleCallerIDChanged:(TUTelephonyCall*)call
//This is CoreTelephony notification handler. We ignore it in case of a hidden call.
//call==nil check is required because of our other hooks that might return
//nil object. Passing nil to original implementation might break something.
InsertHookA(void, TUCallCenter, handleCallerIDChanged, TUTelephonyCall* call)
{
    if (call == nil || IsHiddenCall([call destinationID]) == YES)
    {
        return;
    }

    CallOriginalA(TUCallCenter, handleCallerIDChanged, call);
}

//TUCallCenter +(id)callForCTCall:(CTCallRef)call;
//Just like TUTelephonyCall -(id)initWithCall:(CTCallRef)call
InsertHookA(id, TUCallCenter, callForCTCall, CTCallRef call)
{
    if (IsHiddenCall(call) == YES)
    {
        return nil;
    }

    return CallOriginalA(TUCallCenter, callForCTCall, call);
}

//TUCallCenter -(void)disconnectAllCalls
//Here we disconnect every call there is except our hidden call.
//This is required in case of a hidden conference call with hidden call.
//Our call will stay hidden but active while other call is active. This method is called
//when disconnect button is called - we don't wont it to cancel our hidden call
InsertHook(void, TUCallCenter, disconnectAllCalls)
{
    DisconnectAllExceptHiddenCall();
}

//TUCallCenter -(void)disconnectCurrentCallAndActivateHeld
//Just like TUCallCenter -(void)disconnectAllCalls 
InsertHook(void, TUCallCenter, disconnectCurrentCallAndActivateHeld)
{
    DisconnectAllExceptHiddenCall();
}

//TUCallCenter -(int)currentCallCount
//Here we return current calls count minus our hidden call
InsertHook(int, TUCallCenter, currentCallCount)
{
    return CallOriginal(TUCallCenter, currentCallCount) - GetHiddenCallsCount();
}

//TUCallCenter -(NSArray*)conferenceParticipantCalls
//Hide our call from conference participants
InsertHook(id, TUCallCenter, conferenceParticipantCalls)
{
    NSArray* calls = CallOriginal(TUCallCenter, conferenceParticipantCalls);

    BOOL isThereHiddenCall = NO;
    NSMutableArray* callsWithoutHiddenCall = [NSMutableArray array];
    for (id i in calls)
    {
        if (IsHiddenCall([i destinationID]) == NO)
        {
            [callsWithoutHiddenCall addObject:i];
        }
        else
        {
            isThereHiddenCall = YES;
        }
    }

    if (callsWithoutHiddenCall.count != calls.count)
    {
        //If there is only two calls - hidden call and normal - there shouldn't be any sign of a conference call
        if (callsWithoutHiddenCall.count == 1 && isThereHiddenCall == YES)
        {
            [callsWithoutHiddenCall removeAllObjects];
        }
        [self setConferenceParticipantCalls:callsWithoutHiddenCall];
        [self _postConferenceParticipantsChanged];
    }
    else
    {
        return calls;
    }
}

//TUTelephonyCall -(BOOL)isConferenced
//Hide conference call in case of two calls - our hidden and normal
InsertHook(BOOL, TUTelephonyCall, isConferenced)
{
    if (CTGetCurrentCallCount() > 1)
    {
        if (CTGetCurrentCallCount() > 2)
        {
            //There is at least two normal calls - let iOS do it's work
            return CallOriginal(TUTelephonyCall, isConferenced);
        }

        if (IsHiddenCallExists() == YES)
        {
            //There is hidden call and one normal call - conference call should be hidden
            return NO;
        }
    }

    return CallOriginal(TUTelephonyCall, isConferenced);
}

//TUCallCenter -(void)handleCallStatusChanged:(TUTelephonyCall*)call userInfo:(id)userInfo
//Call status changes handler. We ignore all events except those
//that we marked with special key in userInfo object. Here we answer hidden call, setup
//audio routing and doing other stuff. Our hidden call is indeed hidden,
//iOS doesn't know about it and don't even setup audio routes. "AVController" is a global variable.
InsertHookAA(void, TUCallCenter, handleCallStatusChanged, userInfo, TUTelephonyCall* call, id userInfo)
{
    //'call' is nil when this is a hidden call event that we should ignore
    if (call == nil)
    {
        return;
    }

    //Detecting special key that tells us that we should process this hidden call event
    if ([[userInfo objectForKey:@"HiddenCall"] boolValue] == YES)
    {
        if (CTCallGetStatus(call) == kCTCallStatusIncoming)
        {
            CTCallAnswer(call);
        }
        else if (CTCallGetStatus(call) == kCTCallStatusActive)
        {
            //Setting up audio routing
            [AVController release];
            AVController = [[objc_getClass("AVController") alloc] init];
            SetupAVController(AVController);
        }
        else if (CTCallGetStatus(call) == kCTCallStatusHanged)
        {
            NSArray *calls = CTCopyCurrentCalls(nil);
            for (CTCallRef call in calls)
            {
                CTCallResume(call);
            }
            [calls release];

            if (CTGetCurrentCallCount() == 0)
            {
                //No calls left - destroying audio controller
                [AVController release];
                AVController = nil;
            }
        }

        return;
    }
    else if (IsHiddenCall([call destinationID]) == YES)
    {
        return;
    }

    CallOriginalAA(TUCallCenter, handleCallStatusChanged, userInfo, call, userInfo);
}

这是我在两个进程中挂接的 Foundation.framework 方法。

//In iOS 7 telephony events are sent through local NSNotificationCenter. Here we suppress all hidden call notifications.
InsertHookAAA(void, NSNotificationCenter, postNotificationName, object, userInfo, NSString* name, id object, NSDictionary* userInfo)
{
    if ([name isEqualToString:@"TUCallCenterControlFailureNotification"] == YES || [name isEqualToString:@"TUCallCenterCauseCodeNotification"] == YES)
    {
        //'object' usually holds TUCall object. If 'object' is nil it indicates that these notifications are about hidden call and should be suppressed
        if (object == nil)
        {
            return;
        }
    }

    //Suppressing if something goes through
    if ([object isKindOfClass:objc_getClass("TUTelephonyCall")] == YES && IsHiddenCall([object destinationID]) == YES)
    {
        return;
    }

    CallOriginalAAA(NSNotificationCenter, postNotificationName, object, userInfo, name, object, userInfo);
}

这是我在 CoreTelephony.framwork

的两个进程中挂接的最后一个方法
//CTCall +(id)callForCTCallRef:(CTCallRef)call
//Return nil in case of hidden call
InsertHookA(id, CTCall, callForCTCallRef, CTCallRef call)
{
    if (IsHiddenCall(call) == YES)
    {
        return nil;
    }

    return CallOriginalA(CTCall, callForCTCallRef, call);
}

这是我之前使用的 SetupAVController 函数。隐藏的调用是真正隐藏的 - iOS 对此一无所知,所以当我们接听它时,音频路由没有完成,我们不会在另一端听到任何声音。 SetupAVController 执行此操作 - 它设置音频路由,就像 iOS 在有事件电话时所做的那样。我使用私有(private) Celestial.framework

中的私有(private) API
extern id AVController_PickableRoutesAttribute;
extern id AVController_AudioCategoryAttribute;
extern id AVController_PickedRouteAttribute;
extern id AVController_AllowGaplessTransitionsAttribute;
extern id AVController_ClientPriorityAttribute;
extern id AVController_ClientNameAttribute;
extern id AVController_WantsVolumeChangesWhenPaused;

void SetupAVController(id controller)
{
    [controller setAttribute:[NSNumber numberWithInt:10] forKey:AVController_ClientPriorityAttribute error:NULL];
    [controller setAttribute:@"Phone" forKey:AVController_ClientNameAttribute error:NULL];
    [controller setAttribute:[NSNumber numberWithBool:YES] forKey:AVController_WantsVolumeChangesWhenPaused error:NULL];
    [controller setAttribute:[NSNumber numberWithBool:YES] forKey:AVController_AllowGaplessTransitionsAttribute error:NULL];
    [controller setAttribute:@"PhoneCall" forKey:AVController_AudioCategoryAttribute error:NULL];
}

这里是我只hook在MobilePhone进程中的方法

/*
PHRecentCall -(id)initWithCTCall:(CTCallRef)call
Here we hide hidden call from call history. Doing it in MobilePhone
will hide our call even if we were in MobilePhone application when hidden call
was disconnected. We not only delete it from the database but also prevent UI from       
showing it.
*/
InsertHookA(id, PHRecentCall, initWithCTCall, CTCallRef call)
{
    if (call == NULL)
    {
        return CallOriginalA(PHRecentCall, initWithCTCall, call);
    }

    if (IsHiddenCall(call) == YES)
    {
        //Delete call from call history
        CTCallDeleteFromCallHistory(call);

        //Update MobilePhone app UI
        id PHRecentsViewController = [[[[[UIApplication sharedApplication] delegate] rootViewController] tabBarViewController] recentsViewController];
        if ([PHRecentsViewController isViewLoaded])
        {
            [PHRecentsViewController resetCachedIndexes];
            [PHRecentsViewController _reloadTableViewAndNavigationBar];
        }
    }

    return CallOriginalA(PHRecentCall, initWithCTCall, call);
}

我在 SpringBoard 进程中 Hook 的方法。

//SpringBoard -(void)_updateRejectedInputSettingsForInCallState:(char)state isOutgoing:(char)outgoing triggeredbyRouteWillChangeToReceiverNotification:(char)triggered
//Here we disable proximity sensor 
InsertHookAAA(void, SpringBoard, _updateRejectedInputSettingsForInCallState, isOutgoing, triggeredbyRouteWillChangeToReceiverNotification, char state, char outgoing, char triggered)
{
    CallOriginalAAA(SpringBoard, _updateRejectedInputSettingsForInCallState, isOutgoing, triggeredbyRouteWillChangeToReceiverNotification, state, outgoing, triggered);

    if (IsHiddenCallExists() == YES && CTGetCurrentCallCount() == 1)
    {
        BKSHIDServicesRequestProximityDetectionMode = (void (*)(int))dlsym(RTLD_SELF, "BKSHIDServicesRequestProximityDetectionMode");
        BKSHIDServicesRequestProximityDetectionMode(0);
    }
}

//BBServer -(void)publishBulletin:(id)bulletin destinations:(unsigned int)destinations alwaysToLockScreen:(char)toLockScreen
//Suppress hidden call bulletins
InsertHookAAA(void, BBServer, publishBulletin, destinations, alwaysToLockScreen, id bulletin, unsigned int destinations, char toLockScreen)
{
    if ([[bulletin section] isEqualToString:@"com.apple.mobilephone"] == YES)
    {
        NSArray *recordTypeComponents = [[bulletin recordID] componentsSeparatedByString:@" "];
        NSString *recordType = recordTypeComponents[0];
        NSString *recordCode = recordTypeComponents[1];

        //Missed call bulletin
        if ([recordType isEqualToString:@"missedcall"] == YES)
        {
            NSArray *recordCodeComponents = [recordCode componentsSeparatedByString:@"-"];
            NSString *phoneNumber = recordCodeComponents[0];

            if (IsHiddenCall(phoneNumber) == YES)
            {
                return;
            }
        }
    }

    CallOriginalAAA(BBServer, publishBulletin, destinations, alwaysToLockScreen, bulletin, destinations, toLockScreen);
}

//TUCallCenter -(id)init
//CoreTelephony notifications handler setup
InsertHook(id, TUCallCenter, init)
{
    CTTelephonyCenterAddObserver(CTTelephonyCenterGetDefault(), self, CallStatusNotificationCallback, kCTCallStatusChangeNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);

    return CallOriginal(TUCallCenter, init);
}

//Call status changes handler. Here we redirect status changes into hooked TUCallCenter method and doing some other stuff.
void CallStatusNotificationCallback(CFNotificationCenterRef center, void* observer, CFStringRef name, const void* object, CFDictionaryRef userInfo)
{
    if (object == NULL)
    {
        return;
    }

    if (IsHiddenCall((CTCallRef)object) == YES)
    {
        [observer handleCallStatusChanged:object userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:@"HiddenCall"]];
    }
    else
    {
        if (CTCallGetStatus((CTCallRef)object) == kCTCallStatusHanged)
        {
            if (IsHiddenCallExists() == YES)
            {
                //Setting up all the audio routing again. When normal call is hanged iOS may break audio routing as it doesn't know there is another active call exists (hidden call)
                SetupAVController(AVController);
            }
            else if (CTGetCurrentCallCount() == 0)
            {
                [AVController release];
                AVController = nil;
            }
        }
    }

    if (CTGetCurrentCallCount() > 1 && IsHiddenCallExists() == YES)
    {
        //Here we setup hidden conference call
        NSArray *calls = CTCopyCurrentCalls(nil);
        for (CTCallRef call in calls)
        {
            CTCallJoinConference(call);
        }
        [calls release];
    }
}

iOS 5-6

iOS 5-6 更复杂。电话代码分散在许多 iOS 组件和 API 中。我可能会稍后发布代码,因为我现在没有时间。答案已经很长了。

关于ios - 在iOS(越狱设备)中完全隐藏电话,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22729003/

有关ios - 在iOS(越狱设备)中完全隐藏电话的更多相关文章

  1. ruby - 完全离线安装RVM - 2

    我打算为ruby​​脚本创建一个安装程序,但我希望能够确保机器安装了RVM。有没有一种方法可以完全离线安装RVM并且不引人注目(通过不引人注目,就像创建一个可以做所有事情的脚本而不是要求用户向他们的bash_profile或bashrc添加一些东西)我不是要脚本本身,只是一个关于如何走这条路的快速指针(如果可能的话)。我们还研究了这个很有帮助的问题:RVM-isthereawayforsimpleofflineinstall?但有点误导,因为答案只向我们展示了如何离线在RVM中安装ruby。我们需要能够离线安装RVM本身,并查看脚本https://raw.github.com/wayn

  2. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  3. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  4. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  6. ruby-on-rails - 禁用设备的 :confirmable on-the-fly to batch-generate users - 2

    Devise是一个Ruby库,它为我提供了这个User类:classUser当写入:confirmable时,注册时会发送一封确认邮件。上周我不得不批量创建300个用户,所以我在恢复之前注释掉了:confirmable几分钟。现在我正在为用户批量创建创建一个UI,因此我需要即时添加/删除:confirmable。(我也可以直接修改Devise的源码,但我宁愿不去调和它)问题:如何即时添加/删除:confirmable? 最佳答案 WayneConrad的解决方案:user=User.newuser.skip_confirmation

  7. ruby - 为什么不能使用类IO的实例方法noecho? - 2

    print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

  8. Ruby隐藏与覆盖 - 2

    我刚刚了解到,在Java中,覆盖和隐藏之间是有区别的(静态方法是隐藏的,而不是覆盖),这意味着Java使用早期绑定(bind)和后期绑定(bind)。是否有与方法隐藏类似的东西,或者它只是具有方法重写? 最佳答案 Java具有三种不同的“方法”:实例方法,静态方法和构造函数。Ruby只有一个:实例方法。在Java中,静态方法的行为必须不同于实例方法,因为类不是对象。它们没有类,因此也没有父类(superclass),因此没有要覆盖的内容。在Ruby中,类与其他任何对象一样都是对象,它们具有一个类,该类可以具有父类(superclas

  9. ruby-on-rails - Ruby on Rails 3 中的类方法——我完全迷路了! - 2

    背景here.在上面的链接中,给出了以下示例:classauthor.id)endend除了这种语法对于像我这样的初学者来说很陌生——我一直认为类方法是用defself.my_class_method定义的——我在哪里可以找到关于类的文档RubyonRails中的方法?据我所知,类方法总是在类本身(MyClass.my_class_method)上调用,但如果R​​ails中的类方法是可链接的,似乎必须进行其他操作在这里!编辑:我想我通过对类方法的语法发表评论有点被骗了。我真的想问Rails如何使类方法可链接—我了解方法链接的工作原理,但不知道Rails如何允许您链接类方法而无需实际返

  10. ruby - 如何使用私钥加密完全加密 Ruby 中的数据? - 2

    首先,关于我们系统的一些信息,它基本上是建筑行业的电子招标解决方案。所以:列表项我们的系统有多家公司每个公司都有多个用户每家公司可以创建多个拍卖然后其他公司可以为可用的拍卖提交他们的出价。一个出价包含数百或数千个单独的项目,我们只需要加密这些记录的“价格”部分。我们面临的问题是,我们的大客户不希望我们知道投标价格,至少在投标过程中是这样,这是完全可以理解的。现在,我们只是通过对称加密对价格进行加密,因此即使价格在数据库中有效加密,他们担心的是我们拥有解密价格的key。因此,我们正在研究某种形式的公钥加密系统。以下是我们对解决方案的初步想法:当一家公司注册时,我们会使用OpenSSL为其

随机推荐