← All talks

Writing a Modern Assembler

BSides Knoxville · 202649:52227 viewsPublished 2025-07Watch on YouTube ↗
Speakers
Tags
About this talk
Travis Goodspeed presents the design and implementation of a modern assembler that ports easily to new architectures, solving a key bottleneck in embedded systems exploitation and microcontroller reverse engineering. The talk covers instruction definition, addressing modes, self-testing strategies, and practical applications to ROM extraction and firmware analysis.
Show original YouTube description
Modern disassemblers are awesome, but assemblers today are little better than they were in the nineties. I'm fixing that by writing one that easily ports to new architectures, saving me a ton of work in embedded systems exploitation.
Show transcript [en]

Okay. Um, today we're going to be talking about assembly language. Assembly language is a [ __ ] ton of fun because it allows you to reverse engineer stuff and it also allows you to write code that is hard to reverse engineer or code that runs on very weird architectures. Okay, if you want to write code that works on a microcontroller, but that microcontroller is foreign or particularly high volume or just not one of the standard ones, you need an assembler to do it. And about a year ago, I found myself needing a much better assembler than I had. This is the tale of how I wrote that. Little bit of my background. Um, I am an editor of this fine hacker

magazine, the International Journal of Proof of Concept, or Get the [ __ ] Out. Uh, we have plenty of these out at the registration booth. I've also got a stack here. Take one, take one for your friends. The only thing that could go wrong with them is if they wind up in a dumpster or unloved and unread. Um, my most recent major project was mask ROM tool, which is a CAD program that allows you to start with a photograph of a microchip, specifically the readonly memory portion of the microchip. And from that photograph, you draw lines to mark the rows and the columns. And wherever those lines cross, that's a bit. And after marking all of

the bits of that chip, you can then export a ROM image. And with that ROM image, you can then emulate the program or reverse engineer the program in order to understand how it functions. Um, this is open source. You can grab it from GitHub. I've got a nice little tutorial with it where you can rip the program out of the Nintendo Game Boy, which is the one that has the Nintendo logo coming in from the top of the screen. And uh, that's also the copy protection mechanism in the Game Boy. If the logo shown is not the Nintendo logo, then it will not allow the code in the cartridge to run. But if it is the

Nintendo logo and Nintendo did not license it, then they'll sue the living [ __ ] out of you for trademark infringement, pretending to be a licensed game when you're not. Um, I also have a a book out uh recently, Microcontroller Exploits from No Star Press. The focus of this book is that you have a microcontroller and you want a copy of the software in it, but the protection mechanisms are used. The fuses are blown or the settings are configured so that you're not allowed to read anything and you want to do it anyways. This book explains pretty much every way in open literature to do so. The first half with fully worked examples and the second

half just the punchlines. Um, this is a photograph of how the masc tool works. These, uh, if you see the little circles where there's either a dot or not a dot, those are your bits. The dots are ones. The absence of a dot is a zero. The dot itself is actually a via that connects from one layer of the microchip to another. And if you have the connection, then you read a one at that address. And if you do not have a connection, you get a zero. With my tool, I mark lines across all of the rows and columns and then having marked them, the software extracts the bits. Here we have a red for every one and a blue for every zero.

And the stage after this is to figure out how to convert from the physical order to the logical order. Um, when you handw write a program in hexadeimal, you're putting all the bits together of a given word, but in hardware, they're usually spread apart. All of your most significant bits will be on the far left of the chip. All of your least significant bits will be on the far right. And that's what I needed a better assembler for because I needed a tool that would tell me for a given firmware image, is this a a real program in this machine language or is this gobbledygook because I got the ordering wrong? And if I had a tool that could do that, then I

could automate figuring out the ordering of the bits within a program and I could make the reverse engineering a lot less labor intensive. If you talk to anyone who does a lot of reverse engineering, it's a [ __ ] ton of work. And the only way to make it enjoyable is to automate everything that can be automated so that you're doing the bare minimum manually. And the part that you do should only be the part that requires you to be clever and should only be the part that a machine cannot take care of for you. So I started researching assemblers and um disassemblers have fared pretty well over the past 30 years. Uh show of hands who here uses either

