Reverse Engineering an iOS Device-Registration Request: Tracing Every Fingerprint Parameter
About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.
This research is published for educational and defensive purposes. The target sample is anonymized throughout.
Reverse Engineering an iOS Device-Registration Request: Tracing Every Fingerprint Parameter
A methodical walk through the device-registration flow of a shipping iOS app (anonymized here as com.example.targetapp): intercept the registration request, decrypt the body, and then trace each of its several dozen device-fingerprint parameters back to the code that produces them — one hook and one IDA lookup at a time. This is the kind of patient, parameter-by-parameter work that device-fingerprint and risk-control reverse engineering is made of.
Capturing the request
I used Reqable to capture traffic. The first time the device logs in, the app’s call endpoint fires for the first time and produces a critical device parameter (sk); subsequent launches call it again, but not as a “first call”. The call endpoint registers the device by sending an encrypted request body like the one below. Decrypting it with the platform’s data decryption routine gives roughly this (sensitive values already redacted):
{'skData': {
'tlapp': '1753714535994', 'bcn': '[redacted]', 'do2': '15', 'tvlc': '',
'dhdsi': 'Darwin,21.6.0,iPhone', 'nifct': '1', 'dbg': '0',
'dmc': '0778ca838aeb915[redacted]d040ccc3cf1', 'dcai': '-', 'bdn': '[redacted]',
'tnt': '1', 'dadr': '0x183be7f30|0x198dcd280|0x183c228f4|0x1843a027c',
'bstdr': 'd2802a50|d4001001|540000e3|a9bf7bfd',
'dsst': '2024-07-16 02:56:32 +0000,2024-07-16 02:56:32 +0000',
'iksour': '0', 'tkuid': '88cea24c5dc2b6fa', 'bav': '5.65.2', 'bmkd': '0',
'tfedtk': '0', 'bid': '[redacted]', 'dme': 'iPhone9,1',
'bssm': '20250727103330[redacted]', 'lacjb': '0', 'tot': '1753714543785',
'lftk': '0', 'skst': '0', 'dfdlp': '', 'ladf': '0', 'ljdb': '0',
'dsd2bt': '1753613048', 'ldjb': '0', 'tcst': '250',
'oidv': '[redacted]', 'bacdr': 'd2800430|d4001001|540000e3|a9bf7bfd',
'idv': '[redacted]', 'dcpu': 'arm64_v8,2', 'norcst': '14',
'idfs': 'da82cc8c261c12bb4b53f7b24bb8a3bb', 'lrtcai': '', 'lrsf': '',
'bcv': '1.9.3.250402', 'diud': '[redacted]',
'lfjbl': '1152921500311909827,0,0', 'dat2': '2025-07-27 10:26:55', 'lfd': '0',
'mntd2': '[redacted]', 'lspf': '', 'smid': '[redacted]',
'dcm': 'e97ff888f3e25a1f1fda881db47f4fe7', 'lnfrt': '0', 'lrtkt': '0',
'iaid': '[redacted]', 'src': 'ios', 'dat3': '1970-01-01 08:00:00',
'dtms': '31968497664', 'dss': '750,1334,2', 'ifa': '', 'dsimc': '0',
'dln': 'zh-Hans-CN', 'idfni': '8abc5649728bfa574ee08df4ff31ad24',
'idv2': '[redacted]', 'dust': '1', 'sk': '[redacted]', 'ltwi': '-',
'sksour': '2', 'lrev': '0', 'dat': '2025-07-28 22:53:52',
'itm': '7GPZR297Z9', 'dtiz': '28800', 'lism': '0',
'datmt': '1753714432398', 'dbs': '1,78', 'dvo': '0.00', 'ldjbc': '0,0',
'ikfa': '', 'dpms': '2097807360', 'tfetk': '0', 'dbt': '1753696073860',
'dint': '1745742242.944226818', 'dm': 'iPhone', 'did': 'com.example.targetapp',
'dsb': '51', 'dtu': 'App Store', 'dn': 'iPhone',
'dcm2': 'b2ffaf1cebcb07776bec74c0d5cc7d1e', 'dmpn': '[carrier]',
'do': '15.8.3', 'ljbmc': '0', 'lnrsf': '', 'ljb': '0', 'dme2': 'D10AP',
'dpxy': '1', 'datpt': '0', 'ds': '14132,30487',
'incuf': '[redacted]', 'dms': '2000.62,68.75,710.38,649.69,1674.20,314.14,3.34',
'lsimu': '0', 'cicst': '28', 'dpid': '6155', 'dmcc2': 'CN',
'lsd': 'fac37083aacff02337c3fb8797b1f3bd', 'lrspt': '0',
'mntd': '[redacted]', 'tct': '1753714543885', 'ljbac': '0', 'rcst': '21',
'lrmtc': '0', 'lnpf': '1', 'stone': '[redacted]', 'tllc': '',
'dsst2': '1745742265.564154', 'lstf': '0',
'dadir': '/private/var/containers/Bundle/Application/[redacted]/DUApp.app',
'inzt': '', 'tcdetk': '0', 'tcrp': '1', 'dsdbt': '1753613048836661',
'lrdyf': '0', 'bkv': '', 'iud': '[redacted]', 'bty': 'default',
'bty2': '1', 'nect': '1'},
'devEncParam': {'ct': '1', 'nck2': '62Q9C86sk67A0ye9BE'}
}
That’s a lot of parameters. Most of the body is a flat dictionary, so the plan is simple: figure out what each one is by hooking the code that fills it in.
Hooking the dictionary
Since every parameter in this body lands in a dictionary, the natural first move is to hook the dictionary-insert operation and filter for the field we care about. - setObject:forKeyedSubscript: is the insertion method for NSMutableDictionary:
function hook_setObject() {
var _className = "__NSDictionaryM"; // class name
var _methodName = "- setObject:forKeyedSubscript:"; // NSMutableDictionary method
var hook_method = eval('ObjC.classes.' + _className + '["' + _methodName + '"]');
Interceptor.attach(hook_method.implementation, {
onEnter: function (args) {
var str = ObjC.Object(args[3]).toString()
// console.log(str)
// Print_OC_RET(args[2], str, this.context);
if (str === "field-to-analyze") {
print_c_stack(this.context, "parameter location")
// Print_OC_RET(args[2], str, this.context);
}
}, onLeave: function (retval) {
}
});
}
The hook output gives a backtrace:

