Help wanted reverse-engineering PIC16 assembly code - Page 1
Forum section: electronics / general

author: MindBender
author_rank: Regular Contributor
timestamp: February 25, 2025, 08:51:39 am
content: Hi guys,I need some help reverse-engineering PIC16F1939 firmware. Even though I have over 2 decades of professional experience in embedded software development, my focus is mainly on U-Boot, Linux Kernel and applications and board support. PIC assembly is a whole different beast: I've been grinding my teeth on it for a few evenings, but I don't get much further.Here's the situation:I had the firmware extracted by 3rd party. They probably fuzzed the CPU. When I flash this firmware in an erased board using a PICkit, the board doesn't workentirely, but the functionality I'm looking for stilldoes. So I think integrity of the extracted firmware isgood enough.I also own a MicroChip RealICE, and I have attached it to the board. However, debugging a binary without sources gets a bit weird in MPLab. I don't even know if I'm doing it right.Going through the assembly is more tedious, but what I'm struggling most with, are the mnemonics for bank-switched registers: They seem to be largely incorrect, possibly because the active bank is unknown.My goal is to figure out what happens with data retrieved from an I2C device. How exactly it is processed.What I need is somebody who's a crack at PIC16 assembly, with experience in reverse engineering and familiar with the MPLab environment. Somebody who is capable and willing to help me hands-on, via remote desktop, for a price.PM me if you're interested in helping me solve this puzzle.

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 25, 2025, 09:46:51 am
content: Try decompiling it in Ghidra

author: macboy
author_rank: Super Contributor
timestamp: February 25, 2025, 04:59:13 pm
content: I'll offer some help. I wrote my own disassembler which identifies all the functions and subroutines (targets of a call or goto) and tracks the pagesel register through the code flow, so it knows where the next call or goto is targeted. It also tracks the banksel register bits and decodes all SFP register access by name. It is a huge game changer for reverse engineering and I've used it more than once successfully. Of course it can't give logical names to subroutines or variables, but it does replace the hex values with ASCII names that you can search/replace-all as you figure stuff out.One caveat is that it needs to be able to track code flow from the reset vector (usually address 0x0004 IIRC) otherwise it can't identify subroutines.PM me the hex file and exact model of if the PIC and I'll run it through.

