I'm a BSEE who works in firmware, that said I can give a few pointers but nothing on any specific ECM.
I know that many ECM's today are going multi processor, which makes things a lot harder, however every system will have its code stored in a flash for the most part (so it can be updated). Usually this is an EEPROM, but for cost its also sometimes some kind of NAND or NOR flash.
You can usually pull the code out of the flash device on the PCB using an off the shelf programmer or sometimes through the OBDII port if you can figure out the commands needed. If the engineers did their job well the code is compressed and/or encrypted on the flash part making it one step harder to get to the code.
Next, you're right, it will be in "machine code" but translating that into usable assembly code requires you to know what exact processor they are using. Sometimes that's easy because its a single chip on the PCB and you can figure it out from the silk screen on the chip or board, but many times companies use custom SOC's which then have the processor housed in the chip with many other things and so you have no idea what processor they are using (or even how many there are) without doing a lot more work.
Once you know the processor you can get what is called a decompiler for that code, assuming its not compressed or encrypted from the flash, you can use the decompiler for that processor on the machine code to convert it into assembly which then makes things much easier.
If you have what is often called the DAMOS, or ELF TYPES, or .AXF for the build you can then use that to map out where all the functions start in the assembly and/or where all the global variables are during execution time and that makes things WAY WAY WAY easier.
In the BMW world (where I've done some dabbling for example) the N54 engine uses a triple processor setup and all the function names and variables are in German, making it a PITA to figure things out sometimes, and we only have a very early version of the DAMOS that isnt complete.
If you can get all of those things together, there are sometimes decompilers that will even let you make source code (c or c++) out of the assembly, but that can get really confusing and ugly because you dont know what compiler they used to compile their code which then makes reversing assembly into a normal language rather hard. Best bet is to just learn the assembly for that processor and then look at the assembly code.
Once you have that, you can start screwing around inserting your own assembly code. If they didnt put any kind of protection against executing errant code your life is easy, but if they did you have to sometimes hide function pointers into things like IRQ's if they are not hard coded and the like to trick the processor into running your code, one great way is to sneak a function pointer into the return address on the stack of a known good function the code already calls (assuming its location on the stack is constant at some known time). If they used function pointers in their code a lot your job becomes a lot easier.
Table modification on the other hand is fairly simple, and even if they are CRC checking the tables you can re-calculate the CRC value and shove it in after you modify the table so the code is none the wiser that you changed it usually. Table modification usually lets you change things like AFR, injector duty cycle per RPM per MAF flow, etc, etc (all depends on how the ECM works). So often times these tables are in 2 or three dimensions which you have to look up how multidimensional arrays are laid out in a flat memory space (its not too complicated) to recognize them in assembly.
Hope that answered some questions.
Last edited by Shushikiary; 08-22-2017 at 08:30 AM.