This research presents a security analysis on a cross-platform mobile application. The My Vodafone (Ghana) application formed the basis for the analysis; detailing both static and dynamic analysis.

Technical Details

The platform for the analysis was iOS. Details are below:

Title: My Vodafone (Ghana)
Version: 4.3.0
Bundle URL: com.vodafone.gh.myvodafone.app

Directory Structure

A decrypted and decompressed ipa file shows the following contents:

.
├── AccessibilityResources.bundle
│   ├── Info.plist
│   └── en.lproj
│       └── Localizable.strings
├── AirshipAutomationResources.bundle
├── AirshipConfig.plist
├── AirshipCoreResources.bundle
├── AirshipExtendedActionsResources.bundle
├── AirshipMessageCenterResources.bundle
├── Foundation.ttf
├── Frameworks
│   ├── NetPerformSDK.framework
│   ├── SecLibRNFramework.framework
│   ├── TealiumIOS.framework
│   └── TealiumIOSLifecycle.framework
├── GoogleMaps.bundle
├── GoogleService-Info.plist
├── Info.plist
├── Ionicons.ttf
├── LaunchScreen.storyboardc
├── MaterialCommunityIcons.ttf
├── MaterialIcons.ttf
├── Octicons.ttf
├── PkgInfo
├── SC_Info
├── SimpleLineIcons.ttf
├── Vodafone Rg Bold.ttf
├── VodafoneLt.ttf
├── VodafoneRg.ttf
├── Zocial.ttf
├── _CodeSignature
│   └── CodeResources
├── assets
│   ├── App
│   │   ├── ExternalComponents
│   │   ├── Images
├── device-names.json
├── main.jsbundle
└── myvodafoneapp

The Info.plist file contents a summary of the information related to the application.

	MinimumOSVersion: 13.0
	NSAppTransportSecurity:
    	NSExceptionDomains:
        	localhost:
            	NSExceptionAllowsInsecureHTTPLoads:  true
        	New Exception Domain:
            	NSExceptionAllowsInsecureHTTPLoads:  true
            	NSIncludesSubdomains:  true
        	NSAllowsArbitraryLoads:  true
        	DTXcodeBuild: 13F17a
        	firebase_json_raw: eyJhbmRyb2lkX3Rhc2tfZXhlY3V0b3JfbWF4aW11bV9wb29sX3NpemUiOiAxMCwgImFuZHJvaWRfdGFza19leGVjdXRvcl9rZWVwX2FsaXZlX3NlY29uZHMiOiAzfQ==
            	UISupportedDevices[0]: iPhone10,1
    	UISupportedDevices[1]: iPhone10,4
    	UISupportedDevices[2]: iPhone12,8
    	UISupportedDevices[3]: iPhone9,1
    	UISupportedDevices[4]: iPhone9,3
        	DTAppStoreToolsBuild: 13F100
        	CFBundleName: myvodafoneapp
            	CFBundleSupportedPlatforms[0]: iPhoneOS
        	CFBundleDisplayName: My Vodafone
        	ITSDRMScheme: v2
        	DTPlatformBuild: 19F64
        	CFBundleSignature: ????
        	DTXcode: 1340
        	CFBundleVersion: 82
        	DTSDKName: iphoneos15.5
            	UIDeviceFamily[0]: 1
    	UIDeviceFamily[1]: 2
            	UIBackgroundModes[0]: remote-notification
        	UIFileSharingEnabled:  true
        	CFBundleIcons:
            	CFBundlePrimaryIcon:
                	CFBundleIconName: AppIcon
                        	CFBundleIconFiles[0]: AppIcon60x60
            	DTPlatformName: iphoneos
            	CFBundleDevelopmentRegion: en
            	NSLocationWhenInUseUsageDescription: To show you Vodafone retail shops and other important information based on your location
                  	FirebaseDynamicLinksCustomDomains[0]: https://vodafone.com.gh/home
            	NSLocationAlwaysAndWhenInUseUsageDescription: My Vodafone would like to access your location
                  	CFBundleURLTypes[0]:
                        	CFBundleURLSchemes[0]: mva
                	CFBundleTypeRole: Editor
                	CFBundleURLName: myvodafoneapp
            	CFBundleURLTypes[1]: com.vodafone.gh.myvodafone.app
            	LSRequiresIPhoneOS:  true
            	CFBundleURLTypes[2]: To set profile pictures for your accounts
            	CFBundleURLTypes[3]: myvodafoneapp
            	CFBundleURLTypes[4]: 21G72
            	CFBundleURLTypes:
        	CFBundlePackageType: APPL
	LSApplicationQueriesSchemes[0]: whatsapp
	LSApplicationQueriesSchemes[1]: vodafonemusic
	LSApplicationQueriesSchemes[2]: fb
	LSApplicationQueriesSchemes[3]: youtube
	LSApplicationQueriesSchemes[4]: twitter
	LSApplicationQueriesSchemes[5]: 2ctv
	LSApplicationQueriesSchemes[6]: wi-flix
	LSApplicationQueriesSchemes[7]: dreamlab
	NSContactsUsageDescription: Get easy access to your contacts during transactions. Eg: VFCash and Top Up
	UIUserInterfaceStyle: Light
	DTCompiler: com.apple.compilers.llvm.clang.1_0
	UIRequiredDeviceCapabilities[0]: arm64
	NSLocationAlwaysUsageDescription: To show you Vodafone retail shops and other important information based on your location
	UIViewControllerBasedStatusBarAppearance:  false
	NSCameraUsageDescription: To set profile pictures for your accounts
	UISupportedInterfaceOrientations[0]: UIInterfaceOrientationPortrait
	CFBundleInfoDictionaryVersion: 6.0
	UIAppFonts[0]: Vodafone Rg Bold.ttf
	UIAppFonts[1]: VodafoneLt.ttf
	UIAppFonts[2]: VodafoneRg.ttf
	UIAppFonts[3]: AntDesign.ttf
	UIAppFonts[4]: Entypo.ttf
	UIAppFonts[5]: EvilIcons.ttf
	UIAppFonts[6]: Feather.ttf
	UIAppFonts[7]: FontAwesome.ttf
	UIAppFonts[8]: FontAwesome5_Brands.ttf
	UIAppFonts[9]: FontAwesome5_Regular.ttf
	UIAppFonts[10]: FontAwesome5_Solid.ttf
	UIAppFonts[11]: Fontisto.ttf
	UIAppFonts[12]: Foundation.ttf
	UIAppFonts[13]: Ionicons.ttf
	UIAppFonts[14]: MaterialCommunityIcons.ttf
	UIAppFonts[15]: MaterialIcons.ttf
	UIAppFonts[16]: Octicons.ttf
	UIAppFonts[17]: SimpleLineIcons.ttf
	UIAppFonts[18]: Zocial.ttf
	NSAppleMusicUsageDescription: To allow access to Apple Music
	FirebaseCrashlyticsCollectionEnabled:  false
	DTSDKBuild: 19F64
	UILaunchStoryboardName: LaunchScreen
	DTPlatformVersion: 15.5
	CFBundleShortVersionString: 4.3.0
	LSSupportsOpeningDocumentsInPlace: YES
	UIRequiresFullScreen:  true

