Tuesday, August 16, 2016

Learning with DVRF - Step 9 - Hello MIPS World!

Step 9 - Hello MIPS World!

In the previous post, we covered a lot of introductory concepts on MIPS and a basic MIPS binary. Let’s continue to explore MIPS and add a little bit more substance to a basic binary. This post will analyze a “Hello World” binary and how it differs from the “blank” binary.

Let’s check it out!
  1. Log into the VM
  2. Open up a Terminal window
  3. Change your working directory to the DVRF squashfs folder. In Terminal, type in:


    cd /home/andy/buildroot/buildroot-2016.05/output/host/usr/bin
  4. Let’s look at the MIPS disassembly for “hello”. In Terminal, type in:

    ./mipsel-linux-objdump -d /home/andy/Downloads/DVRF-master/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/hello

  5. As before, a lot of text will fly by on the screen. Scroll up to “<main>”.

  6. In main, we can see that we have more opcodes/instructions than in the "blank" binary. However, we can see similarities that can help us out.
  7. We see similar instructions in the binary such as an "addiu" and "sw" like we saw in "blank". We can infer that these top four instructions are part of the prologue of the "main" function.

    4007b0: 27bdffe0 addiu sp,sp,-32
    4007b4: afbf001c sw ra,28(sp)
    4007b8: afbe0018 sw s8,24(sp)
    4007bc: 03a0f021 move s8,sp

  8. In line 4007b0 with the "addiu" instruction, we see that we are now carving out 32 bytes instead of the previous 8. This should give us enough space for a string.

    27bdffe0 addiu sp,sp,-32
  9. In line 4007b4, we see an additional line that we did not see in the "blank" binary. Here we see that we are moving an address from the stack at an offset of 28 bytes from the stack pointer. The memory address for the return address (ra) is being stored/preserved (sw - store word) into an offset of 28 bytes from the stack pointer register. The saved memory address is where the program will resume execution after the "main" function is done with the epilogue. We will cover this more in the discussion of the epilogue, but for now, just know that we are preserving this memory address of the caller function of the "$ra" register in the stack.

    afbf001c sw ra,28(sp)

  10. In line 4007b8, we see a familiar instruction where we save the frame pointer into the stack pointer at an offset of 24 bytes from the stack pointer.

    afbe0018 sw s8,24(sp)

  11. In line 4007bc we can see the program moving the stack pointer into the frame pointer.

    03a0f021 move s8,sp

  12. Now that we have the prologue out of the way, we can proceed with the meat of the program. Below is the code for "hello.c" as a reminder:

    printf("Hello world");

    return 0;

    The program's only purpose is to print out "Hello world" to the screen.
  13. If we scan down the list of instructions, we can see that we see familiar instructions toward the end of "main" like in the "blank" program (e.g. move v0, zero; move sp,s8). For now we can ignore that section for now and just focus on the instructions in memory address 4007c0 thru 4007e4. We can assume these instructions belong to printing out "Hello world". All of those instructions for a single line of code!

    4007c0: 3c1c0042 lui gp,0x42
    4007c4: 279c8920 addiu gp,gp,-30432
    4007c8: afbc0010 sw gp,16(sp)
    4007cc: 3c020040 lui v0,0x40
    4007d0: 244408e0 addiu a0,v0,2272
    4007d4: 8f828048 lw v0,-32696(gp)
    4007d8: 0040c821 move t9,v0
    4007dc: 0320f809 jalr t9
    4007e0: 00000000 nop
    4007e4: 8fdc0010 lw gp,16(s8)

  14. In line 4007c0 we can see a load upper immediate instruction where we load 0x42 (0x00420000 in hex) into the global pointer register. In this case, we are only going to get the first four numbers (upper immediate - 0042). The gp register at this point received 0x42 as it was shortened by the compiler and was only represented with two numbers (42).

    4007c0: 3c1c0042 lui gp,0x42

  15. Don’t worry, this was all weird to me as well. I’ll add in another two lines of code and then talk about some related things. It’s basically using some addition or subtraction to get information about the GOT. Not Game of Thrones (while awesome), but the Global Offset Table. I’ll give some links after we do the next two lines of instruction.
  16. In line 4007c4, we have an “add immediate unsigned” instruction. In this line, we are going to subtract decimal 30432 from what we have in the gp register.

    4007c4: 279c8920 addiu gp,gp,-30432

  17. Why do we need to do this? Because math. No but seriously. This particular line of instruction will subtract 30432 from the gp register and then store the result in the gp register. In our case, we know that we have:

    gp = 0x00420000 in hex

    If we convert 30432 from decimal into hex, we get 76e0.

    Why do we care about decimal to hex? Well we want to do math in the same base number. It’s easier to show the “why” of the reason in hex in a little bit.
  18. Now we have two numbers in the same base and can do math easier. Well, we can use online calculators to make it easier! In our case, we want to subtract x0076e0 from x420000. In this online calculator, we can do this subtraction:

  19. As much fun as that is, 418920 seems highly insignificant at this point (or is it)
  20. In line 4007c8, we can see we are storing the gp register onto the stack at an offset of 16 bytes from the stack pointer. In this case, we’re storing 418920 onto the stack. What a fun number!

    4007c8: afbc0010 sw gp,16(sp)

  21. We know that in our code we have the string “Hello world” that is printed out to the screen. However, in the block of instructions above or below, we don’t see anything saying “Hello world”. Actually it’s less readable stuff and more Q*bert speak. The text that is output to the screen is not modifiable from the user and basically read-only. Since we know that this string is going to be read-only/constant and an expected value, our program has put this into a special section called “.rodata”. As you may have guessed, this is the read-only section. However, we haven’t seen this section in our current disassembly from objdump.
  22. If you scroll up/down in the disassembly, we can see that the “-d” option with objdump only provides us with the .init, .text, .MIPS.stubs, and .fini sections. Let’s open up a new Terminal, browse to the same directory as earlier. In this new Terminal, type in:

    ./mipsel-linux-objdump -s /home/andy/Downloads/DVRF-master/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/hello

  23. This version of objdump uses the “-s” switch:

  24. We can see now that we have a lot of different information. Well, it’s the same information, but presented differently. We still see the .init and .text sections as before, but we don’t see the more human friendly representation of the instructions. Scroll to the “.text” section:

  25. There were a lot of different functions in the .text section in our previous view of the data. But, we see the first column looks like it has memory addresses and the other four columns have various numbers. Let’s scroll down to the 4007c0 line.

  26. Remember how we stored the gp register contents onto the stack?

  27. It may not be readily apparent, but the instructions in the previous step (26) are in the screenshot in step 25. The endianness is not displayed the same. For example, we see “afbc0010” in step 26. The endian byte order in step 25 is big endian and shows the numbers differently as “1000bcaf”.

  28. The same information is there! Just looks different. That was fun with the colors and arrows. While that was a fun tangent, we want to look for the .rodata section. It’ll be down past the .text section.

    Hello world! It looks like the string containing “Hello world” is located at 4008e0. We don’t need anything further from this Terminal with objdump -s. We can go back to the objdump with -d.
  29. In the Terminal, with disassembly from objdump -d, we can resume back to the next line of instruction. In line 4007cc, we can see that we are again doing a load upper immediate but with 40h (0x40 or 40 hexadecimal) into the “v0” register. It would appear that the “v0” register is a scratch pad type variable for math. As before, we’re going to just care about 40h and place that into the “v0” register.

    4007cc: 3c020040 lui v0,0x40

  30. In line 4007d0, we can see that we’re now going to do some more math (with v0 and 2272) and place the result from math stuff into the “a0” register.

    4007d0: 244408e0 addiu a0,v0,2272

  31. Unlike in the previous “set”, we are not going to do subtraction, but instead, do addition! It would appear that 2272 is a decimal number (there was no “x” in front of the number like in the previous step) and we need to convert that to hexadecimal. The number 2272 in decimal is 8e0 in hex. While we can use an online calculator, we can see in the above screenshot the last 3 numbers in the second column are “8e0”. Since those numbers are in hexadecimal, we can go with that. You can also use a calculator to double check as well! The math looks like:

    0x00400000 + 0x000008e0 which is 0x004008e0

    We place 0x4008e0 into the “a0” register which is an argument register. We care about this address in the “a0” register because 0x4008e0 is the start of “Hello world”!!!!

  32. I promised some links. Let’s review the Global Offset Table. You should take a quick break and read these links:
    - GOT link #1
    - GOT link #2
    - GOT link #3
  33. Now that we have some more insight into the GOT, we can infer that we didn’t create printf ourselves. That would be a lot of unnecessary work and an exercise left to the reader to create. Hah, jk. Instead, we’re going to rely on the use of printf in an external library to do the work for us.
  34. Let’s open up another Terminal and browse to the same directory we’re in now. In Terminal, type in:


    cd buildroot/buildroot-2016.05/output/host/usr/bin

  35. We are going to use another program called “readelf” to identify different properties of the “hello” binary. In Terminal, type in:

    ./mipsel-linux-readelf -a /home/andy/Downloads/DVRF-master/Firmware/_DVRF_v03.bin.extracted/squashfs-root/pwnable/Intro/hello

    The “-a” switch for readelf gives us everything and the kitchen sink:

  36. Now we have a ton of info about this binary that we didn’t see before with objdump. Most of the information is not relevant to us right now. The only things we really care about at this point is the stuff at the bottom. For now, I’m just going to leave the screenshot of the information from the bottom and we’ll go back to the MIPS instructions.

  37. We last saw our adventurer… I mean before we got on a side tangent, we saw where we mapped the “Hello world” string from the .rodata section into our program execution flow. In many cases (all?), we will load up the arguments in the stack/registers/wherever to a function and then call the function. In our case, we loaded up the string “Hello world” in the “a0” register. The program will send along the contents of “a0” to the called function (in our case printf) with that argument. If we had more arguments, we could see different behavior depending on the number of available registers to the number of arguments to a function. B1ack0wl (who created the DVRF project) has a pretty good writeup of what happens when you send arguments to a function when you run out of registers. We have five instructions left from our original assessment of the “actual program” below.

    4007d4: 8f828048 lw v0,-32696(gp)
    4007d8: 0040c821 move t9,v0
    4007dc: 0320f809 jalr t9
    4007e0: 00000000 nop
    4007e4: 8fdc0010 lw gp,16(s8)

  38. You may have noticed from the screenshot in step 37 that it has some similarities in the screenshot in step 36. For now, let’s step through the first instruction.
  39. In line 4007d4, we see we have a “lw” instruction which stands for “load word” (ref: here and here). In this line, we will move whatever is -32696 (in decimal) from the global pointer and move that to the “v0” register. Different sources imply that the “v0” register is used as a return value register after a function is called. Up to this point, I have seen “v0” mostly used in manipulations used to call functions (e.g. printf, atoi). In the previously mentioned b1ack0wl article, the “v0” register is used in conditional flow statements as well:

    But, back to our code:

    4007d4: 8f828048 lw v0,-32696(gp)

  40. There are a few different ways on how to interpret this code. We can infer that we have to call printf at some point in time, especially since we just loaded up the “Hello world” string. It does not appear that MIPS has a “call” instruction like x86 to make it obvious with symbol resolution that printf is being called. With that, we can peek in the screenshot from step 36 and see if we see anything familiar.

  41. In the very last night in the previous screenshot, we see not only the printf function name, but -32696(gp) like what we saw in our line of instruction:

    4007d4: 8f828048 lw v0,-32696(gp)

  42. We could infer from just that alone that we are loading 410968 (the memory address for printf) into the “v0” register. The -32696(gp) matches the screenshot from readelf of the printf function in the global entries and the same pattern of -32696(gp) as in the line of instruction!
  43. Let’s take the other way around as if we didn’t know the printf mapping to 410968 and getting all of this information as easily. Let’s do some math!
  44. First we need to convert -32696 from decimal and into a hex value to play nicely. The decimal value of -32696 is -7fb8 in hexadecimal based on this calculator.

  45. In line 4007d4 of instruction, we saw that we are subtracting a number from the global pointer. We know the value of global pointer (gp) in two ways. We know the gp value from step 20 that the value of gp is 418920 (we pushed it onto the stack). We also know the gp value from readelf!

  46. If we continue using math, we could do 418920 (gp) - 7fb8 (the decimal number we converted to hex) which equals 410968. 410968 is the address of printf! Awesome!

  47. That was pretty interesting. Let’s proceed with the rest of the instructions. In line 4007d8, we see we are moving the contents from the “v0” register into the “t9” register. From what I’ve seen, the t9 register is a scratchpad type register where we can put whatever into it.

    4007d8: 0040c821 move t9,v0

  48. In line 4007dc, we are doing a “jalr” operation with the “t9” register. The “jalr” opcode is a “jump and load register” instruction. We are going to jump to the value in register $t9 and the return address is stored in $r31/$ra. This is how the program knows where to return once the stuff in another function completes. In this case, we sent the string “Hello world” in $a0 as an argument for the function call printf. This jump operation took the string, went to the printf function, output the string to the screen, and will now return.

    4007dc: 0320f809 jalr t9

  49. Now that we have output the string to the screen, there’s no more code left from what we saw in the beginning of this post. In line 4007e0, we see there’s a “nop” operation. A “nop” operation is placed after a jump as a delay slot. Because MIPS. But, also because sometimes a second instruction can also be ran after a jump statement. Program execution flow could get messed up if we had two different operations performing at the same time. A “nop” helps to keep our program running as expected. Here are four references with additional information:
    - NOP link #1
    - NOP link #2
    - NOP link #3
    - NOP link #4

    4007e0: 00000000 nop

  50. If there’s nothing left to do, we will be left with the cleanup. We have the following instructions left in “main”.

    4007e4: 8fdc0010 lw gp,16(s8)
    4007e8: 00001021 move v0,zero
    4007ec: 03c0e821 move sp,s8
    4007f0: 8fbf001c lw ra,28(sp)
    4007f4: 8fbe0018 lw s8,24(sp)
    4007f8: 27bd0020 addiu sp,sp,32
    4007fc: 03e00008 jr ra
    400800: 00000000 nop

  51. To quickly surmise the instructions above, we’re basically resetting everything to how we started. The epilogue was covered in the previous post, but now we have a few additional instructions. We see that we are pushing the saved global pointer address back to the gp register, zeroing out the v0 register, replacing the stack pointer with the frame pointer, moving the saved return address of the calling function in the stack back to the $ra register, moving the frame pointer back to the saved address on the stack, reclaiming stack space, and finally, calling back to where the program was called from. This has been a fun post and now that we have a good handle on the basics of MIPS, we can start with the challenges! The first challenge we examine will be the stack overflow challenge in the pwnables/Intro folder.

No comments:

Post a Comment