Скрийте напълно телефонно обаждане в iOS (устройство за джейлбрейк)

Искам напълно да скрия телефонно обаждане в ios. Моят приоритет е да направя това на ios 7 (най-новата версия на ios към момента!), но бих искал да знам как да скрия телефонно обаждане на ios 6 и по-стари, ако е възможно. Намерих някои функции за това като свързване към initWithAlertController метод от клас SBUIFullscreenAlertAdapter. Благодарение на creker в тази връзка намерих друг метод за закачане, който е по-добре така. Проблемът е, че все още има лента за повикване, когато телефонът не е заключен или когато телефонът е заключен, телефонът показва, че е в средата на комуникацията. Ето екранни снимки: връзка към изображение

Искам да знам какви са методите за справяне с това за закачане? Има ли нещо друго, което трябва да знам, за да постигна това, което искам?

За изтриване на всякакви други следи, които са останали, за които се сетих след приключване на обаждането, изтривам историята на обажданията от нейната база данни. Има ли по-добър начин?


person iFred    schedule 29.03.2014    source източник


Отговори (1)


Ще се опитам да публикувам колкото мога код, но няма да работи от нулата. Използвам собствени макроси за генериране на кукички, така че трябва да ги пренапишете, за да работят с вашия код. Ще използвам псевдо функция IsHiddenCall, за да определя дали дадено обаждане е нашето скрито обаждане (проста проверка на телефонен номер). Тук е, за да опрости кода. Очевидно трябва да го приложите сами. Ще има и други псевдо функции, но тяхното изпълнение е много просто и ще бъде очевидно от техните имена. Това не е просто ощипване, така че имайте търпение.

Освен това кодът не е ARC.

По принцип ние закачаме всичко, което може да каже на iOS, че има телефонно обаждане.

iOS 7

Нека започнем с iOS 7, тъй като това е последната версия на iOS в момента и внедряването на скрито повикване е по-лесно, отколкото в iOS 6 и по-стари версии.

Почти всичко, от което се нуждаем, се намира в частен TelephonyUtilities.framework. В iOS 7 Apple премести почти всичко, свързано с телефонните разговори, в тази рамка. Ето защо стана по-просто - всички други компоненти на iOS използват тази рамка, така че трябва да я закачим само веднъж, без да е необходимо да ровим във всеки iOS демон, рамка, която може да направи нещо с телефонни обаждания.

Всички методи са закачени в два процеса - SpringBoard и MobilePhone (телефонно приложение). Идентификационните номера на пакета са съответно com.apple.springboard и com.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, когато има активно телефонно обаждане. Използвам частни API от частни Celestial.framework

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];
}

Ето метод, който закачам само в процеса на мобилен телефон