author: jpanhalt
author_rank: Super Contributor
timestamp: February 25, 2025, 06:34:50 pm
content: For 16F1xxx, mnemonics for instructions are only 47 and generally make sense. The main exception raised by some people is "sublw," which translates to subtract WREG from a literal. A feature of that series is the ability to operate directly on the w (working) register. The name WREG (depending on how you set case sensitivity) rather than "w" must be used. (Actually, I have never tired using just w so I can't be sure of that.) Another thing to watch out for is the carry bit (STATUS,0). Microchip calls it the carry/borrow bit. Its behaviour makes sense, but some people get confused by its behavior during subtraction. Its behavior also varies a little between 16Fxxx and 16F1xxx chips, so I always keep the 2-page instruction cheat sheet handy.I second the need for a good disassembler.Finally, bank shifting for the disassembler I use is crummy. There is usually a warning to confirm the bank. Does the code go past a single page significantly? By that I mean are there calls and returns from another page (not to be confused with RAM banks)? For most of my stuff, I put the ASCII character table on another page to avoid using code space and access it using indirect addressing (FSR0 and FSR1). Those can reach any page and also allow you to access user RAM as a single, "linear RAM" space.If there's a section that is particularly confusing, post it here, and we can try to help.EDIT: Very poor reader here and just noticed this,"willing to help me hands-on, via remote desktop, for a price." Anything I offer is open source and priceless (i.e., free).

author: jpanhalt
author_rank: Super Contributor
timestamp: February 25, 2025, 06:37:31 pm
content: @macboyI do not have your disassembler. The one I use is quite old. Are you willing to share a link?John

author: MindBender
author_rank: Regular Contributor
timestamp: February 26, 2025, 01:04:38 pm
content: Quote from: DavidAlfa on February 25, 2025, 09:46:51 amIt has all the hallmarks of hand-coded assembly; I've tried Ghidra, but it gets confused easily.

author: MindBender
author_rank: Regular Contributor
timestamp: February 26, 2025, 01:11:21 pm
content: I'm using gpdasm. And it's a good one, because it produces the same result as MPLab X does. But it's adisassembler; All it does is disassemble machine language into assembly.Finding functions and keeping track of which bank is selected, is a whole different story.

author: MindBender
author_rank: Regular Contributor
timestamp: February 26, 2025, 01:22:06 pm
content: I think the code I have is complete, starting from the reset vector. I've been going through the code manually, but that is a tedious job, and I'm stumbling onto all kinds of oddities, such as a function preceded by what looks like a jump table, utilized by that function, jumping back into the function. But also jumping to addresses outside that function in no used by that particular function, but that indicates its probably a resource shared with other functions.Is your fancy disassembler with function-finding feature and bank selection tracker open source? And does it work for the 16F1939?

author: jpanhalt
author_rank: Super Contributor
timestamp: February 26, 2025, 03:22:22 pm
content: Re: Keeping track of the BankIf you can run it in a simulator, special function register BSR will tell you which bank it is in. I still use MPASM in MPLab 8.92, so getting there is perhaps different. For software debug, you have almost unlimited breakpoints. For hardware simulation (ICD3 & MPLab 8.92), your chip allows 3 breakpoints. Your more current software and hardware may allow even more.How many instructions are there total?

author: MindBender
author_rank: Regular Contributor
timestamp: February 26, 2025, 04:28:02 pm
content: Quote from: jpanhalt on February 26, 2025, 03:22:22 pmUnfortunately, it crashes in the simulator: It probably expect some pins to be at some level.I do own a PICkit, a Real ICE and a PM3.QuoteA little over 10,000. I'm going through the functions manually now, but there seem to be some errata in the datasheet: It contradicts itself in how many PCLATH bits are transferred to the PC register upon goto and call instructions. The operation part says "PCLATH<6:3> -> PC<14:11>", the description says "The upper bits of PC are loaded from PCLATH<4:3>." The first makes sense, for 4000-word program memory, but the latter is what everybody on the internet says it is. I'm surprised the datasheet of a chip this mature still contains sloppy errors like this.

author: macboy
author_rank: Super Contributor
timestamp: February 28, 2025, 03:01:48 pm
content: Sorry about posting then ghosting. I was busy with work.I have never open-sourced nor released my disassembler. Unfortunately, it only supports the baseline (12 bit instruction) and midrange (14 bit instr) PICs, but not the newer and better enhanced midrange, which the PIC16F1939 is. It could be modified to support that architecture, but I don't have any projects needing that right now so I don't have much incentive. As for specific device support, it uses the .inc file (e.g. P16F628A.inc) from MPLAB IDE to obtain the memory layout, SFR register names and bit names, etc., so it can support essentially any device in a supported architecture. That information is used during the disassembly process to name the registers and bits that are operands of instructions. I am not against open-sourcing the code, but I wrote it 10 years ago and I can hardly remember the state it's in, other than it is messy and full of FIXME comments.

author: jpanhalt
author_rank: Super Contributor
timestamp: February 28, 2025, 03:19:56 pm
content: Thanks for the response. I am pretty much stuck on the enhanced mid-range. They are pretty similar, but I do use the enhanced instructions for indirect addressing and linear RAM.John

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 28, 2025, 03:38:44 pm
content: Can't be tthat messy if at least has comments!

author: abeyer
author_rank: Frequent Contributor
timestamp: February 28, 2025, 08:34:30 pm
content: Quote from: DavidAlfa on February 28, 2025, 03:38:44 pmCode:// When I wrote this, only God and I understood what I was doing// Now, God only knows

author: jpanhalt
author_rank: Super Contributor
timestamp: February 28, 2025, 08:46:35 pm
content: Hey, at 82, that struck too close to home.

author: MindBender
author_rank: Regular Contributor
timestamp: March 03, 2025, 03:03:18 pm
content: Quote from: macboy on February 28, 2025, 03:01:48 pmNo worries!QuoteWell, I've been at it for a few evenings and a weekend, and I think I got what I need. Lessons learned:1. Even though Ghidra supports the PIC16F19139, it does not properly recognize subroutines.2. MPLab has a built-in disassembler, but the mnemonics it assigns to operants are not determined by tracing bank selection, and are misleading.3. Gpdasm was a better option for me, because it doesn't resolve operants to presumed mnemonics: I prefer an opaque number over an erroneous name any time.4. A disassembler does not attempt to find subroutines. That's a tedious manual job.Thank for the help, guys!