Unzip the IPA to get the Mach-O executable and drop it into IDA. The first address in the backtrace, 0x9d5602c, lands at sub_10ADD98E0 — the code that writes a parameter into the dictionary. v592 is the dictionary, and v537 is the parameter being set into it:

Rinse and repeat
From here the process is mechanical: for every parameter in the body, hook it, take the backtrace, find the site in IDA, and look at the logic that produced it. The value written into the dictionary is always a nearby v537; chase the assignment above it and you arrive at its generation logic:

For example, this one sends an a1 argument to a sharedInstance accessor. To see what that function object actually does, search for it nearby.
Working one example end to end
Take the ljbmc parameter. Hook it with the fuzzy hook above, follow the stack, and find the logic in IDA. Clicking setDuNorNYJInfo on the left shows the concrete logic:

The body of sub_10AD65F00 isn’t obvious from reading it, so hook the call site instead and dump the x2 register — the string being passed in — while watching the assembly view:
const baseAddr = Module.findBaseAddress('com.example.targetapp');
// hook the call to sub_10AD65F00 and print x2
const sub_0x9CEA1FC = baseAddr.add(0x9CEA1FC);
Interceptor.attach(sub_0x9CEA1FC, {
onEnter() {
// console.log(this.context.x2);
const obj = new ObjC.Object(this.context.x2);
console.log(obj)
}
});
The printed value:


Reading it (with a little LLM help), the logic is a jailbreak check. From there the work is the same as everywhere else: walk the code, annotate it, and then decide. If a field is a detection result, see whether you can hook around it or fake the value. If it’s a random value, construct and substitute your own. If it’s derived from some algorithm, reverse the algorithm. Step through the call endpoint’s parameters one by one, analyzing and adjusting, until the registered device is accepted.
Closing notes
Some parameters are genuinely device-bound — smid and bssm, for instance, are generated from an fcuuid value, which I handled separately at the device layer. The whole pipeline — hook the registration flow, control the device parameters, and register — is what this post maps out. The catch, as always with risk control, is that it moves: what worked two weeks ago is dead this week.
Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).
Get in touch