pro or gedra or um binary ninja. Great. These tools are awesome. Uh you have a nice flashy guey. They support a ton of different architectures. You have lifters and you have decompilers. So unlike the bad old days where we'd have to take um like a listing as a text file and then add our comments on the side and find and replace symbol names, now you do it all through the guey and it can automatically translate it to C for you in many cases. Sometimes it gets confused, sometimes you have to redirect it, but these have gotten really good. Now on the assembler end making a binary out of assembly code basically nothing has happened outside

of nothing has happened for weird architectures in 30 years and nothing has happened outside of um machine generated code in 30 years. So if you work on an architecture like ARM or RISV, they're are pretty good assemblers for that, but they're only good at taking the output of a compiler and producing a binary from that. They are not assemblers that you as an individual should be expected to enjoy programming in. Um, the other problem with these things is that the most advanced ones are for x86 and this is because x86 is the hardest assembly language to work with. Um, so basically we have had no features and no advancement except for the support of a couple new instructions on a couple of

architectures. I wanted to do something better. Um, I'm not targeting x86. I'm explicitly leaving that out for now. Um, particularly because it's harder than any other architecture, CISK or risk. My Assembler is intended to be obscenely obscenely easy to port. If you have a paper book from the 1980s that describes an architecture, you should be able to sit down in a coffee house and in a couple of hours you should have a plug-in for my assembler to add full support for that language. I I also from the very beginning designed the assembler and the disassembler to be symmetric. So when you're adding support for a language, at no point are you ever writing assembler code that does not match your

disassembler code. They're always held in sync. When you define a field, you're defining a field both for parsing assembly language and for masking off machine language. And everything that is used in that parsing and in that interpretation is away from your language definition. We have a shared parser for all languages. Um, now in my mask tool, I need this assembler in um in a library. I need a C++ library for it. But if I'm working on a hobby project, let's say I'm writing a Game Boy game. Well, for that I want to use it from the command line in a make file. And when I'm learning a new language, when I'm learning um let's say I want to

know the difference between a Game Boy and Z80 because they're related but they're not they're not identical. I have a ripple mode so that I can type in little bits of hexodimal and it will tell me what that disassembles to. Or I can type in little bits of m of assembly code and it will tell me what hexodimal that uh builds toward. Um, I also have a lot of support for things that are important as you're learning a language. I have cheat sheets for every language. So, if you're working in one that you're not familiar with, it will give you a list of all of the general instruction formats that you might use. or it will

take a program and it will give you a listing of that program with comments of what each verb does in the right hand column so that you are never lost as to what a pneummonic means. If you forget whether ADC means add with carry or add without carry, you can look to find out. Uh and that's not a madeup example. There are some architectures where it does mean with carry and some where it means without. Um, I'm also tying this back into my mask tool. So, so that the all the languages that I support within the assembler are automatically supported for ROM reverse engineering and you're welcome to embed this in your own projects uh if you happen to work with similar

architectures. For a demo, we're going to start off with the Texas Instruments 85. Um, for any of you who've been coming to Bides for a while, uh, Brennan Wilson used to come here a lot in the pre-COVID days. He did brilliant lectures, a lot of which were about graphing calculator hacking. Um, he did fundamental work in the TI83 plus and 84 plus era. The TI85 came a little bit earlier. It has a Z80 CPU and there is a bug in it. The bug allows you to get native code execution. Um, the way that the bug works is, uh, if you look at the top of this calculator, you see how it says F1, F2, F3 F4 F5.

In the guey, you can configure a custom menu. And that custom menu has menu entries that are copied from the ROM table of the menu tree. and a a menu entry on this calculator is made of an ASKI string and a pointer to a function handler. So the way that you get code execution in this is that you back up the calculator to your computer through the serial port on the bottom. You then patch it. You patch the dump to replace one of the function pointers with a pointer to an address in RAM. And the only address in RAM because this this calculator's operating system is basic. Everything is garbage collected. The whole heap moves around. The only

location that stays in place is the LCD screen buffer that's currently showing the graph there. The way that these teenagers got code execution was they had a bit map picture saved and that bitmap picture contained their shell code. They would load it onto the screen and then they would call the custom menu entry in order to jump into the picture as native code. Pretty badass for teenagers, right? Like we're who in the room was doing something that cool at 16? So, this is an example of a program that I wrote for the TI85. Um, you'll notice that this looks a lot like NASA code and it's because I'm I'm trying to keep my format as similar as

