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.
First glance
As usual, I’m going to start by looking through the login
function.
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 0x2400
.
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 0x1a
in 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.
I used 0102030405060708090a0b0c0d0e0f101a
for my final input.
Endnotes
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.