UPDATE: this article was corrupted for unknown reason, I wish I could find the backup :(
UPDATE: If you’re looking for a more flexible patch, check HERE.
UPDATE: modified with a more detailed disassembly listing, also gave the function-equivalent Object-C source.The carrier patch for SpringBoard allows to use customized carrier logo on status bar, however, the current patch has a limitation, it forces the SpringBoard to use the AT&T’s logo regardless of the actual carrier, you change the logo by replacing the AT&T’s logo with your own images. While this method works fine, it’s not perfect though, because if you roam you can’t tell the current carrier from the logo anymore, it always shows the AT&T’s logo (or your customized one).This article introduces a better way to patch, which adds your carrier into the SpringBoard without affecting the current carriers. You’ll have the same roaming experience like you have on an unpatched SpringBoard. Since this requires some reversing experience, I’ll describe how to reverse and patch the SpringBoard in details. The SpringBoard being analyzed in this post is version 1.1.1 version 1.1.2 (sorry, my head was really messed :).
Learn the Calling Convention
Before we can patch the SpringBoard, we have to know how the function call is made, which registers are used during the function call, which registers store the return values. Let’s start by analyzing the [NSAutoreleasePool alloc], as it is commonly used in Cocoa programming and it’s calling is simple, only two parameters, an object name “NSAutoreleasePool” and a method name “alloc”.
The following is extracted from the SpringBoard disassembly listing which contains the info we need to analyze the initial calling convention:
LDR R0, =p_NSAutoreleasePool LDR R1, =p_alloc ADD R7, SP, #0x14+var_8 LDR R0, [R0] ; R0 -> "NSAutoreleasePool" LDR R1, [R1] ; R1 -> "alloc" BL _objc_msgSend LDR R1, =p_init LDR R1, [R1] ; R1 -> "init" BL _objc_msgSend
From the first _objc_msgSend in the listing we know when calling [NSAutoreleasePool alloc], two registers are assigned as below:
R0 -> “NSAutoreleasePool”
R1 -> “alloc”
But what about the return value? Well, the second _objc_msgSend shows that only R1 is re-assigned (to ‘init’), nothing more. According to the first _objc_msgSend we know that the R0 should be the object name, but there’s nowhere setting up the R0 for the second _objc_msgSend, so it must come from the function return value. Now we can translate the above info into a Object-C souce line:
[[NSAutoreleasePool alloc] init]; // return value in R0
This is quite reasonable in a Cocoa program. Keep this in mind: When calling _objc_msgSend, R0 points to the object name, R1 points to the method name, the return value is in R0. The more complex calling conventions (with parameters) will be revealed later when we analyze the to-be-patched function.
Find the Right Place to Patch
Now we need to think where is a good place to patch. Obviously the best place is in the function which returns the carrier logo filename. Let’s start from the IDA Pro’s Strings view, and locate the “at&t” string, tracking from there, after a few jumps, we will finally get to a function named operatorNameForIcon:. From its name, it’s easy to tell this is just the right place for us to patch :)Here is the disassembly listing of the function operatorNameForIcon:, I have commented/labeled it for easy reading:
; ---------------------------------------------------------------------------
; - (id) operatorIconForName:(CFString *)theName;;; theName: current carrier name
; ---------------------------------------------------------------------------
operatorIconForName ; DATA XREF: __inst_meth:000AAE00var_18 = -0x18
var_8 = -8STMFD SP!, {R4-R7,LR}
SUBS R5, R2, #0
ADD R7, SP, #0x14+var_8
STR R8, [SP,#0x14+var_18]!
SUB SP, SP, #4
MOV R4, R0
BEQ unknown_operator
LDR R3, [R0,#0x20] ; _mode
LDR R6, =p__operatorName_isEqualToName_allowPrefixMatch
CMP R3, #0
MOV R3, #0
STR R3, [SP,#0x18+var_18]
LDR R1, [R6]
LDR R3, =cfstr_att
MOVNE R8, #3 ; if (_mode)
; mode = 3;
MOVEQ R8, #0 ; else
; mode = 0;
BL _objc_msgSend ; found = [operator operatorName:theName
; isEqualToName:@"at&t"
; allowPrefixMatch:0];
UXTB R0, R0
CMP R0, #0
BEQ chk_cingular ; if (!found)
; goto chk_cingular;
operator_is_att
LDR R0, =p_SBStatusBarController
LDR R1, =p_statusBarImageNamed_forMode
LDR R2, =cfstr_CARRIER_ATT ; carrier = @"CARRIER_ATT";
LDR R0, [R0]
LDR R1, [R1]
B operator_foundchk_cingular
STR R0, [SP,#0x18+var_18]
LDR R1, [R6]
MOV R0, R4
MOV R2, R5
LDR R3, =cfstr_cingular
BL _objc_msgSend ; found = [operator operatorName:theName
; isEqualToName:@"cingular"
; allowPrefixMatch:0];
TST R0, #0xFF
BEQ chk_tmobile ; if (!found)
; goto chk_tmobile;
operator_is_cingular
LDR R0, =p_SBStatusBarController
LDR R1, =p_statusBarImageNamed_forMode
LDR R2, =cfstr_CARRIER_CINGULAR ; carrier = @"CARRIER_CINGULAR";
LDR R0, [R0]
LDR R1, [R1]operator_found
MOV R3, R8
BL _objc_msgSend ; [SBStatusBarController statusBarImageNamed:carrier
; forMode:mode];
B return2callerchk_tmobile
MOV R3, #1
STR R3, [SP,#0x18+var_18]
LDR R1, [R6]
MOV R0, R4
MOV R2, R5
LDR R3, =cfstr_tmobile
BL _objc_msgSend ; found = [operator operatorName:theName

One Comment
Great site…especially the educational articles on disassembly/decompilation of iPhone stuff. There is very little info on the Internet about this type of thing in general (aimed at the inexperienced), and much less about iPhone/OS X/ObjC. I’m trying to figure out what [AVRecorder activate] is doing in Celestial, and this site is an extremely valuable resource.