possible. I have a label on the left. Anything follow anything before a colon, that's a label as the first word in a line. Optionally, you can have a new line after a label, but you're not required to. The first word after the optional label is either a directive or an instruction. Lang means I want to set the language. Call means I want to call I want to insert the call instruction. Add I want to insert the add instruction. The ones with dots are directives and the ones without dots are instruction verbs. Um, you'll notice that sometimes I'm referring to the label or to a global symbol name rather than a value. So when I have um ldc cursor row, elsewhere

there's a file that says that cursor row equals whatever constant value. That's an address in RAM that it's reading from in order to figure out the cursor position within the the text driver on the screen. But when I wanted to write this, I was on a bus. I was visiting um Dartmouth College in New Hampshire and I was on the bus and first I needed to write the driver for it. After I have the driver, I can start with this source code. I can jump to the binary output. I can also do weirder things like um this is a listing output. This is what you make when you have a program and you need to look at the bytes of that

program while also knowing what those bytes means. You want to render both the bytes and the instructions and the addresses all in one. You might also want to output a file that's compatible with other assemblers. So, NASOM is an x86 assembler, but a lot of us use it when we're writing a binary file by hand because we can have data bytes and then we can have comments about those bytes and the um you know the file format has been the same for ages. If I give you this listing, you can know that the CD bite at the beginning is the call op code and you can change it. And when you change it, you don't need my tooling to

do it because I've exported it to standard tooling. Um, sometimes you're writing shell code. I was working on shell code for a television smart card. It was a Dish Network card from 1998 called the ROM 3 card. Um, if any of you stole satellite TV a quarter century ago from Dish Network, it was probably because of this card. This is the output of my shell code for that card as a Golang module. And at the top I have all of my symbol names so that I can refer to them from my Golang code even as my labels change their positions. And at the bottom I have the bytes of my program complete with comments so that

anyone altering this can understand what each of those bytes mean.

But as I said earlier, I before I can use them in any of these languages, the machine language here happens to be 6805, I need to write a module for it. And a founding principle of this project was that I should be able to write a module to add support for a new language in a couple of hours at a coffee house without screwing it up. So when you look at the CPU user manual, this is the Z80 from the TI85 graphing calculator. Um, this is the noop instruction. Um, no is the easiest instruction on any architecture. it just does nothing. On Z80, it happens to be zero. Um, if you look at the bottom of this

um, excerpt from the manual, you see that it's defining every bite of the op code. None of them are undefined and all of them are zero. This is my definition within my Z80 module for the assembler. Um, this is in a class that is inherited from like a generic language class and I'm inserting a new instruction type. The mnnem is short for pneummonic which is the pretentious way to say verb. It's also a pretentious way to say op code depending upon whether you are assembling or disassembling. So I define the pneummonic to be kn meaning that that will be the verb of this instruction. The one means that this is one bite long and I follow that

with back slashx0 because this one bite is zeros. Why do I have f after it? Well f is the mask. This particular instruction is one bite long and every bit is defined. So I define every bit in the mask. If only the first four bits were defined, my mask would be f0 instead. Then after inserting that, I I call the help method to add the definition of this to say, oh, this does nothing. This is a no operation. And then I also call the example method in order to make an example. And the purpose of the example is that I can very easily do self- testing. If the example does not assemble by matching this pneummonic and then

disassemble by matching this pneummonic, then I have a mistake in my definitions. Maybe I have another instruction that I accidentally also defined as 0 0 because I copied and pasted this one. Maybe I have an example with too wide of a mask and because the mask is too wide, I'm accidentally mashing to lots of things. The example allows me to test for all of that automatically and to catch the mistakes as I make them. So insert creates the function. Uh pneummonic defines it. Knop is the verb. I'm doing it one bite long. Zero is my op code. FF is my mask. every bit of the op code matters. I got a help string for my cheat sheet

and I've got an example for myself testing. The other thing this gives me is autocomplete. So in my ripple mode, I can type the letter N and hit tab and it will then tell me that there are two instructions that begin with N that I've defined so far. One of which is negate and the other is no op. And which one would I like? And it can also predict parameters for me. And this is incredibly useful when you are new to an architecture. You're capable of learning it, but you don't know it yet. So, as a poor speaker, you need to catch up. This ripple mode in like a little terminal on the side allows you to do that.

