 Hi. Welcome. We're going to do a bit of a lazy solve of Medium Rare's HHV Challenge 6. This was a challenge that Rare put together for HackFest 2020. You can see the challenge description and binary are linked there from the DCHHV org site. HackFest 2020 happened back in November and Rare added this challenge at that time to his sequence of other HHV challenges. If you want to play along with the video, I suggest you go and download this binary hit pause and come back when it's downloaded. You can see the challenge description here. But all that to say that what we have is an AT Mega, the binary from it. We know that AT Mega is connected to a seven segment display through some kind of map. We don't know. If we can figure out what that mapping is, presumably we can read out the flag as it scrolls by on the seven segment display. That's the approach we're going to take. Most of the presentation is going to happen here in the terminal. You'll find that when I type things, there's a little display of my keyboard typing happening, so you can learn what the shortcut keys are that I'm going to use when I'm using things like Radar. Maybe install the tools, follow along. Let's get started. So let's start by using Radar 2 to disassemble that AT Mega firmware. We can open up the firmware using the iHacks plugin, which lets Radar parse directly X files without any conversions or intermediate files we have to produce. And we're going to open that up with automatic analysis, so that's dash capital A, which is the same as typing AAA inside Radar. And then let's do a disassembly of the current function once we have it open. So when we try to do that disassembly, there's a lot of these code Xref comments that scroll the material off the bottom. So right off the bat, we're going to want to page around a few, if you have a terminal multiplexer like I do, you can page using that. If you don't, Radar has a built-in pager, which you can access with tilde dot dot, like this. And we can see what the entry point of this binary is doing. So why don't we ask Radar about some things that it knows about this hex file. E is for accessing all of the Radar to configuration variables. E question question tells us what it's talking about. So let's have a look at that. E prints a whole bunch of stuff. We could obviously page through that using the built-in pager. There's a lot of different configuration variables. Some of the names are pretty descriptive. If we want to know more information about that, we can use E question question. And you can see the descriptions that are built in by the developers then become accessible to us. Because this is a pager, we can also search through it, see various CPU settings. So there's obviously config variables that are set up by Radar that are about CPU. We can grep through the output using tilde by itself, which is the internal grep operator. You can grep through all those configuration variables for CPU. And we can see that magically Radar knew that the binary we handed it in that hex file format comes from an AVR architecture, an AT Mega 8. What else does it know about this binary? Well, I is for information about the container. When you're in doubt about a command in Radar, you can always throw a question mark on the end of the command, and it'll print the help. So page, in this case, I gets info from the open file as it tells us. We're going to do I all by itself, which tells us some things. Format is AVR. There we go. It knew, somehow it knew. We don't need to know how. We just need to solve the challenge. So what about trying to browse around and figure stuff out? Radar has got some stuff for that. We can use V for visual mode, which by default opens up a rather nice hex dump. You can actually use P to switch the printing mode. If you hit P once, then you're brought immediately to a disassembly. You can see here some of the disassembly in the entry point, which looks just like our PDF that we printed out. When you're doing disassembly in visual mode, Radar lets you look at flow graphs by adding space. So here we are. You can see the nice extended basic blocks linking to each other. When you are looking at these graphs, you can actually navigate through calls to other functions. So we can see here there is a call to function for E, and there's this little markup of the square OA. When you see those, you can actually type OA or whatever, and it navigates the function. There's another call, also marked by OA, which we can follow with OA. And we can go back using U to undo that seek. That's a nice way to navigate through the binary. In other display modes, like when you're not in the graph display, you might see markup of numerals, and you can actually navigate that way with the square bracket and then the number. But U also works in that case. This is a pretty good way to navigate around the binary. And why do we want to navigate? Because we want to understand that binary. Now maybe you don't know AVR assembly. That's okay. I don't really know it that well either. We can actually switch the disassembly mode and print suitor disassembly, which is sometimes easier to read. So you can see I dropped into command mode while we are in visual mode by hitting colon, and then we turn on the ASM true and hit enter again. And now instead of looking at AVR disassembly, we're looking at a pseudocode. So R1 is getting XORed back in R1. R28 takes the value of 5F, go tos instead of calls. So in a lot of cases, for a lot of people, this might be easier to follow. We followed the 4E, we followed the 3, 4, and so on. So feel free to look at this in pseudo assembly mode if you like. All right. So where are we? We're in function X34. And what is it doing? Okay. So 34, which is remember being called by 4E, so we can go back. 4E immediately calls X34. And X34 is rather short. It is setting up values into the port A pins pull-ups location. So reads the value from the pins pull-ups, applies a mask of E1, writes it out. And then it reads the data direction for port A, or as a mask, writes it back out. Same for B. So this routine is pretty obviously doing some setup stuff of the input and output directions of the GPIO pins on the AVR. In particular, the data direction registers are getting turned on, getting the bits 1E turned on for data direction A. And the bits of 7 turned on for data direction B. Like we said, almost certainly output input configuration. And we know that 1E and 07 were the two byte values being put into those configuration registers. So what if we wanted to look at a value in binary? Radar has a whole bunch of printing functions that are prefixed by question mark. So this is different than putting question mark at the end of a command. This is the first character question mark. You can print hexadecimal with question mark V. We can print in binary with question mark B. So if those were in fact configuration input output pins for GPIOs, then there would be 1, 2, 3, 4, 5, 6, 7 of them for a 7-segment display. Let's see. 1, 2, 3, 4, 5, 6, 7. So this is a pretty reasonable sanity check that makes us think that we probably did find the configuration registers. But most importantly, what values are being put out onto those pins? So let's go back to 4E. The command you see there is pretty good to remember. When you want to go back, the equivalent of that U command that we did in visual mode, you do S minus, kind of like CD minus on the command line. And remember PDF is for printing a disassembly of the current function, which will probably run off the bottom of your terminal. So remember the internal pager, it is your friend. So at the beginning here, a whole bunch of these registers are getting loaded up with different constants, R20 through R26. And then we have IO operations, writing the value of a register to 1B and 18, which occurs again. And then occurs again. So there's these repeated writes to 1B and 1AX from various different registers. You can see it's R20 in one case, R23 in the next, R21 in the next. And these different registers were loaded up with different constants. And these locations 1B and 1A are actually recognized by Ray Dare as the port A and port B output pin configurations. So presumably when we write these values out, these are going to have an impact on the GPIO output pin state. And in between the outs are loops. You can see here that R29 is getting loaded up with a particular value. So is R28 getting us traction. Ah, yes, that's right. So AVR does this thing where they actually combine two registers together for extended words. And then it's doing a subtraction. So it's doing a countdown of a loop here and only is going to exit that loop when the counter runs out. So you have setting values on output on the GPIOs followed by busy weight delay loops. This function here is almost certainly the function that's driving those, go back to our diagram. That's the function that's driving the outputs of these seven pins, which are somehow mapped to the segments. What we want is to figure out what is the sequence of those outputs, and then eventually we can remap the outputs to some kind of correct mapping onto the seven segments and then be able to read the text that's scrolling by on the seven segments. So we can see here there's lots of different outs in this function that go on and on. So it's writing us some kind of a long string, probably a flag. Remember that in RAID Air we have this internal grep operator with tilde and we can actually grep for things like out. So in this case, we just grep for all of the disassembly lines that match the string out in this function. We can even use the internal grep to just show particular columns. So when you add that square operator, you can filter just a column out. We can see that the sequence of registers that are being written to the locations is something we have access to with six. And if we were to do five to six, we could even see what location gets which register. So we're very close to having that output sequence of values. RAID Air includes the ability to emulate different architectures. It uses an internal intermediate language called ESIL to do this. So if we were to look at this function, the first thing it does is calls function 34, which as you recall sets up a whole bunch of different states that we don't need to follow that setup because we're not actually driving the pins. We're just interested in the output and calling that function requires to set up the stack and have a return address, but we don't want the hassle. So let's just start from the next address after the call. The emulation module in RAID Air can be initialized with AEI. And then we can set the emulations program counter instruction pointer to the current seek with AEIP. We know that there are out operations and these are IO operations. We're going to be changing values in IO space. When you are expecting changes in the mappings, you want to tell RAID Air to allow that by setting the IO cache to true. And then once we've done this, we can actually single step the code in visual mode. So we've already done P previously to get into this state, which is the disassembly. We're going to P one more to get a nice registered output. And then as we hit S for single step, you can actually see that there's an effect. So we just single stepped over R20 and now has the value of E, single stepping as possible. Now, of course, once we're on the out instruction, there is going to be a small problem. So AVR and ESIL does have a problem without instructions. When we single step over that out, we're going to find that the values in memory, even though we've set IO cache to true, are not going to be tracked. So you can see here a session where we're doing a step until there's an IO instruction, changing the seek, the current location of RAID Air to the program counter, the instruction pointer of ESIL. And here we're doing a disassembly. So this is a different kind of disassembly function, PID instead of PDF, roughly the same thing, but it removes all the comments. And then we can analyze some of the instructions. So let's do a few of those steps. Okay, so back to 50, AI, AIP. We already set the cache. Let's step until there's an IO operation and seek to the location. If we were to just print one instruction, we can see that we're on out of R20 to hex 1B. AO is Analyzing Opcodes. It kind of lets you print what RAID Air knows about the Reticular Opcode. So let's ask it what it knows about this one instruction quite a lot, its address, how it's actually disassembled the pseudo form, the ESIL form, and the family. So in its ESIL representation, RAID Air actually knows the source of the operation, the fact that it's going to IO, what address of IO it's going to. This is obviously 1B hex, and that it's getting stored. So RAID Air is able to parse that information out. And of course the current value of a register can be queried using AR. So we get the value, it's E. I think you remember at the setup of the function. That's the first one that got assigned. So there's that value. We can actually look at memory. So P8 is a way of printing 8-bit expair strings. We can print four of them at the IO location. That's the current value. And if we are to do one step, that's a ESIL step. If it worked, we would actually see the change, which we don't. So there is a rub here that ESIL is unable to emulate those out instructions. So we can't just watch memory and look at the changes by just watching 1B and 1A hex in memory. But we're not defeated. We just need to do it the harder way. Remember that that AO of that single instruction did include a lot of information, including the ESIL representation, which obviously has the source and the destination in it. And we can also query the value of the registers. So if we could just script this up, we could extract it. And there is such a facility for this. It's called R2Pype. R2Pype has a whole bunch of different bind, language bindings for controlling Radar2 programmatically. And we're going to use Python. So that Python script looks kind of like something that's driving Radar from Python. You're embedding the Radar commands into our command calls and using the results in some case to store values. And then we can use the constructs we're familiar with in the language. So you'll recognize this setup of ESIL here. Remember dash A is the same as AA for analysis. Here we're turning off a few of the extra syntax sugar that would show up in our prints down here. And in this case, we're using a JSON variant of command. This is pretty powerful because if we use a Radar command that uses JSON output inside the command j form, what we get out is a structure, you know, a dict in Python. And then we're able to query that structure without trying to parse strings, which we will later do because some of the fields do need some of that string parsing. We did mention the question mark V for printing hexadecimals, and the double dollar sign is the current address. So you can see how that would work right here, the hexadecimal of the current seek. PDJ is print disassembly in JSON form. And then what we've done here is grab that ESIL representation. In fact, PDJ1 is the same as the AO form that we just talked about. They're both available. That's what we're doing there. We grab ESIL, we split it up, and then we can grab the source register, the target address, and then through another command by querying the registers, we can query the value of the source register to have on hand. And then as we're looping through, we're single stepping until we hit an IO family instruction and changing radars seek to the instruction pointer of the emulator. When we find an IO instruction that's pointing at hex18, we're going to print out a CSV of the pin state. Otherwise, we're going to just augment our pin state with the things that are pointing at 1b. We do this because the pattern in the disassembly was to print, was to write to hex1b and then write hex18. So we'll, in this case, we'll pull e, the first value assigned, into the lower half of our open pin state. And then r25 takes that value of 1. We'll put that into the upper half of our pin state and print. And then the script will continue to run down the code until it hits this out instruction, and it'll store the value of r23 at that point in the lower half, store the value of r19 in the upper half, and then print the row. And the CSV printing that I've kind of elided with these comments is nothing special. It's just printing a header here and then printing the bit values of the output pin state registers on each occurrence. So we're going to leave rate error and we're going to run that script instead. And you can see it in action. What you're looking at here is, as we discussed, e and 1 and so on. And in fact, we can now move to using Cygrock to inspect this pin state, treating it just like a logic analyzer capture. Okay, so it works just like we screen grabbed down below. So what you're looking at here is an ASCII art rendering of the pin states that we just put together. You're seeing here the very first column. Those three together make e and that makes one. So the columns here of the CSV are actually printed as rows. If you're more familiar with normal logic analyzers or pulse view, you might see it looking like this. So this is the same thing, the same CSV, but in the pulse view, graphical version of the logic analyzer. We're going to stay in the terminal though. And that is because we can do some more powerful stuff in the terminal and build on top of that. So if you remember the bit mask that was set for the output pins, a one E07 that actually has bits, three, four, et cetera set. So we can specify that as a pin mask to Seigrock. So we're just looking at the channels we're interested in. And then we can actually plot that output as renderings of a seven segment display. So to finish this challenge, we actually did patch Lib Seigrock to extend the decoding. That's possible on seven segments, segment, segment displays. The alphabet was kind of limited before and rare used all kinds of cute lettering choices to encode the flag. So we need to actually expand all of the alphabet that's available. I'm having a really hard time moving this window onto the monitor that you're looking at. Let me try one more time. There we go. So we had to add things like quotation marks, equal signs, different casings of letters, dashes, and obviously underscores because this is a CTF. So flags are probably going to have underscores. And that commit was accepted as a PR and it's in there. And for fun, we did include a unit test and this challenge is actually now committed to Seigrock test as the mystery message. It's present there in GitHub now under Seigrock. So we can actually make use of that by asking for the seven segment decoder of Seigrock. And we're going to use the show unknown equals yes. Remember, we don't know what the mapping is. So unless rare set up this mapping to match exactly the defaults that we found in that pin state mask, which he didn't because this is a CTF challenge. We're not going to get the flag right away. So some of the letters are going to be nonsensical. So in the cases where it just isn't a letter that's available in the expanded alphabet we just showed you, it'll show that hash. So in this case, with the default mapping, it doesn't look very flag like and we're going to have to do some searching. So in recap where we're at, we were reversed air quotes, the binary in that AT mega. We know which pins are relevant. We even know what their values are and how they change over time. But we don't know how those pins map to the segments of the seven segment display. We don't know how to make that part happen. And then because we have that decoder, if we knew this map, the decoder could show us a printable representation of that pretty quickly. So we need to figure out that mapping. And of course, brute forcing it is possible. So, you know, that previous command that you saw, you can pretty easily put that into a shell script. I'll copy out the version that I developed previously. You know, it's not a big surprise. We're just showing the pins that we're limited to. We're using that ASCII output. We're using the same decoder and including show unknown yes, whatever argument we give it needs to be the pin map. And then we use some Ock, you know, stuff to squish it all together into one row output. So let's try this default map and no joy. As you saw before, the default mapping doesn't work. It's pretty straightforward to, you know, use Python or any other language to put together a permutation of all the possible pin maps. I'll pull them in here. And I'm pretty sure there's a better way to do this, but this is the way I did it. And this will create a file that will contain all possible maps for the seven segment display that we're interested in with the form of a equals number through G equals numbers. So now we have that file of all the maps. And of course, you know, lazy brute force, we can just pipe that read each line into a variable. And run that attempt map for each line that we read and hope for the best. And it's running pretty decent. You know, we're getting some good output. Definitely a fun scroll to watch. But if we started getting bored of watching the scroll and just started grabbing for flag, we would find that we would be waiting quite a while. It's not very fast. It takes about an hour using this approach, at least on my setup. Using GNU parallel is pretty attractive here because the problem is trivially paralyzable. There's no data interdependence here. The next problem that you run into using GNU parallel, at least I did, is because of my VM, the whole thing was IO bound. Oh, actually, that turned out way faster this time. Don't look at that. So let's use GNU parallel to get this done. So we're going to go to that RAM disk and spin it up. Now, once again, using that attempt map, once again, grabbing for flag. And in this case, it should happen in less than a minute. Of course, in live demos. Nothing ever goes as planned. So there it is. Flag shift into reverse. Of course, that translation process of going from ASCII to 7th segment to ASCII is lossy. And Rare also forgot the closing bracket. So shoe horning that a bit in the format. The flag is actually flag shift into reverse with threes for two of the three ease in reverse. And that is the lazy solve. So many thanks to iHack, Quebec Sec, HackFest, and Medium Rare for putting this fun challenge together. I had fun banging away on this during HackFest. And I enjoy all of Rare's challenges. I encourage you to try them out. They're still up there. I know Rare is also going to be doing a talk here at the HHV this year for walkthroughs of his challenges. I know there's going to be more new ones this year. Both he and I will be hanging on a discord and available for questions should you have any. Thanks very much. Have a good con.