/*
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.

//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 е по-сложен. Кодът за телефония е разпръснат из много компоненти и API на iOS. Може да публикувам кода по-късно, тъй като нямам време в момента. Отговорът вече е наистина дълъг.

person creker    schedule 06.04.2014
comment
Благодаря ти много. Вашият отговор ми помогна много. Тествах го и имам някои проблеми. 1- Има грешка тук:extern id AVController_PickableRoutesAttribute; и подобни редове, казва, че не може да го намери. какъв е проблемът? 2- DisconnectAllExceptGoodCall() какво трябва да прави? Имам предвид кое обаждане е добро? - person iFred; 09.04.2014
comment
За други функции като DisconnectAllExceptHiddenCall() използвах CTCopyCurrentCalls(nil) за достъп до всички повиквания и след това използвах CTCallDisconnect(call) за всеки обект от него. Правилно ли го правя? - person iFred; 09.04.2014
comment
Когато казвате закачане в процеса на трамплин, имате предвид настройката на пакетния филтър на com.apple.springboard, а с MobilePhone имате предвид com.apple.mobilephone? Мога ли да създам всички тези с едно ощипване или трябва да има три? (един за процес MobilePhone, един за процес SpringBoard и един за двата?) - person iFred; 09.04.2014
comment
1. DisconnectAllExceptGoodCall трябва да бъде DisconnectAllExceptHiddenCall, преименувах го. 2. AVController_PickableRoutesAttribute - свързахте ли рамката? 3. DisconnectAllExceptHiddenCall трябва да прекъсне всички повиквания с изключение на нашето скрито повикване. 4. Можете да напишете едно ощипване за всичко. Всъщност аз така го правя. - person creker; 09.04.2014
comment
2- Копирах дъмп на клас на Celestial framework, за да включа папка на iOSopendev и импортирах Celestial.h. Трябва ли да направя нещо друго? - person iFred; 09.04.2014
comment
Може да се наложи да го свържете към вашия проект (пуснете го в папката на рамката във вашия проект). Не мисля, че Xcode може сам да намери частна рамка. - person creker; 09.04.2014
comment
В метод handleCallStatusChanged от клас TUCallCenter има ред CTCallAnswer(call). Типът на call е TUTelephonyCall*, но аргументът на CTCallAnswer трябва да бъде CTCallRef. Как е възможно? На някои други места имате IsHiddenCall(TUTelephonyCall*) и IsHiddenCall([TUTelephonyCall* destinationID]) как да се справя с тях? Опитах [TUTelephony* повикване], но беше грешно. Благодаря - person iFred; 10.04.2014
comment
Вижте CallStatusNotificationCallback. Там извиквам handleCallStatusChanged, предавайки CTCallRef аргумент и userInfo аргумент с HiddenCall ключ. Така че типовете са правилни. Просто използвам повторно съществуващите функции, за да свърша цялата работа на едно място. Що се отнася до IsHiddenCall, не намерих IsHiddenCall(TUTelephonyCall*). Има два случая - IsHiddenCall(CTCallRef) и IsHiddenCall(NSString*). - person creker; 10.04.2014
comment
В прототипа на handleCallStatusChanged, call е TUTelephonyCall*. Така че защо предавате CTCallRef? - person iFred; 10.04.2014
comment
Има ли значение? Аргументът call е указател, мога да предам каквото искам - методът е закачен, извикването му ще извика моята собствена реализация. Исках да преместя обработката на състоянието на повикванията на едно място и за двата процеса. Може да има и други причини, но не ги помня - беше по-скоро процес на проба-грешка. Не е нужно да правите точно това, което направих аз, но кодът ми работи, тестван е много пъти в различни сценарии. - person creker; 10.04.2014
comment
Благодаря ти creker! Скритото обаждане вече работи добре, с изключение на приложението за мобилен телефон, което има обаждането в историята на обажданията, което ще се опитам да разбера сам. Но сега има проблем с редовните разговори. В handleCallStatusChanged се срива. Не мога да намеря къде се случва, но мисля, че е в последния оператор if и оригиналната функция. Ако повикването е редовно, типът на call ще бъде TUTelephonyCall* или все още е CTCallRef? - person iFred; 10.04.2014
comment
Предполагам, че ще бъде TUTelephonyCall*, защото използвате [call destinationID]. Какъв е типът на destinationID? моето предположение е CTCallRef - person iFred; 10.04.2014
comment
destinationID връща NSString* - person creker; 10.04.2014
comment
За обикновени извиквания handleCallStatusChanged аргументът е TUTelephonyCall*, написах правилни прототипи на метода над всички кукички. Ако предам CTCallRef, тогава оригиналната реализация НЕ се извиква - има израз return за това. - person creker; 10.04.2014
comment
Добре! проблемът беше, че мислех, че destinationID върна CTCallRef!!! Сега е по-добре. Имам въпрос и той е, че не мога да се закача за PHRecentCall. Искам да кажа, когато вляза вътре initWithCall нищо не се регистрира. Някакви идеи? (Използвам пакетни филтри com.apple.mobilephone и com.apple.springboard плюс филтър Mode = "Any".) - person iFred; 10.04.2014
comment
Проверете селектора на метод, филтър за идентификатор на пакет. Не мога да помогна с много. Ето клас-изхвърляне github.com/limneos/classdump-dyld/blob/master/iphoneheaders/ Може би ще помогне. - person creker; 11.04.2014
comment
Здравей отново! Малко е късно, но има въпрос. Как бихме могли да включим само микрофона и да изключим високоговорителя? Защото, ако телефонът възпроизвежда някакъв звук, той ще бъде поставен на пауза и телефонното обаждане ще бъде на високоговорителя. - person iFred; 10.05.2014
comment
Не знам. Опитайте, може би ще проработи и ще помогне и на някой друг. - person creker; 15.05.2014
comment
Мисля, че трябва да променя атрибута, зададен за AVController_AudioCategoryAttribute във функцията SetupAVController. Това, което се задава, е @"PhoneCall", което според мен трябва да се промени. Проблемът е, че не знам с какво да го сменя!;) Можете ли да помогнете? - person iFred; 15.05.2014
comment
И аз не знам. Получих този @"PhoneCall" низ от разглобяването на SpringBoard - така се настройва аудио маршрутизирането, когато има активно телефонно обаждане. - person creker; 15.05.2014
comment
страхотна работа @creker! относно iOS 6, имате ли поне имената на методите за закачане? или рамките, които трябва да бъдат закачени? - person Alexandre Blin; 17.05.2014
comment
@creker Въпреки че това е отдавна, но можете ли да ми помогнете малко с разглобяването. Опитах се да направя някои неща, но не мога да получа това, което искам. Разгледах разглобяването на небесната рамка, но няма проверени низове в метода setAttribute:forKey. И така, как мога да достигна до правилния атрибут за моя проблем (тоест да настроя само микрофона, а не високоговорителя) - person iFred; 01.07.2014