Um, the same thing works for disassembly. Um, here I type in 00 and my ripple mode happily tells me that that is a noop and it happily auto comments that with my cheat sheet string telling me that this is no operation and the ripple mode does not care like it it automatically figures out which mode. So if I type in an instruction, it gives me the assembly. If I try type in uh bytes, it gives me the disassembly. I can also generate a cheat sheet from all of these. So this is part of the Z80 cheat sheet and it tells me that I have the decrement instruction in a couple of different forms. It tells me that I have um the

nuop instruction that we just defined, the halt instruction, uh setting interrupt modes. I can GP through this in order to find instructions that I might have forgotten on this particular architecture like what is addition, what is does it have multiplication look all of those up and then self- testing um self testing will run through every instruction that I have everything that I've defined in this language and it compares my definition to the definition of all other instructions and we have rules around this. So um the knop instruction for it to pass the self test, it needs to match the example in both directions. So I take the example and I assemble it and I make sure that

it matches that same instruction. If it matches another, I have a problem. If it matches more than one, I have a problem unless I've specified a winner. Um if any of the bits of the instruction have not been defined then I have a problem because I have an ambiguity. Um some architectures have a real ambiguity and you can specify that. You can say hey with this bit mask I have a don't care field but if you don't do that then it will warn you hey you you made a mistake somewhere you screwed up. I also have a fuzzing self test where I just read like a megabyte out of /dev/random and make sure that I can

disassemble it without crashing. This ensures that when you wrap this as a library, it's never going to crash in the middle of interpreting something. Now, the knop instruction is really easy. I mean, we're just substituting a word there. In order to be a useful assembler, I need to support a lot of other addressing types. And in most assembly languages, uh, a sort of prefix letter before your number tells it what you're doing with that number. So an immediate means that we want that number itself. So an immediate before 0x dead means I want de a d as a number. If that's my source, I'm taking that literal value and putting it in my destination. An absolute is different. Um, these are

sometimes called indirect. An absolute means that I I don't want the number itself. I want the thing in memory at that address. So if 0xbe is my source, it's going to look in memory at the 16 bit address bef and it's going to grab the bite that is there or maybe a word if it's a 32-bit architecture. I also have addresses. And addresses are weird because they're used in things like jumping and function calls. Here I'm not talking about the literal value and I'm also not talking about the thing in memory at that value but I'm talking about an address as an address. Maybe I have a function handler there. Um you use this for function calls and

occasionally for some IO operations although the IO operations are usually absolute. Um now a relative looks exactly the same in assembly language as uh an address but the they're different in machine code. In machine code an address is that that number directly included in the instruction. Um so in a little Indian machine I would have like a dde you know it being backward for relative I take the value of the program counter plus some offset which is usually two but can vary by the architecture and I take the difference between that and the address that I'm going to and that allows it to be shorter. That's when yeah very often used in conditional jump instructions where you're jumping a

little bit forward or a little bit backward from your position. Uh I have named registers. So like on x86 you have um a ax e um in each architecture you define each of those by name and that becomes a keyword that is no longer allowed for symbols. Um you also have um like uh absolute markers on registers sometimes. So like a at HL means I'm referring to the value in memory at the address that is held within the HL register. You also sometimes have indexing which is implied by grouping symbols. So if I have within grouping symbols HL comma ix that means that I want to take the value in memory at HL plus ix and read that value back.

Um this is a more complicated instruction. This is a load. Um this load is for an 8bit instruction set. Sorry an 8 bit um instruction word. So uh I have 01 at the beginning and I follow that by three bits that define my destination register and then I sorry three bits that define my source register and then three bits that define my destination register and I've got a list for what those three bits decode to. in in my code I define the what I call Z80 regge 8 and I do this with the C pre-processor and here I'm just defining the instruction like I did for NOP and then I give an example and the example is a

little more complicated because I have parameters and then I throw in those parameters from left to right along with the um along with the matching bits. You know, my I have two 8 bit registers. The first one of them is masked by 38. The second one is masked by 07. Um, and then I can begin using them in the repo loop in order to know that they work. And I can also begin using them within my source files in order to write larger programs. If you have an architecture with a whole bunch of instructions, but your shell code needs very few of them, you can begin by defining just the instructions that you'll use and not worry about the