In this file the supported devices are indicated; iPhone 10,4 etc. There is an exception to allow insecure HTTP loads.

There is a firebase_json_raw field which is base64 encoded and its decode value is {"android_task_executor_maximum_pool_size": 10, "android_task_executor_keep_alive_seconds": 3}; nothing really interesting here.

From the Info.plist file, there is a permission request for location, camera and contacts; NSLocation, NSCamera and NSContacts.

Interestingly, there is a permission to allow access to Apple Music; 😅 indicated as NSAppleMusicUsageDescription: To allow access to Apple Music.

The default application is indicated by CFBundleURLName and is myvodafoneapp. The bundle URL is defined by CFBundleURLTypes and the value is com.vodafone.gh.myvodafone.app.

There are eight(8) launch services registered in the application (for more details on launch services visit https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html); whatsapp, vodafonemusic, fb, youtube, twitter, 2ctv, wi-flix and dreamlab.

There are about 19 fonts that are reference in the Info.plist file. The app version is indicated by the key CFBundleShortVersionString and it’s value is 4.3.0.

Inside the directory, there is a GoogleService-Info.plist file; which contains google services API keys, firebase database URL etc.

ANDROID_CLIENT_ID: ***bc89vjr4p4.apps.googleusercontent.com
API_KEY: AIza***3kzY
BUNDLE_ID: com.vodafone.gh.myvodafone.app
CLIENT_ID: ***-3fb9i3mkeok8dd***2t.apps.googleusercontent.com
DATABASE_URL: https://vodaf***.firebaseio.com
GCM_SENDER_ID: 63***49
GOOGLE_APP_ID: 1:63*****749:ios:e91d****7a04
IS_ADS_ENABLED: false
IS_ANALYTICS_ENABLED: false
IS_APPINVITE_ENABLED: true
IS_GCM_ENABLED: true
IS_SIGNIN_ENABLED: true
PLIST_VERSION: 1
PROJECT_ID: vodafoneapp-****
REVERSED_CLIENT_ID: com.googleusercontent.apps.63***49-3fb9***g2t
STORAGE_BUCKET: vodafoneapp-****.ot.com

