There is yet another ARM/THUMB/C166 pattern finder.
Main features:
- Compatible with Smelter patterns syntax.
- Compatible with Ghidra SRE patterns syntax.
- Enhanced patterns syntax:
- Nested patterns for LDR.
- Half-byte patterns.
- Bitmask patterns.
- JSON output.
The name was chosen in respect to Viktor89, who is greatest patch porter in the Siemens Mobile modding scene.
- Windows: download .exe in Releases.
- ArchLinux:
yay -S ptr89-git - OSX:
brew install siemens-mobile-hacks/tap/ptr89 - Ubuntu 24.04/Debian 13+: download .deb in Releases.
- Build from sources:
git clone https://github.com/siemens-mobile-hacks/ptr89 cd ptr89 git submodule init git submodule update # Ubuntu 24.04/Debian 13+ fakeroot debian/rules binary # OSX, Linux, Unix, MinGW cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build cmake --install build # Windows cmake -B build cmake --build build --config Release
Usage: ptr89 [arguments]
Global options:
-h, --help show this help
-f, --file FILE fullflash file [required]
-b, --base HEX fullflash base address [default: A0000000 for arm, auto for c166]
-A, --arch ARCH architecture: arm or c166 [default: arm]
-a, --align N search align [default: 1]
-V, --verbose enable debug
-J, --json output as JSON
--show-bytes show all bytes for long results
Find patterns:
-p, --pattern STRING pattern to search
-n, --limit NUMBER limit results count [default 100]
Find xrefs:
-x, --xref HEX address to search
-n, --limit NUMBER limit results count [default 100]
Find patterns from functions.ini:
--from-ini FILE path to functions.ini
Prettify pattern:
--prettify STRING pattern
For C166, use -A c166 or --arch c166. If --base is not specified, it is calculated automatically:
base = 16 * 1024 * 1024 - fullflash_size
For example, a 14 MiB M55 fullflash gets base 0x200000. An explicit --base always overrides this calculation.
# F0 C8 MOV R12, R8
# F0 D9 MOV R13, R9
# DA 25 72 93 CALLS 0x25, 0x9372 -> 0x259372
# F0 C8 MOV R12, R8
# F0 D9 MOV R13, R9
# DA 9A 32 93 CALLS 0x9A, 0x9332 -> 0x9A9332
$ ptr89 -f M55_v91.bin -A c166 -p '&BL(F0C8F0D9DA257293F0C8F0D9DA9A3293 + C)'
Pattern: '&BL(F0C8F0D9DA257293F0C8F0D9DA9A3293 + C)'
Found 1 branch:
FUNCTION OFFSET
009A9332 007A9332$ ptr89 -f EL71v45.bin -x A04CA048
Xrefs to 0xA04CA048
Found 3 xrefs:
OFFSET XREF KIND
00CF63A4 A0CF63A4 branch
00FC25DC A0FC25DC branch
00FC25E0 A0FC25E0 pointer
Search done in 1612 ms$ ptr89 -f EL71v45.bin -p "F0B5061C0C1C151C85B068461122??49??????????E0207869466A460009085C307021780134"
Pattern: 'F0B5061C0C1C151C85B068461122??49??????????E0207869466A460009085C307021780134'
Found 1 address:
ADDRESS OFFSET BYTES
A058BB99 0058BB99 F0 B5 06 1C 0C 1C 15 1C 85 B0 68 46 11 22 0A 49 …
Search done in 72 ms$ ptr89 -f EL71v45.bin -p "??2800D0F5E6704780B508F0??E980BD80B5+1" -p "??B589B006A901A80522??????????49051C"
Pattern: '??2800D0F5E6704780B508F0??E980BD80B5+1'
Found 1 address:
ADDRESS OFFSET BYTES
A0092F93 00092F93 28 00 D0 F5 E6 70 47 80 B5 08 F0 58 E9 80 BD 80 …
Pattern: '??B589B006A901A80522??????????49051C'
Found 1 address:
ADDRESS OFFSET BYTES
A05C4B39 005C4B39 F0 B5 89 B0 06 A9 01 A8 05 22 3B F7 5E FF 54 49 …
Search done in 143 ms-J or --json returns full, untruncated bytes without elapsed. Search and
x-ref results intentionally use different structures:
{
"patterns": [
{
"pattern": "&BL(F0C8F0D9DA257293F0C8F0D9DA9A3293 + C)",
"type": "branch",
"results": [
{ "address": 10130226, "offset": 365140, "bytes": "DA9A3293" }
]
}
]
}{
"target": 2689376328,
"xrefs": [
{
"xref": 2697946020,
"offset": 13591460,
"type": "branch",
"bytes": "CCF21AE9"
}
]
}For a fixed address, offset and bytes are omitted. --from-ini returns a
functions array with one result object or null for each entry.
ptr89 -f EL71v45.bin --from-ini ELKA.ini > swilib.vkp
Syntax is fully compatible with WinHex, Smelter and Ghidra SRE patterns.
# Exact match one byte
B5
# Match ANY one byte
??
# Match one byte by half-byte mask
# ? is wildcard for any 4bit part of byte
2?
?2
# Match one byte by bit mask
# . is wildcard for bit
[1111....]You can use spaces or commas to separate different bytes or groups.
All these patterns are equivalent:
# With commas
??,B5,??,B0,??,1C,??,20,??,43,??,99,??,4D,??,90,??,94,??,1C,??,92,??,91,??,68,??,26,??,23,??,21,??,A2,??,48,??,47
# With spaces
?? B5 ?? B0 ?? 1C ?? 20 ?? 43 ?? 99 ?? 4D ?? 90 ?? 94 ?? 1C ?? 92 ?? 91 ?? 68 ?? 26 ?? 23 ?? 21 ?? A2 ?? 48 ?? 47
# With spaces by 2 bytes groups
??B5 ??B0 ??1C ??20 ??43 ??99 ??4D ??90 ??94 ??1C ??92 ??91 ??68 ??26 ??23 ??21 ??A2 ??48 ??47
# Without any separators
??B5??B0??1C??20??43??99??4D??90??94??1C??92??91??68??26??23??21??A2??48??47Apply some correction value to the found offset.
Syntax:
pattern + offsetCorrector
pattern - offsetCorrectorFor example:
# offsetCorrector value is always in HEX.
# For e.g.: 20 and 0x20 are equal.
B801C4E10200A0E30000C1E5B601D4E11040BDE8??????EA + 20Steps:
- Pattern
B801C4E10200A0E30000C1E5B601D4E11040BDE8??????EAfound at 0xA01A39D4 - Result is
0xA01A39D4 + 0x20 = 0xA01A39F4
Decoding a pointer value from the bytes found by the pattern.
With --arch c166, a four-byte code pointer is decoded as a little-endian 16-bit segment offset followed by an 8-bit segment number. The padding byte is ignored.
Syntax:
*( subPattern )
*( subPattern ) + valueCorrector
*( subPattern ) - valueCorrectorFor example:
# valueCorrector value is always in HEX.
# For e.g.: 20 and 0x20 are equal.
*(B801C4E10200A0E30000C1E5B601D4E11040BDE8??????EA + 20) + 2Steps:
- Pattern
B801C4E10200A0E30000C1E5B601D4E11040BDE8??????EA+20found at 0xA01A39F4 - Decoding bytes as pointer at 0xA01A39F4:
A01A39F4: B8 37 D8 A8 ; 0xA8D837B8
- Result is
0xA8D837B8 + 0x2 = 0xA8D837BA
Emulating ARM/THUMB LDR Rd, [PC, #offset] instruction found by the pattern.
The &(...) operator remains ARM-specific. C166 register-pair references are supported by the LDR{...} nested form described below.
Syntax:
&( subPattern )
&( subPattern ) + valueCorrector
&( subPattern ) - valueCorrectorFor example:
&( ??,48,??,47,??,B5,??,B0,??,1C,??,D1,??,20 ) + 0x4Steps:
- Pattern
??,48,??,47,??,B5,??,B0,??,1C,??,D1,??,20found at 0xA093BA58 - Emulating LDR on 0xA093BA58:
A093BA58: 37 48 ; ldr r0, [pc, #0xdc] ; Emulation: PC + 0xDC = 0xA093BB38
- Decoding pointer at 0xA093BB38
A093BB38: 10 97 E6 A8 ; 0xA8E69710
- Result is
0xA8E69710 + 0x4 = 0xA8E69714
Emulating ARM/THUMB B/BL/BLX or LDR PC, [PC, #offset] and C166 branch instructions found by the pattern.
Syntax:
&BL( subPattern )
&BL( subPattern ) + valueCorrector
&BL( subPattern ) - valueCorrectorFor example:
&BL( ?? ?? ?? [1111101.] 00 00 5? E3 ?? ?? 9F 05 08 40 B? 08 ?? ?? ?? 0A 08 80 B? E8 )Steps:
-
Pattern
?? ?? ?? [1111101.] 00 00 5? E3 ?? ?? 9F 05 08 40 B? 08 ?? ?? ?? 0A 08 80 B? E8found at 0xA06A09A4 -
Emulating BL on 0xA06A09A4:
A06A09A4: 7B D6 E7 FA ; BLX #0xA0096398
-
Result is
0xA0096398 | 1 = 0xA0096399
Follow the branch and checking it for a pattern.
Syntax:
# ARM B/BL/BLX, THUMB BL/BLX, or C166 CALLA/CALLS/PCALL/JMPA/JMPS/JB/JNB/JBC/JNBS
{ subPattern }
_BLF(subPattern) # alias for { }
# THUMB B or a 2-byte C166 CALLR/JMPR
[ subPattern ]Supported instructions:
# ARM
B #offset
BL #offset
BLX #offset
LDR PC, [PC, #offset]
# THUMB
BL #offset
BLX #offset
# C166 (4 bytes)
CALLA cc, caddr
CALLS seg, caddr
PCALL reg, caddr
JMPA cc, caddr
JMPS seg, caddr
JB bitaddr, rel
JNB bitaddr, rel
JBC bitaddr, rel
JNBS bitaddr, rel
# C166 (2 bytes)
CALLR rel
JMPR cc, rel
For example:
?? 1C ?? 48 ?? B5 ?? 68 { ?? 1C ?? 68 ?? 68 ?? 2B ?? D0 ?? 68 [ ?? 23 [ ?? B5 ?? 1C ?? 6E ] ] 47 } BD + 0x1Steps:
- Pattern
?? 1C ?? 48 ?? B5 ?? 68 ?? ?? ?? ?? BD + 0x1found at 0xA0978822A0978822: 01 1C ADD R1,R0,#0x0 A0978824: 62 48 LDR R0,[DAT_A09789B0] A0978826: 80 B5 PUSH {R7,LR} A0978828: 00 68 LDR R0,[R0,#0x0]=>DAT_A8DBE3F0 A097882A: 9E F0 F6 FD BL FUN_A0A1741A ; <--- see this A097882E: 80 BD POP {R7,PC}
- Emulating BL at 0xA097882A (+8)
A097882A: 9E F0 F6 FD ; BL #0xA0A1741A
- Checking pattern
?? 1C ?? 68 ?? 68 ?? 2B ?? D0 ?? 68 ?? ?? 47at 0xA0A1741AA0A1741A: 0A 1C ADD R2,R1,#0x0 A0A1741C: 01 68 LDR R1,[R0,#0x0] A0A1741E: 8B 68 LDR R3,[R1,#0x8] A0A17420: 00 2B CMP R3,#0x0 A0A17422: 01 D0 BEQ LAB_A0A17428 A0A17424: C9 68 LDR R1,[R1,#0xC] A0A17426: F6 E7 B FUN_A0A17416 ; <--- see this A0A17428: 70 47 BX LR
- Emulating BL at 0xA0A17426 (+12)
A0A17426: F6 E7 ; B #0xA0A17416
- Checking pattern
?? 23 ?? ??at 0xA0A17416A0A17416: 01 23 MOV R3,#0x1 A0A17418: D7 E7 B LAB_A0A173CA ; <--- see this
- Emulating BL at 0xA0A17418 (+2)
A0A17418: D7 E7 ; B #0xA0A173CA
- Checking pattern
?? B5 ?? 1C ?? 6Eat 0xA0A173CAA0A173CA: F8 B5 PUSH {R3,R4,R5,R6,R7,LR} A0A173CC: 04 1C ADD R4,R0,#0x0 A0A173CE: 80 6E LDR R0,[R0,#0x68] A0A173D0: 0D 1C ADD R5,R1,#0x0 A0A173D2: 16 1C ADD R6,R2,#0x0
- Pattern result is
0xA0978822 + 0x1 = 0xA0978823
Follow the reference and checking it for a pattern.
Syntax:
# ARM LDR or the first MOV of a C166 pointer pair (4-byte instruction)
LDR{ subPattern }
# THUMB LDR (2-byte instruction; not supported for C166)
LDR[ subPattern ]Supported instructions:
# ARM/THUMB
LDR Rd, [PC, #offset]
# C166 large model: huge/code pointer
MOV Rn, #SOF(target)
MOV Rn+1, #SEG(target)
# C166 large model: far data pointer
MOV Rn, #POF(target)
MOV Rn+1, #PAG(target)
The two C166 MOV instructions must be adjacent and Rn must be even. LDR{...} occupies the first four-byte MOV; the following SEG/PAG load is inspected to reconstruct the linear address. When the raw operands are valid both as a far data pointer and as a huge/code pointer, the nested pattern is checked at both addresses.
ARM:
LDR{ 436f70797269676874204d47432032303034 } 1e ff 2f e1C166:
# M55 at 0x207680:
# E6 FC 8C 75 MOV R12, #0x758C = SOF(0x20758C)
# E6 FD 20 00 MOV R13, #0x20 = SEG(0x20758C)
# Target at 0x20758C:
# E6 00 07 00 MOV DPP0, #7
LDR{ E6000700 } E6FD2000Steps for ARM:
- Pattern
?? ?? ?? ?? 1e ff 2f e1found at 0xA00A0B1C - Emulating LDR at 0xA00A0B1C (+0)
A00A0B1C: 00 00 9F E5 LDR R0, [PC, #+0x0] ; 0xA00A0B24 ; Emulation: PC + 0x0 = 0xA00A0B24
- Decoding pointer at 0xA00A0B24
A00A0B24: 1D 34 0A A0 ; 0xA00A341D
- Checking pattern
436f70797269676874204d47432032303034at 0xA00A341DA00A341D: ds "Copyright MGC 2004 - Nucleus PLUS - Integrator RVCT v. 1.15"
- Result is:
0xA00A0B1C
If the found address points to a THUMB PUSH { ... } instruction, +1 will be added to the result.
Example:
F0B5061C0C1C151C85B068461122??49??????????E0207869466A460009085C307021780134
Steps:
- Pattern
F0B5061C0C1C151C85B068461122??49??????????E0207869466A460009085C307021780134found at 0xA058BB98A058BB98 F0 B5 PUSH {R4,R5,R6,R7,LR} ; <-- see this A058BB9A 06 1C ADD R6,R0,#0x0 A058BB9C 0C 1C ADD R4,R1,#0x0 A058BB9E 15 1C ADD R5,R2,#0x0 A058BBA0 85 B0 SUB SP,#0x14 A058BBA2 68 46 MOV R0,SP A058BBA4 11 22 MOV R2,#0x11
- Result is:
0xA058BB98 | 1 = 0xA058BB99
Usually used in patterns.ini for entries whose address is already known.
# Address is 0xA8000000
< A8000000 >