rest of the language until you need to write more complicated programs or uh disassemble machine code. Um we sometimes have uh conflicts which have to be prioritized. So the previous definition is shown on the upper half of the screen. The lower definition is for one particular edge case which is when the um the source register is referring to at HL. It looks like it's a part of that earlier list but it's not. And so I define it both ways. And the prioritize call here says that the second one is the better one. That if I ever have a conflict between the two, I want the second one and not the first one. If I were to remove that and then run my

test cases, I would it would recognize the duplicate, it would trigger an error. It would tell me that I did not have closure on my language definition. And that has saved me so many times when I'm a little bit tired and I'm copy and pasting one instruction to make another instruction and maybe I forget to rewrite the op code bite. this will catch that because either the definition is internally self-consistent or it is not. Um, if you want one that is not the um easy 80 instruction set, there's a definition of it online straight from the manufacturer. It is riddled with mistakes. The definition that it gives you is wrong and you have to compare the

examples to the definitions in order to figure out where the right value is. I'm still not entirely sure that I got it right. Okay, so we have a definition. We've defined the 685 instruction set for example, and then we get uh a new card in with a new ROM and a rare instruction set. This is an F card from Direct TV. Um, it was made by NDS and these cards were hacked to hell and back. The way that this card works is that it has uh EPROM, it has ROM, and it has RAM. The ROM could be extracted and emulated. And there was a program you could get for your DOS computer and you would run

that emulator and then it would pretend to be this card on a serial port using whatever EPROM image you gave it. And the emulation was perfect. It was exactly the same as the real hardware. This confused uh a lot of the satellite TV receivers. It allowed you to decrypt your TV signal and far more importantly, it allowed you to share your key with another TV subscriber elsewhere on the continent or with thousands of them. Um, now the instruction set here is a little different from 685. The bits are scrambled. And so I asked a buddy of mine, uh, Chris Gerinsky, who knows a lot about TV cards. Uh, I just said, "Hey man, um, I hear that the op

codes are scrambled. Can you tell me more about it?" He gives this exact quote from an old mailing list post. He says, "Only the op codes are scrambled, not the operands. The bit order from most significant bit to least significant bit is 76453012. If you chart that out, a few of them are flipped, but um some of them are not. And also the most significant bit is x word with a one. So we're flipping the most significant bit in it. Um and then he pointed out that I could see this on the die photos, and I really enjoyed taking die photos. So I quickly took one of these cards out of my collection, boiled it in acid, and found what he was

talking about. Um, so in order to support the f card, uh, I just use C++ class inheritance and I I made the scramble function. The scramble function simply does what he told me to do on Twitter. Swapping around lines, inverting the most significant one. These are the lines that you can see on the die of the chip. Um, the instruction decoder is at the bottom of this view and the data bus is consists of the long horizontal lines in the middle of this view. We're going to zoom in on the connection between them. And uh, depth of field is a bit confusing here. So what you see in yellow that is above what you see in

brown. The the yellow ones are metal lines. The brown ones are polysilicon lines. These are the bits of significance in terms of columns that run to the instruction decoder. Uh, I'm going to flip back and forth between this and the previous slide just a little bit. And you can watch how you can kind of see how it's those eight lines that connect at the bottom and how they're obscured by the uh horizontal nettle lines. The metal lines consist of rows from memory. Um, here the most significant one is at the bottom. The least significant one is at the top. This is an 8-bit microcontroller. So it makes sense that we have 8 bits of uh an op

code. Um and on this architecture, your op code is only ever at most one bite long, which is why we never have to uh worry about a second bite or preserve any of this. And the other thing to note are these little circles where they cross. You probably didn't see those circles the first time I showed the image. These circles are called VAS, and they're vertical connections between the metal layer and the polysilicon layer beneath it. On the regular chip, these are all in a nice diagonal line pattern. This chip is the same except that they're in a different order. And this is what produces the bit swapping that that we saw within the definition.

So now we know the new order. The only thing we need is some code to reverse engineer. So again, Chris helps me out by Twitter. Um, he gives me a shell code for this smart card as a quick little post. The instructions are to send a five byte header 4842 A70. It's then going to reply with the number 42. And then you send it these 92 bytes and it will send you back a 372 byt ROM. This EPROM is what you need to plug into the emulator to have the emulator receive TV as that particular card's identity. um the TV companies back then, they would buy pirated cards and they would read the EPROM out of them and then