The application contains AirShip (https://www.airship.com) configuration files and bundles; the default AirshipConfig.plist file contains the following contents:

developmentAppKey: Fzs****RhBw
developmentAppSecret: Vbv***oMXMQ
inProduction: true
productionAppKey: bo****-ZQV1g
productionAppSecret: zTY****l8Q

The framework directory contains, NetPerformSDK.framework, SecLibRNFramework.framework, TealiumIOS.framework and TealiumIOSLifecycle.framework. The NetPerformSDK is used for network speed testing, the SecLibRNFramework implement security functions (such as encryption/decryption of bytes of data, etc.), the TealiumIOS is a customer data hub framework.

The directory contains device-names.json file which indicates the various iOS devices supported by the application.

Static Analysis of Application

The application was developed using React Native. In the root directory of the application is a file named main.jsbundle; a bundled javascript of the whole application.

The bundle javascript when opened in a TextEditor contains a bunch of minified javascript code; shown below.

var __BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now(),__DEV__=false,process=this.process||{},__METRO_GLOBAL_PREFIX__='';process.env=process.env||{};process.env.NODE_ENV=process.env.NODE_ENV||"production";
!(function(r){"use strict";r.__r=o,r[__METRO_GLOBAL_PREFIX__+"__d"]=function(r,i,n){if(null!=e[i])return;var o={dependencyMap:n,factory:r,hasError:!1,importedAll:t,importedDefault:t,isInitialized:!1,publicModule:{exports:{}}};e[i]=o},r.__c=n,r.__registerSegment=function(r,t,i){s[r]=t,i&&i.forEach(function(t){e[t]||v.has(t)||v.set(t,r)})};var e=n(),t={},i={}.hasOwnProperty;function n(){return e=Object.create(null)}function o(r){var t=r,i=e[t];return i&&i.isInitialized?i.publicModule.exports:d(t,i)}function l(r){var i=r;if(e[i]&&e[i].importedDefault!==t)return e[i].importedDefault;var n=o(i),l=n&&n.__esModule?n.default:n;return e[i].importedDefault=l}function u(r){var n=r;if(e[n]&&e[n].importedAll!==t)return e[n].importedAll;var l,u=o(n);if(u&&u.__esModule)l=u;else{if(l={},u)for(var a in u)i.call(u,a)&&(l[a]=u[a]);l.default=u}return e[n].importedAll=l}o.importDefault=l,o.importAll=u;var a=!1;function d(e,t){if(!a&&r.ErrorUtils){var i;a=!0;try{i=h(e,t)}catch(e){r.ErrorUtils.reportFatalError(e)}return a=!1,i}return h(e,t)}var f=16,c=65535;function p(r){return{segmentId:r>>>f,localId:r&c}}o.unpackModuleId=p,o.packModuleId=function(r){return(r.segmentId<<f)+r.localId};var s=[],v=new Map;function h(t,i){if(!i&&s.length>0){var n,a=null!==(n=v.get(t))&&void 0!==n?n:0,d=s[a];null!=d&&(d(t),i=e[t],v.delete(t))}var f=r.nativeRequire;if(!i&&f){var c=p(t),h=c.segmentId;f(c.localId,h),i=e[t]}if(!i)throw Error('Requiring unknown module "'+t+'".');if(i.hasError)throw _(t,i.error);i.isInitialized=!0;var m=i,g=m.factory,I=m.dependencyMap;try{var M=i.publicModule;return M.id=t,g(r,o,l,u,M,M.exports,I),i.factory=void 0,i.dependencyMap=void 0,M.exports}catch(r){throw i.hasError=!0,i.error=r,i.isInitialized=!1,i.publicModule.exports=void 0,r}}function _(r,e){return Error('Requiring module "'+r+'", which threw an exception: '+e)}})('undefined'!=typeof globalThis?globalThis:'undefined'!=typeof global?global:'undefined'!=typeof window?window:this);
!(function(n){var e=(function(){function n(n,e){return n}function e(n){var e={};return n.forEach(function(n,r){e[n]=!0}),e}function r(n,r,u){if(n.formatValueCalls++,n.formatValueCalls>200)return"[TOO BIG formatValueCalls "+n.formatValueCalls+" exceeded limit of 200]";var f=t(n,r);if(f)return f;var c=Object.keys(r),s=e(c);if(d(r)&&(c.indexOf('message')>=0||c.indexOf('description')>=0))return o(r);if(0===c.length){if(v(r)){var g=r.name?': '+r.name:'';return n.stylize('[Function'+g+']','special')}if(p(r))return n.stylize(RegExp.prototype.toString.call(r),'regexp');if(y(r))return n.stylize(Date.prototype.toString.call(r),'date');if(d(r))return o(r)}var h,b,m='',j=!1,O=['{','}'];(h=r,Array.isArray(h)&&(j=!0,O=['[',']']),v(r))&&(m=' [Function'+(r.name?': '+r.name:'')+']');return p(r)&&(m=' '+RegExp.prototype.toString.call(r)),y(r)&&(m=' '+Date.prototype.toUTCString.call(r)),d(r)&&(m=' '+o(r)),0!==c.length||j&&0!=r.length?u<0?p(r)?n.stylize(RegExp.prototype.toString.call(r),'regexp'):n.stylize('[Object]','special'):(n.seen.push(r),b=j?i(n,r,u,s,c):c.map(function(e){return l(n,r,u,s,e,j)}),n.seen.pop(),a(b,m,O)):O[0]+m+O[1]}function t(n,e){if(s(e))return n.stylize('undefined','undefined');if('string'==typeof e){var r="'"+JSON.stringify(e).replace(/^"|"$/g,'').replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return n.stylize(r,'string')}return c(e)?n.stylize(''+e,'number'):u(e)?n.stylize(''+e,'boolean'):f(e)?n.stylize('null','null'):void 0}function o(n){return'['+Error.prototype.toString.call(n)+']'}function i(n,e,r,t,o){for(var i=[],a=0,u=e.length;a<u;++a)b(e,String(a))?i.push(l(n,e,r,t,String(a),!0)):i.push('');return o.forEach(function(o){o.match(/^\d+$/)||i.push(l(n,e,r,t,o,!0))}),i}function l(n,e,t,o,i,l){var a,u,c;if((c=Object.getOwnPropertyDescriptor(e,i)||{value:e[i]}).get?u=c.set?n.stylize('[Getter/Setter]','special'):n.stylize('[Getter]','special'):c.set&&(u=n.stylize('[Setter]','special')),b(o,i)||(a='['+i+']'),u||(n.seen.indexOf(c.value)<0?(u=f(t)?r(n,c.value,null):r(n,c.value,t-1)).indexOf('\n')>-1&&(u=l?u.split('\n').map(function(n){return'  '+n}).join('\n').substr(2):'\n'+u.split('\n').map(function(n){return'   '+n}).join('\n')):u=n.stylize('[Circular]','special')),s(a)){if(l&&i.match(/^\d+$/))return u;(a=JSON.stringify(''+i)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=n.stylize(a,'name')):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=n.stylize(a,'string'))}return a+': '+u}function a(n,e,r){return n.reduce(function(n,e){return 0,e.indexOf('\n')>=0&&0,n+e.replace(/\u001b\[\d\d?m/g,'').length+1},0)>60?r[0]+(''===e?'':e+'\n ')+' '+n.join(',\n  ')+' '+r[1]:r[0]+e+' '+n.join(', ')+' '+r[1]}function u(n){return'boolean'==typeof n}function f(n){return null===n}function c(n){return'number'==typeof n}function s(n){return void 0===n}function p(n){return g(n)&&'[object RegExp]'===h(n)}function g(n){return'object'==typeof n&&null!==n}function y(n){return g(n)&&'[object Date]'===h(n)}function d(n){return g(n)&&('[object Error]'===h(n)||n instanceof Error)}function v(n){return'function'==typeof n}function h(n){return Object.prototype.toString.call(n)}function b(n,e){return Object.prototype.hasOwnProperty.call(n,e)}return function(e,t){return r({seen:[],formatValueCalls:0,stylize:n},e,t.depth)}})(),r='(index)',t={trace:0,info:1,warn:2,error:3},o=[];o[t.trace]='debug',o[t.info]='log',o[t.warn]='warning',o[t.error]='error';var i=1;function l(r){return function(){var l;l=1===arguments.length&&'string'==typeof arguments[0]?arguments[0]:Array.prototype.map.call(arguments,function(n){return e(n,{depth:10})}).join(', ');var a=arguments[0],u=r;'string'==typeof a&&'Warning: '===a.slice(0,9)&&u>=t.error&&(u=t.warn),n.__inspectorLog&&n.__inspectorLog(o[u],l,[].slice.call(arguments),i),s.length&&(l=p('',l)),n.nativeLoggingHook(l,u)}}function a(n,e){return Array.apply(null,Array(e)).map(function(){return n})}var u="\u2502",f="\u2510",c="\u2518",s=[];function p(n,e){return s.join('')+n+' '+(e||'')}if(n.nativeLoggingHook){n.console;n.console={error:l(t.error),info:l(t.info),log:l(t.info),warn:l(t.warn),trace:l(t.trace),debug:l(t.trace),table:function(e){if(!Array.isArray(e)){var o=e;for(var i in e=[],o)if(o.hasOwnProperty(i)){var l=o[i];l[r]=i,e.push(l)}}if(0!==e.length){var u=Object.keys(e[0]).sort(),f=[],c=[];u.forEach(function(n,r){c[r]=n.length;for(var t=0;t<e.length;t++){var o=(e[t][n]||'?').toString();f[t]=f[t]||[],f[t][r]=o,c[r]=Math.max(c[r],o.length)}});for(var s=y(c.map(function(n){return a('-',n).join('')}),'-'),p=[y(u),s],g=0;g<e.length;g++)p.push(y(f[g]));n.nativeLoggingHook('\n'+p.join('\n'),t.info)}else n.nativeLoggingHook('',t.info);function y(n,e){var r=n.map(function(n,e){return n+a(' ',c[e]-n.length).join('')});return e=e||' ',r.join(e+'|'+e)}},group:function(e){n.nativeLoggingHook(p(f,e),t.info),s.push(u)},groupEnd:function(){s.pop(),n.nativeLoggingHook(p(c),t.info)},groupCollapsed:function(e){n.nativeLoggingHook(p(c,e),t.info),s.push(u)},assert:function(e,r){e||n.nativeLoggingHook('Assertion failed: '+r,t.error)}},Object.defineProperty(console,'_isPolyfilled',{value:!0,enumerable:!1})}else if(!n.console){function g(){}var y=n.print||g;n.console={debug:y,error:y,info:y,log:y,trace:y,warn:y,assert:function(n,e){n||y('Assertion failed: 

An unbundled code can be generated by dumping the entired bundled JS in an unminify (https://unminifyjs.sperixlabs.org). This results in a more refined JS; as shown below.

var __BUNDLE_START_TIME__ = this.nativePerformanceNow ? nativePerformanceNow() : Date.now(),
    __DEV__ = false,
    process = this.process || {},
    __METRO_GLOBAL_PREFIX__ = '';
process.env = process.env || {};
process.env.NODE_ENV = process.env.NODE_ENV || "production";
!(function(r) {
    "use strict";
    r.__r = o, r[__METRO_GLOBAL_PREFIX__ + "__d"] = function(r, i, n) {
        if (null != e[i]) return;
        var o = {
            dependencyMap: n,
            factory: r,
            hasError: !1,
            importedAll: t,
            importedDefault: t,
            isInitialized: !1,
            publicModule: {
                exports: {}
            }
        };
        e[i] = o
    }, r.__c = n, r.__registerSegment = function(r, t, i) {
        s[r] = t, i && i.forEach(function(t) {
            e[t] || v.has(t) || v.set(t, r)
        })
    };
    var e = n(),
        t = {},
        i = {}.hasOwnProperty;

    function n() {
        return e = Object.create(null)
    }

    function o(r) {
        var t = r,
            i = e[t];
        return i && i.isInitialized ? i.publicModule.exports : d(t, i)
    }

    function l(r) {
        var i = r;
        if (e[i] && e[i].importedDefault !== t) return e[i].importedDefault;
        var n = o(i),
            l = n && n.__esModule ? n.default : n;
        return e[i].importedDefault = l
    }

    function u(r) {
        var n = r;
        if (e[n] && e[n].importedAll !== t) return e[n].importedAll;
        var l, u = o(n);
        if (u && u.__esModule) l = u;
        else {
            if (l = {}, u)
                for (var a in u) i.call(u, a) && (l[a] = u[a]);
            l.default = u
        }
        return e[n].importedAll = l
    }
    o.importDefault = l, o.importAll = u;
    var a = !1;

    function d(e, t) {
        if (!a && r.ErrorUtils) {
            var i;
            a = !0;
            try {
                i = h(e, t)
            } catch (e) {
                r.ErrorUtils.reportFatalError(e)
            }
            return a = !1, i
        }
        return h(e, t)
    }
    var f = 16,
        c = 65535;

    function p(r) {
        return {
            segmentId: r >>> f,
            localId: r & c
        }
    }
    o.unpackModuleId = p, o.packModuleId = function(r) {
        return (r.segmentId << f) + r.localId
    };
    var s = [],
        v = new Map;

    function h(t, i) {
        if (!i && s.length > 0) {
            var n, a = null !== (n = v.get(t)) && void 0 !== n ? n : 0,
                d = s[a];
            null != d && (d(t), i = e[t], v.delete(t))
        }
        var f = r.nativeRequire;
        if (!i && f) {
            var c = p(t),
                h = c.segmentId;
            f(c.localId, h), i = e[t]
        }
        if (!i) throw Error('Requiring unknown module "' + t + '".');
        if (i.hasError) throw _(t, i.error);
        i.isInitialized = !0;
        var m = i,
            g = m.factory,
            I = m.dependencyMap;
        try {
            var M = i.publicModule;
            return M.id = t, g(r, o, l, u, M, M.exports, I), i.factory = void 0, i.dependencyMap = void 0, M.exports
        } catch (r) {
            throw i.hasError = !0, i.error = r, i.isInitialized = !1, i.publicModule.exports = void 0, r
        }
    }

A deep dive through the unminified JS file reveals some default endpoints and constants such as:

 var o = {
        vfCashQueryStatement: "vfCashQueryStatement",
        smartSurfPurchase: "smartSurfPurchase",
        smartSurfBalanceSummary: "smartSurfBalanceSummary",
        vfCashGetTerminalDetails: "vfCashGetTerminalDetails",
        payMerchantQR: "payMerchantQR",
        vfCashQRCodeDetails: "getQRDetails",
        vfCashNewEndpoint: "https://*****appmw.vodafone.com.gh/MVAppAPI/VF_Cash",
        vfCashNewEndpointUAT: "https://*****appmw.vodafone.com.gh/MVAppAPIUAT/VF_Cash",
        vfCashDeleteFreqContacts: "vfCashDeleteFreqContacts",
        vfCashGetFreqContacts: "vfCashGetFreqContacts",
        vfCashAddFreqContact: "vfCashAddFreqContacts",
        TestUrl: "https://*****appmw.vodafone.com.gh/MVAppAPIUAT/User",
        vfCashTestUrl: "https://*****appmw.vodafone.com.gh/MVAppAPI/User",
        .....
};

The UserLoginAction is shown below:

var u = n.default.loginUserAction,
                S = {
                    username: t,
                    password: s,
                    action: u,
                    OS: o
                };
            return function(t, s) {
                t(x(!0)), (0, r(d[16]).makeRequest)(n.default.userAuthenticationUrl, 'post', S, function(n) {
                    t(x(!1));
                    var o = n.RESPONSECODE,
                        u = s(),
                        S = JSON.parse(JSON.stringify(u.authenticate.userData)),
                        E = JSON.parse(JSON.stringify(u.authenticate.defaultService));
                    if (0 == o) {
                        var f = n.SESSION.key,
                            l = n.SESSION.secret,
                            p = n.SESSION.session,
                            A = (0, r(d[17]).handleSessionVals)(f, p, l),
                            R = p.replace(/-/g, '');
                        n.RESPONSEDATA.ServiceList;
                        S.formattedSessionId = R, S.hashedKey = A, S.sessionId = p, t(z(S, E, '')), t(w(!0)), c(!0, 'Successfully logged in')
                    } else if (1 == o) {
                        n.RESPONSEMESSAGE;
                        t(w(!1))
                    } else if (2 == o) {
                        n.RESPONSEMESSAGE;
                        t(w(!1))
                    }
                }, null)
            }

The parameters for the user login action are the username, password, action and os. From the code snippet, the response returns a session key, secret and a session value.

A hashedKey is computed from the expression A = (0, r(d[17]).handleSessionVals)(f, p, l). A formattedSesssionId is also derived from the expression R = p.replace(/-/g, '').

handleSessionVals function is shown below:

e.handleSessionVals = function(t, c, o) {
    var u = t.concat(c).concat(o);
    return n.default.hex_md5(u).substring(0, 16)
};
    

From the above function, the handleSessionVals takes the following parameters respectively: SESSION.key, SESSION.session, and SESSION.secret. To generate the handleSessionVals, the SESSION.key is concatenated with the SESSION.session, and further concatenated with the SESSION.secret. The result is passed to hex_md5 function and first 16 characters returned.

The hex_md5 function is shown below:

m.exports.hex_md5 = function(n) {
    return _(o(l(n), n.length * t))
}

It also relies on other subroutines as shown below:

var n = "", t = 8;

function o(n, t) {
    n[t >> 5] |= 128 << t % 32, n[14 + (t + 64 >>> 9 << 4)] = t;
    for (var o = 1732584193, u = -271733879, i = -1732584194, l = 271733878, d = 0; d < n.length; d += 16) {
        var _ = o,
            s = u,
            x = i,
            A = l;
        u = h(u = h(u = h(u = h(u = f(u = f(u = f(u = f(u = a(u = a(u = a(u = a(u = c(u = c(u = c(u = c(u, i = c(i, l = c(l, o = c(o, u, i, l, n[d + 0], 7, -680876936), u, i, n[d + 1], 12, -389564586), o, u, n[d + 2], 17, 606105819), l, o, n[d + 3], 22, -1044525330), i = c(i, l = c(l, o = c(o, u, i, l, n[d + 4], 7, -176418897), u, i, n[d + 5], 12, 1200080426), o, u, n[d + 6], 17, -1473231341), l, o, n[d + 7], 22, -45705983), i = c(i, l = c(l, o = c(o, u, i, l, n[d + 8], 7, 1770035416), u, i, n[d + 9], 12, -1958414417), o, u, n[d + 10], 17, -42063), l, o, n[d + 11], 22, -1990404162), i = c(i, l = c(l, o = c(o, u, i, l, n[d + 12], 7, 1804603682), u, i, n[d + 13], 12, -40341101), o, u, n[d + 14], 17, -1502002290), l, o, n[d + 15], 22, 1236535329), i = a(i, l = a(l, o = a(o, u, i, l, n[d + 1], 5, -165796510), u, i, n[d + 6], 9, -1069501632), o, u, n[d + 11], 14, 643717713), l, o, n[d + 0], 20, -373897302), i = a(i, l = a(l, o = a(o, u, i, l, n[d + 5], 5, -701558691), u, i, n[d + 10], 9, 38016083), o, u, n[d + 15], 14, -660478335), l, o, n[d + 4], 20, -405537848), i = a(i, l = a(l, o = a(o, u, i, l, n[d + 9], 5, 568446438), u, i, n[d + 14], 9, -1019803690), o, u, n[d + 3], 14, -187363961), l, o, n[d + 8], 20, 1163531501), i = a(i, l = a(l, o = a(o, u, i, l, n[d + 13], 5, -1444681467), u, i, n[d + 2], 9, -51403784), o, u, n[d + 7], 14, 1735328473), l, o, n[d + 12], 20, -1926607734), i = f(i, l = f(l, o = f(o, u, i, l, n[d + 5], 4, -378558), u, i, n[d + 8], 11, -2022574463), o, u, n[d + 11], 16, 1839030562), l, o, n[d + 14], 23, -35309556), i = f(i, l = f(l, o = f(o, u, i, l, n[d + 1], 4, -1530992060), u, i, n[d + 4], 11, 1272893353), o, u, n[d + 7], 16, -155497632), l, o, n[d + 10], 23, -1094730640), i = f(i, l = f(l, o = f(o, u, i, l, n[d + 13], 4, 681279174), u, i, n[d + 0], 11, -358537222), o, u, n[d + 3], 16, -722521979), l, o, n[d + 6], 23, 76029189), i = f(i, l = f(l, o = f(o, u, i, l, n[d + 9], 4, -640364487), u, i, n[d + 12], 11, -421815835), o, u, n[d + 15], 16, 530742520), l, o, n[d + 2], 23, -995338651), i = h(i, l = h(l, o = h(o, u, i, l, n[d + 0], 6, -198630844), u, i, n[d + 7], 10, 1126891415), o, u, n[d + 14], 15, -1416354905), l, o, n[d + 5], 21, -57434055), i = h(i, l = h(l, o = h(o, u, i, l, n[d + 12], 6, 1700485571), u, i, n[d + 3], 10, -1894986606), o, u, n[d + 10], 15, -1051523), l, o, n[d + 1], 21, -2054922799), i = h(i, l = h(l, o = h(o, u, i, l, n[d + 8], 6, 1873313359), u, i, n[d + 15], 10, -30611744), o, u, n[d + 6], 15, -1560198380), l, o, n[d + 13], 21, 1309151649), i = h(i, l = h(l, o = h(o, u, i, l, n[d + 4], 6, -145523070), u, i, n[d + 11], 10, -1120210379), o, u, n[d + 2], 15, 718787259), l, o, n[d + 9], 21, -343485551), o = v(o, _), u = v(u, s), i = v(i, x), l = v(l, A)
    }
    return Array(o, u, i, l)
}

function u(n, t, o, u, c, a) {
    return v((f = v(v(t, n), v(u, a))) << (h = c) | f >>> 32 - h, o);
    var f, h
}

function c(n, t, o, c, a, f, h) {
    return u(t & o | ~t & c, n, t, a, f, h)
}

function a(n, t, o, c, a, f, h) {
    return u(t & c | o & ~c, n, t, a, f, h)
}

function f(n, t, o, c, a, f, h) {
    return u(t ^ o ^ c, n, t, a, f, h)
}

function h(n, t, o, c, a, f, h) {
    return u(o ^ (t | ~c), n, t, a, f, h)
}

function i(n, u) {
    var c = l(n);
    c.length > 16 && (c = o(c, n.length * t));
    for (var a = Array(16), f = Array(16), h = 0; h < 16; h++) a[h] = 909522486 ^ c[h], f[h] = 1549556828 ^ c[h];
    var i = o(a.concat(l(u)), 512 + u.length * t);
    return o(f.concat(i), 640)
}

function v(n, t) {
    var o = (65535 & n) + (65535 & t);
    return (n >> 16) + (t >> 16) + (o >> 16) << 16 | 65535 & o
}

function l(n) {
    for (var o = Array(), u = 0; u < n.length * t; u += t) o[u >> 5] |= (255 & n.charCodeAt(u / t)) << u % 32;
    return o
}

function d(n) {
    for (var o = "", u = 0; u < 32 * n.length; u += t) o += String.fromCharCode(n[u >> 5] >>> u % 32 & 255);
    return o
}

function _(n) {
    for (var t = "", o = 0; o < 4 * n.length; o++) t += "0123456789abcdef".charAt(n[o >> 2] >> o % 4 * 8 + 4 & 15) + "0123456789abcdef".charAt(n[o >> 2] >> o % 4 * 8 & 15);
    return t
}

function s(t) {
    for (var o = "", u = 0; u < 4 * t.length; u += 3)
        for (var c = (t[u >> 2] >> u % 4 * 8 & 255) << 16 | (t[u + 1 >> 2] >> (u + 1) % 4 * 8 & 255) << 8 | t[u + 2 >> 2] >> (u + 2) % 4 * 8 & 255, a = 0; a < 4; a++) 8 * u + 6 * a > 32 * t.length ? o += n : o += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(c >> 6 * (3 - a) & 63);
    return o
}

From the analysis of the bundle JS, all network requests aside the UserLoginAction involve an encryption of the request payload and a decryption of the response payload. Example is shown below:

return function(t) {
	(0, r(d[18]).encrypt2)(l, S, S).then(function(s) {
	    t(Y('deleting default service'));
	    var f = {
	        requestBody: s
	    };
	    (0, r(d[16]).makeRequest)(n.default.userAuthenticationUrl, 'post', f, function(n) {
	        (0, r(d[18]).decrypt2)(n.responseBody, S, S).then(function(n) {
	            t(Y('deleting account response'));
	            var s = JSON.parse(n),
	                f = s.RESPONSECODE;
	            if (0 === f) {
	                t(ie()), t(se(E, o, c, u, S)), t(Y('deleting account passed'));
	                var l = s.RESPONSEMESSAGE;
	                t(oe(l))
	            } else if (1 === f || 2 === f) {
	                t(Y('deleting account failed'));
	                var l = s.RESPONSEMESSAGE;
	                t(oe(l))
	            }
	        })
	    }, p)
	})
}

The next action is to figure out what encrypt2 and decrypt2 do. These two functions are shown below:

e.encrypt2 = e.decrypt2 = void 0;
var n = r(d[0])(r(d[1])),
    t = r(d[0])(r(d[2])),
    u = r(d[3]),
    c = u.NativeModules.AesCrypto;
e.encrypt2 = function(o, f, l) {
    return n.default.async(function(n) {
        for (;;) switch (n.prev = n.next) {
            case 0:
                if ('android' !== u.Platform.OS) {
                    n.next = 2;
                    break
                }
                return n.abrupt("return", c.encrypt(o, f, l).then(function(n) {
                    return n
                }).catch(function(n) {}));
            case 2:
                return n.abrupt("return", t.default.encrypt(o, f, l).then(function(n) {
                    return n
                }).catch(function(n) {}));
            case 3:
            case "end":
                return n.stop()
        }
    }, null, null, null, Promise)
};
e.decrypt2 = function(o, f, l) {
    return n.default.async(function(n) {
        for (;;) switch (n.prev = n.next) {
            case 0:
                if ('android' !== u.Platform.OS) {
                    n.next = 2;
                    break
                }
                return n.abrupt("return", c.decrypt(o, f, l).then(function(n) {
                    return n
                }).catch(function(n) {}));
            case 2:
                return n.abrupt("return", t.default.decrypt(o, f, l).then(function(n) {
                    return n
                }).catch(function(n) {}));
            case 3:
            case "end":
                return n.stop()
        }
    }, null, null, null, Promise)
}

The above encryption and decryption function relies on a native AES crypto library c = u.NativeModules.AesCrypto; a React-Native bridge.

Decompiling the myvodafoneapp binary in Hopper (https://www.hopperapp.com) shows the following RCTAesCrypto encrypt/decrypt function. The parameters of both the encryption/decryption function takes two arguments; the key and the initialization vector.

Encryption Code (Assembly):

-[RCTAesCrypto encrypt:appkey:gIv:resolver:rejecter:]:
00000001006b4d34         stp        x28, x27, [sp, #-0x60]!                     ; Objective C Implementation defined at 0x10172eb58 (instance method), DATA XREF=0x10172eb58
00000001006b4d38         stp        x26, x25, [sp, #0x10]
00000001006b4d3c         stp        x24, x23, [sp, #0x20]
00000001006b4d40         stp        x22, x21, [sp, #0x30]
00000001006b4d44         stp        x20, x19, [sp, #0x40]
00000001006b4d48         stp        fp, lr, [sp, #0x50]
00000001006b4d4c         add        fp, sp, #0x50
00000001006b4d50         mov        x22, x6
00000001006b4d54         mov        x23, x5
00000001006b4d58         mov        x21, x4
00000001006b4d5c         mov        x20, x3
00000001006b4d60         mov        x0, x2                                      ; argument "instance" for method imp___stubs__objc_retain
00000001006b4d64         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4d68         mov        x19, x0
00000001006b4d6c         mov        x0, x20                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4d70         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4d74         mov        x20, x0
00000001006b4d78         mov        x0, x21                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4d7c         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4d80         mov        x21, x0
00000001006b4d84         mov        x0, x22                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4d88         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4d8c         mov        x22, x0
00000001006b4d90         adrp       x27, #0x101880000
00000001006b4d94         ldr        x25, [x27, #0x160]                          ; objc_cls_ref_SecurityUtil,__objc_class_SecurityUtil_class
00000001006b4d98         adrp       x8, #0x10186e000                            ; &@selector(sharedMenuController)
00000001006b4d9c         ldr        x24, [x8, #0x398]                           ; "encryptAESData:app_key:gIv:",@selector(encryptAESData:app_key:gIv:)
00000001006b4da0         mov        x0, x23                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4da4         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4da8         mov        x23, x0
00000001006b4dac         mov        x0, x25                                     ; argument "instance" for method imp___stubs__objc_msgSend
00000001006b4db0         mov        x1, x24                                     ; argument "selector" for method imp___stubs__objc_msgSend
00000001006b4db4         mov        x2, x19
00000001006b4db8         mov        x3, x20
00000001006b4dbc         mov        x4, x21
00000001006b4dc0         bl         imp___stubs__objc_msgSend                   ; objc_msgSend
00000001006b4dc4         mov        fp, fp
00000001006b4dc8         bl         imp___stubs__objc_retainAutoreleasedReturnValue ; objc_retainAutoreleasedReturnValue
00000001006b4dcc         mov        x25, x0
00000001006b4dd0         adrp       x8, #0x101861000                            ; &@selector(dealloc)
00000001006b4dd4         ldr        x1, [x8, #0x58]                             ; argument "selector" for method imp___stubs__objc_msgSend, "length",@selector(length)
00000001006b4dd8         bl         imp___stubs__objc_msgSend                   ; objc_msgSend
00000001006b4ddc         mov        x26, x0
00000001006b4de0         mov        x0, x25                                     ; argument "instance" for method imp___stubs__objc_release
00000001006b4de4         bl         imp___stubs__objc_release                   ; objc_release

Encryption Code (Obj C):

-(void)encrypt:(void *)arg2 appkey:(void *)arg3 gIv:(void *)arg4 resolver:(void *)arg5 rejecter:(void *)arg6 {
    var_50 = r28;
    stack[-88] = r27;
    r31 = r31 + 0xffffffffffffffa0;
    var_40 = r26;
    stack[-72] = r25;
    var_30 = r24;
    stack[-56] = r23;
    var_20 = r22;
    stack[-40] = r21;
    var_10 = r20;
    stack[-24] = r19;
    saved_fp = r29;
    stack[-8] = r30;
    r19 = [arg2 retain];
    r20 = [arg3 retain];
    r21 = [arg4 retain];
    r22 = [arg6 retain];
    r23 = [arg5 retain];
    r0 = [SecurityUtil encryptAESData:r19 app_key:r20 gIv:r21];
    r29 = &saved_fp;
    r0 = [r0 retain];
    r26 = [r0 length];
    [r0 release];
    if (r26 == 0x0) {
            (*(r22 + 0x10))(r22, @"ERROR", @"decrypt failed", 0x0);
    }
    (*(r23 + 0x10))(r23, [[SecurityUtil encryptAESData:r19 app_key:r20 gIv:r21] retain]);
    [r23 release];
    [r24 release];
    [r22 release];
    [r21 release];
    [r20 release];
    [r19 release];
    return;
}

Decryption Code (Assembly):

                     -[RCTAesCrypto decrypt:appkey:gIv:resolver:rejecter:]:
00000001006b4e94         stp        x28, x27, [sp, #-0x60]!                     ; Objective C Implementation defined at 0x10172eb70 (instance method), DATA XREF=0x10172eb70
00000001006b4e98         stp        x26, x25, [sp, #0x10]
00000001006b4e9c         stp        x24, x23, [sp, #0x20]
00000001006b4ea0         stp        x22, x21, [sp, #0x30]
00000001006b4ea4         stp        x20, x19, [sp, #0x40]
00000001006b4ea8         stp        fp, lr, [sp, #0x50]
00000001006b4eac         add        fp, sp, #0x50
00000001006b4eb0         mov        x22, x6
00000001006b4eb4         mov        x23, x5
00000001006b4eb8         mov        x21, x4
00000001006b4ebc         mov        x20, x3
00000001006b4ec0         mov        x0, x2                                      ; argument "instance" for method imp___stubs__objc_retain
00000001006b4ec4         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4ec8         mov        x19, x0
00000001006b4ecc         mov        x0, x20                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4ed0         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4ed4         mov        x20, x0
00000001006b4ed8         mov        x0, x21                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4edc         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4ee0         mov        x21, x0
00000001006b4ee4         mov        x0, x22                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4ee8         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4eec         mov        x22, x0
00000001006b4ef0         adrp       x27, #0x101880000
00000001006b4ef4         ldr        x25, [x27, #0x160]                          ; objc_cls_ref_SecurityUtil,__objc_class_SecurityUtil_class
00000001006b4ef8         adrp       x8, #0x10186e000                            ; &@selector(sharedMenuController)
00000001006b4efc         ldr        x24, [x8, #0x3a0]                           ; "decryptAESNString:app_key:gIv:",@selector(decryptAESNString:app_key:gIv:)
00000001006b4f00         mov        x0, x23                                     ; argument "instance" for method imp___stubs__objc_retain
00000001006b4f04         bl         imp___stubs__objc_retain                    ; objc_retain
00000001006b4f08         mov        x23, x0
00000001006b4f0c         mov        x0, x25                                     ; argument "instance" for method imp___stubs__objc_msgSend
00000001006b4f10         mov        x1, x24                                     ; argument "selector" for method imp___stubs__objc_msgSend
00000001006b4f14         mov        x2, x19
00000001006b4f18         mov        x3, x20
00000001006b4f1c         mov        x4, x21
00000001006b4f20         bl         imp___stubs__objc_msgSend                   ; objc_msgSend
00000001006b4f24         mov        fp, fp
00000001006b4f28         bl         imp___stubs__objc_retainAutoreleasedReturnValue ; objc_retainAutoreleasedReturnValue
00000001006b4f2c         mov        x25, x0
00000001006b4f30         adrp       x8, #0x101861000                            ; &@selector(dealloc)
00000001006b4f34         ldr        x1, [x8, #0x58]                             ; argument "selector" for method imp___stubs__objc_msgSend, "length",@selector(length)
00000001006b4f38         bl         imp___stubs__objc_msgSend                   ; objc_msgSend
00000001006b4f3c         mov        x26, x0
00000001006b4f40         mov        x0, x25                                     ; argument "instance" for method imp___stubs__objc_release
00000001006b4f44         bl         imp___stubs__objc_release                   ; objc_release

Decryption Code (Obj C):

-(void)decrypt:(void *)arg2 appkey:(void *)arg3 gIv:(void *)arg4 resolver:(void *)arg5 rejecter:(void *)arg6 {
    var_50 = r28;
    stack[-88] = r27;
    r31 = r31 + 0xffffffffffffffa0;
    var_40 = r26;
    stack[-72] = r25;
    var_30 = r24;
    stack[-56] = r23;
    var_20 = r22;
    stack[-40] = r21;
    var_10 = r20;
    stack[-24] = r19;
    saved_fp = r29;
    stack[-8] = r30;
    r19 = [arg2 retain];
    r20 = [arg3 retain];
    r21 = [arg4 retain];
    r22 = [arg6 retain];
    r23 = [arg5 retain];
    r0 = [SecurityUtil decryptAESNString:r19 app_key:r20 gIv:r21];
    r29 = &saved_fp;
    r0 = [r0 retain];
    r26 = [r0 length];
    [r0 release];
    if (r26 == 0x0) {
            (*(r22 + 0x10))(r22, @"ERROR", @"decrypt failed", 0x0);
    }
    (*(r23 + 0x10))(r23, [[SecurityUtil decryptAESNString:r19 app_key:r20 gIv:r21] retain]);
    [r23 release];
    [r24 release];
    [r22 release];
    [r21 release];
    [r20 release];
    [r19 release];
    return;
}

From the above analysis, it can be figured out that the handleSessionVals is used in the encryption and decryption of the payload; it is passed as both the key and iv.

Dynamic Analysis of the Application

Proxying the app through Burpsuite, a sample request payload for the UserLoginAction is shown below:

POST /MVAppAPI/UserSvc HTTP/1.1
Host: *****.vodafone.com.gh
Cookie: visid_incap_2779200=fGaJ****TC8gi1Asypddu; visid_incap_2779192=yX5OR1yhT***J0jIO4sGROSVst
Accept: application/json, text/plain, */*
Content-Type: application/json
User-Agent: myvodafoneapp/85 CFNetwork/1237 Darwin/20.4.0
Accept-Language: en-gb
Content-Length: 170
Accept-Encoding: gzip, deflate
Connection: close

{"username":"****","password":"****","action":"loginToAccount","os":"iOS v4.3.2","udid":"***"}

The response payload for the UserLoginAction contains the SESSION variables need to generate the AES Key.

"SESSION":{"session":"cf31ac47-**-46a6-bea3-**","secret":"*****-68ab-4303-b400-d0620751e51b","key":"cb4****f-a7af-4cff-****-03ab9c584563"},"RESPONSEMESSAGE":"Successfully processed"

Any subsequent request made after the UserLoginAction uses an encrypted payload, which is decrypted using the AES Key.

POST /MVAppAPI/User HTTP/2
Host: ***.vodafone.com.gh
Accept: application/json, text/plain, */*
Content-Type: application/json
Accept-Encoding: gzip, deflate
User-Agent: myvodafoneapp/85 CFNetwork/1237 Darwin/20.4.0
Username: ****
Session: cf31ac474f7d46a6bea3ef1c2bbf5d4b
Accept-Language: en-gb
Content-Length: 150

{"requestBody":"e1jzR6XK+kEQHazEFOvFUOTqx/eDkFZ2MvbiUsXEWiejd/ENUdLt1G0aIMtnqEmP\r\n35QQIJ24dpBv4yIiEm2lyF+FLX6UAdtXmJA9GDBhVaL+rMTT1N7RLK9e3Ov9mes5"}

A session value is added as part of the headers; Session: cf31ac474f7d46a6bea3ef1c2bbf5d4b. The value for the session key is the same as the SESSION.session variable with all the - replaced with .

A sample encrypted request payload is shown below:

POST /MVAppAPI/UserSvc HTTP/2
Host: ****.vodafone.com.gh
Cookie: incap_ses_1700_2779192=Tsu/V8sg8W0inf4mEZ2XF19YQWMAAAAARfvc5voyaCHQ85F5P4wG9A==; visid_incap_2779200=fGaJiBltQTWwMlBPhy/wUa5JL2MAAAAAQUIPAAAAAABOZ1nGbBQTC8gi1Asypddu; visid_incap_2779192=yX5OR1yhTBGPcy0+DGw0M3lJL2MAAAAAQUIPAAAAAACBiVyt59J0jIO4sGROSVst
Accept: application/json, text/plain, */*
Content-Type: application/json
Accept-Encoding: gzip, deflate
User-Agent: myvodafoneapp/85 CFNetwork/1237 Darwin/20.4.0
Username: ***
Session: cf31ac474f7d46a6bea3ef1c2bbf5d4b
Accept-Language: en-gb
Content-Length: 150

{"requestBody":"PY9DdDAVRfZat+uP/kVWa54ykhCmeS8N82elBHP3wiiLdQOnsbY+gb3DvQrTtdto\r\ngkoK/EirSeJ79ahHZ+p+A4lSCO7aOh697hn02G+PIP6Yf5jgLKGQL+DsW33NxQzC"}

For instance, the encrypted PY9DdDAVRfZat+uP/kVWa54ykhCmeS8N82elBHP3wiiLdQOnsbY+gb3DvQrTtdto\r\ngkoK/EirSeJ79ahHZ+p+A4lSCO7aOh697hn02G+PIP6Yf5jgLKGQL+DsW33NxQzC decrypts to {"action":"getAccountServices","msisdn":"***","username":"****","os":"iOS v4.3.2"} using the derived AES Key.