In this new level, just like the previous one, we will exploit a buffer overflow vulnerability and create a working shellcode in order to solve the level, only this time our shellcode will be limited. Keep reading to learn more :)
When you enter the level, as always, there is a pop up manual. The last paragraph states the following:
This is Software Revision 03. We have received unconfirmed reports of issues with the previous series of locks. We have reimplemented much of the code according to our internal Secure Development Process
Turns out they “reimplemented much of the code” following their
main function you’ll notice it simply calls
<login> function, allocated at
Allrighty then, let’s see what
login actually does.
44f4 <login> 44f4: 3150 f0ff add #0xfff0, sp 44f8: 3f40 7044 mov #0x4470 "Enter the password to continue.", r15 44fc: b012 b045 call #0x45b0 <puts> 4500: 3f40 9044 mov #0x4490 "Remember: passwords are between 8 and 16 characters.", r15 4504: b012 b045 call #0x45b0 <puts> 4508: 3e40 3000 mov #0x30, r14 450c: 3f40 0024 mov #0x2400, r15 4510: b012 a045 call #0x45a0 <getsn> 4514: 3e40 0024 mov #0x2400, r14 4518: 0f41 mov sp, r15 451a: b012 dc45 call #0x45dc <strcpy> 451e: 3d40 6400 mov #0x64, r13 4522: 0e43 clr r14 4524: 3f40 0024 mov #0x2400, r15 4528: b012 f045 call #0x45f0 <memset> 452c: 0f41 mov sp, r15 452e: b012 4644 call #0x4446 <conditional_unlock_door> 4532: 0f93 tst r15 4534: 0324 jz #0x453c <login+0x48> 4536: 3f40 c544 mov #0x44c5 "Access granted.", r15 453a: 023c jmp #0x4540 <login+0x4c> 453c: 3f40 d544 mov #0x44d5 "That password is not correct.", r15 4540: b012 b045 call #0x45b0 <puts> 4544: 3150 1000 add #0x10, sp 4548: 3041 ret
At a first glance there are some functions that catch my eye:
- At address
r15) as parameters.
- At address
(Stack Pointer)value at that particular moment (via
r15) as parameters.
- At address
clr r14) and
r15) as parameters.
- Finally, at address
Based on this information, what we can deduce so far is:
- Our buffer is 48 (0x30) bytes long.
- Our buffer first gets stored at address
- It then gets copied to whatever
(Stack Pointer)value is at that particular point of execution.
- The original copy at
0x2400gets zeroed by
Let’s confirm that behavior and correct our assumptions. I suggest placing breakpoints right after each important function.
Before executing the program, I also suggest you to track
r15 is order to see it’s value into Live Memory Dump window. In order to track a register you must simply execute
track rN command, where N is the register number. In this particular case it’d be
When we’re asked to input data, as always, we’ll provide some easily recognizable bytes. In my case, I provided 50 bytes. You can copy your input from here:
Our first assumptions were right since there are only 48 bytes (you can count how many Es are there and you’ll count up to 8) at address
strcpy finishes, we can inspect
And we can, in fact, inspect the memory via Live Memory Dump window and see that our buffer got indeed copied at address
0x2400 gets zeroed and if you continue the execution you’ll get “That password is not correct” and the program will either finish expectedly or crash.
Interesting, what ought we do now? Well, let’s keep breakpointing the code and acquire more knowledge about what it does. Interestingly enough, last two instructions of
login function are:
4544: 3150 1000 add #0x10, sp 4548: 3041 ret
Let’s breakpoint on both of them and see what happens at execution time.
Once again, I recommend you to provide a full-length input. That is, 48 or more bytes.
You will notice
sp’s value right before adding
0x43ee familiar? Well, it indeed is.
0x43ee is where our input gets stored after
strcpy does it job. This means
sp will point to
ret instruction is about to get executed, right when
login is about to finish. And that means we can control return address of
login function because the buffer is 48 (0x30) bytes long starting at address
0x43ee. That’s nice!
As we’ve seen in previous levels, this allows us to jump wherever we want when
login function ends. But,
The first thing to have in mind is the function we’re exploiting, the vulnerable function. In this case, it is
strcpy defines our exploiting context and it has, of course, some limitations. Let’s read some official documentation about strcpy():
Copies the C string pointed by source into the array pointed by destination, including the terminating null character
(and stopping at that point)
Those very last words are very important to us because they basically tell us how to NOT write our shellcode. That is,
0x00 is a badchar in this context since
strcpy will stop copying from there on. A badchar, or badchar, is a character, or set of characters, that, given a vulnerable function, a context, renders the shellcode useless. You can read more about badchars all over the Internet. This one and this one are mere examples.
Now, keeping in mind badchars, let’s design our shellcode.
One solution that I implemented is very simple. We design such a payload whose length is lesser than 16 bytes (0x10) and then we jump to it. That is, we overwrite
login return address with
0x43ee address. Now,
As we’ve seen in previous levels, LockitAll User Manual tells us how to achieve insta-unlock. That is, pushing 0x7F into the stack and calling INT.
INT function is at address
454c <INT> 454c: 1e41 0200 mov 0x2(sp), r14 4550: 0212 push sr 4552: 0f4e mov r14, r15 4554: 8f10 swpb r15 4556: 024f mov r15, sr 4558: 32d0 0080 bis #0x8000, sr 455c: b012 1000 call #0x10 4560: 3241 pop sr 4562: 3041 ret
Well, what if we simply try the last level’s solution. That is, pushing
0x7f and calling
push #0x7f call #454c
Once again, we can assemble our code using Microcorruption Assembler. As you can see in the image below, I underlined the NULL byte that’ll make our shellcode useless.
The NULL byte is automatically inserted because MSP-430 works with 2 bytes addressing. In 2 bytes length, 0x7f is the same as 0x007f. It’s exactly the same number.
3012 are the constant opcodes for
push instruction. Now notice how: (Remember MSP-430 uses Little Endian byte ordering scheme)
- Both integers
#0x41and #0x0041became 4100.
#0x414243became 4342. (Only the 2 LSB are kept)
Now, in order to get rid of NULL bytes there are thousands of solutions and alternatives. What I suggest is very simple. That is, using basic arithmetic operations. Remember we are assembling instructions, we can do whatever we want as long as syntax is correct. Now, what if we take advantage of
Let’s say we place some value that has no NULL bytes in some register and then perform some arithmetic operation that has no NULL bytes on it so that the result is what we want. Since that result will be stored in a register,
mov #0x0180, r15 sub #0x0101, r15 push r15 call #0x454c
0x007f and that’s exactly what we want. (Remember 0x007f is the same as 0x7f) Of course there are many different ways of achieving the wanted value.
The assembled code is:
Now, remember there are 16 (0x10) bytes from the beginning of the buffer until return address and our assembled shellcode is only 14 bytes long. We will have to add
So, the solving input (hex encoded) will be:
You could as well use 2 bytes integer overflow properties. That is, adding two big numbers (2 bytes long) so that the result exceeds 2 bytes size but the last 16 bits (2 bytes) represent 0x7f. Let’s see the following example which will definitely help us understand this concept.
mov #0x56AA, r12 add #0xA9D5, r12 push r12 call #0x454c
This code actually works. Let’s assemble it, try it and explain why it does work.
The working input (hex encoded) is:
It works because the result is bigger than the
0 1 0 1 0 1 1 0 1 0 1 0 1 0 1 0 (0x56AA) + 1 0 1 0 1 0 0 1 1 1 0 1 0 1 0 1 (0xA9D4) _______________________________________________________ 1 | 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 (0x1007f)
The result is
0x1007f but since the length of integers is 2 bytes, only the last 16 bits count.
0x7f. Notice how despite the result, no operand contains null bytes. Thats how we avoid badchars using integer overflow
We’ve seen how
strcpy is vulnerable to Buffer Overflow attacks. Exploiting such vulnerability we can change the program’s execution flow in order to execute our shellcode. At the same time,
strcpy limits our shellcode since there are some characters that make it useless, the so called badchars. There are many ways of avoiding badchars, we’ve seen one of the most simple. That is, basic arithmetic operations. Once badchars are avoided
login function so the
ip jumps right to our shellcode.