having a copy of the EPROM, they would know which subscriber to cut off. And if one subscriber is account is being used by 10,000 cards, when you cut off that one account, 10,000 accounts go blank and 10,000 people are mad at their local uh drug dealer or satellite TV dealer or whoever sold them the card. So I took this pattern and I ran it into my disassembler that was built up from the the same definition that I'd earlier uh received. So I took the the standard 685 module I inherited from it and then in my inherited class I swapped around the the bits of the op code and I get this for decoding it. This is not quite the entire string.

It's cut off with um part of the 2D sequence that begins on the third line and continues on the fourth. Um this is awesome because this is real code. Um can anyone with good eyesight tell me how I how I know that it's real code? Like what about this tells you that it's not just a random string being interpreted as if it were machine code in this architecture? Um I can't see you from here so you have to blurt it out. Yes. Okay. So he says parameters make sense for their types. Um you're talking about like the 57 and the 88s.

Right. Exactly. And um there's something at the tail end of it that also stands out to me. The no ops at the end also matter. So these no ops in a row are called a knopsled. You use this near the entry point of your code so that if your entry point is off by a few bytes, your code will still execute. And you also use it when your code needs to be fixed length and you're removing instructions but you need to keep the length constant. you'll insert noops in order to fill that blank region with something that it will just run through. Um, and then uh, as this gentleman pointed out, the JSR instructions, they're both jumping toward the same

region of memory. 1 7F 1 F31, those are pretty close to each other. If this were random data, those numbers would be wildly different. And these addresses both happen to be within the same peripheral type. Um this is jumping into mask ROM and that mask ROM kind of ends at 0x200 0. Um so this is fitting into that. And then um again as the fellow from the audience pointed out the um these addresses like the LDA at 888 we see that um 88 is used twice here which makes sense for a global variable. You know you load from the global variable then you increment the value that you loaded and finally you write the value

back. And that's how you increment a global counter as you're looping around it. That tells us that this is real code. And we now are able to read the exploit that was written for this antique microcontroller given the comments from two tweets and reusing almost all of the code from a generic description of a related chip. Um the code can then be matched up to other pieces of the dump. So having a a disassembled version of that shell code that we can read, we can change it to give us a copy of something else. That code was designed to export the EP ROM memory, which is valuable to you if you want to steal TV. I don't have a

time machine and that satellite is no longer transmitting. So, I'm not going to steal TV with it and I'm not concerned with the EPROM contents, but I was very concerned with getting a copy of the ROM out or getting a copy of the code from EPROM. And both of those I can dump out and then disassemble and reassemble with this tool. As I'm playing around with emulating the exploit, I can change things. I can play around with how the TV company might have defended itself against that exploit. And I can do all of that with a language module that I wrote in an hour, an hour and a half in a coffee house without having to start from scratch and

without having to worry that I made a terrible mistake that would ruin the results of my program. Um, since getting this working, I linked it back into my ROM reverse engineering tool, which lets me do some really cool things. I can attempt to decode the chip in every way that I've ever read about, you know, left to right, right to left, top to bottom, bottom to top. I can spin it. I can flip it. And I can take all of those potential decodings and I can then grade them to see how likely they are to be real assembly language in the known language architecture of that chip. Uh if I photograph the fard, you know, I

already have a a greater class that works for 6805. I can extend it to work for fard and then immediately I can know how well I decoded everything. On the left, you can see another chip that I'm working on. And this chip is particularly um hard to photograph. It uses something called an implant ROM. And a consequence of an implant ROM is that the bits are invisible. And you have to use a chemical called a dash etch after delayering them. And the dash etch will change the ones brown slightly faster than it changes zeros brown under a under a very bright halogen lamp. When I was processing this chip, I [ __ ] up. I had a bubble on the chip

and the bubble prevented the colors from being stained properly or maybe it stained them a little bit faster. So that bubble in the top left of the left view is zoomed in on the right view. I don't know what any of the bits are in that position, but I do know the rest of the bits. And I know that the rest of the bits are pretty accurate. I've measured it twice. I've error corrected them. So here I'm I'm running my disassembler on this chip. And as I run my disassembler on the chip, I'm having it give me not the bites of the instructions. Instead, I'm having it give me the bits of the damage mask that I defined when I was drawing

