This is the fifth in a series. You might want to read the previous post before reading this.
This post is based on the Hanoi level on microcorruption.com. Like last time, we’re trying to find an input to open a lock without knowing the correct password, using knowledge of assembly language.
As usual, I’m going to start by looking through the
4520: c243 1024 mov.b #0x0, &0x2410 4524: 3f40 7e44 mov #0x447e "Enter the password to continue.", r15 4528: b012 de45 call #0x45de <puts> 452c: 3f40 9e44 mov #0x449e "Remember: passwords are between 8 and 16 characters.", r15 4530: b012 de45 call #0x45de <puts>
The first line here sets the value at
0x2410 to 0 - but it’s hard to say why without reading more. The others write out some text. Reading on…
4534: 3e40 1c00 mov #0x1c, r14 4538: 3f40 0024 mov #0x2400, r15 453c: b012 ce45 call #0x45ce <getsn>
This should look familiar too. The parameters for
getsn are the max number of characters to get, and the place to store the result. Here, they are
0x1c = 28 characters and the location
This already looks like a good place to start. The message says that passwords can’t be longer than 16 characters but the code actually accepts 28. Where do the extra characters go? They’re going to start at
0x2410, a number which sounds familiar. It was set to 0 at the very start of the function.
Looking further down, that number appears again:
455a: f290 1a00 1024 cmp.b #0x1a, &0x2410 4560: 0720 jne #0x4570 <login+0x50> 4562: 3f40 f144 mov #0x44f1 "Access granted.", r15 4566: b012 de45 call #0x45de <puts> 456a: b012 4844 call #0x4448 <unlock_door> 456e: 3041 ret
First the code checks
0x2410 has the value
0x1a. If it isn’t equal, we jump to a later point in the function. If it is equal, we carry on: the text “Access granted” is printed, then the function to unlock the door is called.
We can use a buffer overflow here to put the value
0x2410. The code allows for 28 bytes to be input from
0x2400, so 16 bytes of nothing followed by the
1a byte should do the trick.
0102030405060708090a0b0c0d0e0f101a for my final input.
This was a straightforward example: the code very clearly expected 16 characters for input, but allowed room for many more. It’s actually very easy to leave room for buffer overflows in real-world code, but they’re normally harder to spot. The easiest way to find them is often to try ‘fuzzing’ with random long strings of input, to see if they make a program crash. If a program does crash, clearly something was unanticipated about the input you gave it, which could let you find a buffer overflow.