the bits in my ROM reverse engineering tool. And this way, whenever I see a one in the bit listing, I know that I should not trust that instruction. And by looking at that individual one and flipping the bit, I can kind of figure out what else the instruction might mean. If a source register could be a two or it could be um a four, this would help me figure that out. If the op code is wrong, then maybe I don't know whether I'm doing addition or subtraction, but I still know what the parameters are. Um, this is going to be incredibly useful in decoding those sorts of ambiguous programs where I have damage and I don't

know what the underlying value is, but I do know where the damage is and this helps me guide myself around it. Um, also because I wrote this in the QT framework and I enjoy uh stupid little toys, you know, As I was learning some of the languages in order to write this project, I had to study them with pen and paper and I I did a lot of that with pen and paper without a laptop in order to avoid getting distracted. So I later um ported the library to run on Android and iOS. Um if any of you want to invest, I think we should do like Dolingo for assembly languages and uh get that psychopathic owl uh

chasing after people. Uh, if anyone from Duelingo is here, please get in touch with me. And, um, I'd really appreciate you restoring the streak that I lost last night. So, uh, that's all I have for you today. I hope you enjoyed it and, uh, I hope that you read some good scripture with proof of concept or get the [ __ ] out. If you'd like a copy, feel free to grab one from me here on the stage or from the front registration desk. Uh, do we have any questions?

Yes. If we could turn on the uh Do you have it? Can you hear me? Yep. There it is. Where'd you go? Okay.

So, uh pastor, yes. I have a question about the uh FCard assembly language. Yep. Y So you said it's based off of 6805, right? Mhm. And that the um each op code is a single bite. Yes. So I'm just curious, how many different op codes I mean obviously you can fit, you know, up to a certain amount of op codes in that language, but how many did you find in the F card? Um so it is the So not only is it based on the Motorola 685, it's big Indian, right? Uh believe that one's Big On Indian, but I'd have to check my source code to know for sure. Yes, it is Big On Indian. Um this chip

actually is a 6805. They just changed the wiring going into the instruction decoder in order to create this swapping. And that's why the swapping is not more complicated. if they encrypted the order of memory or if they um you know changed the op code set by swapping rows of the um instruction decoder ROM. They could have made the scrambling a lot more complicated. Well, because realistically the scrambling is just a couple of logic gates right? Uh there aren't even logic gates. It's just changing via locations. That's amazing. Right. So, every transistor is the same. And not only that, but the masks that make the transistors and that make the metal wiring are the same. The only thing

different is the mask that makes the VAS between the metal and the polysilicon. Yeah, that that's ins I'm so glad I was born in the 2000s. Oh, but if you tried doing this on something made in the last 5 years, it is so much smaller and harder to photograph and your software is so bloated. Like um it back when this chip was made, nobody thought of using a GPU with an inference model to make software that runs from a battery. Um everything had to be lean, everything had to be small. And that is such a gift to reverse engineers that the target is small and easier to work with. Yes. How long have you been working on this

project? I was tinkering around with ideas for this and had a few false starts, maybe two years ago. It was only in the past year after I finished the microcontroller exploits book that I finally wrote the version that is now published. Um, my early mistakes were that I I tried to avoid writing a real parser and that shot myself in the foot. Um, so these now have like a proper parser in the lex or yak style which became a lot easier to manage once it was working. Um, it it's frustrating because you're having to build toward a behavior that doesn't work in the middle and you feel like you'll never get to the end, but then you do and it's not

nearly as bad as you thought in the beginning. Yes. So, on the vein of weird Z80s, how feasible would be implementing some support for something like Kabuki? I'm I'm sorry, the thing kicked on at just worst moment for what? Kabuki. It's a Capcom encrypted. I believe it's does address shifts, but it also varies its behavior depending on whether it is decoding an instruction or data. Okay. Um, is a definition available of its behavior sufficient to emulate it? To my understanding, yes. Okay, then I could probably do it or I could probably show you how to do it. Um, dig up the definition and catch me in the courtyard after the lecture. Okay, cool. All right, any last questions?

All right, thank you. Round of applause, please. Um, and please do pick up one of these. I've got a lovely article in here. catching the USSR counterfeiting a Japanese calculator something like 33 years ago.