














It is quite reasonable to put this lab along with bomb lab as they rely on basically the same skill, which is the ability to read and decipher and understand the control flow of the assembly code compiled from c source code.
Funny enough, these two labs are far easier than the first data lab in my opinion. It is such a pain in the ass to play with machine level data representation convention, considering all its corner cases and magic bit level tricks.
This is kinda like a warm-up phase for this lab, requiring an input to manipulate the program towards another function while not calling it from source code.
By looking at the assembly code of function getbuf:
1 | 00000000004017a8 <getbuf>: |
We can see that the function allocates 0x28 bytes on the stack as the buffer holding user input.
To understand how a buffer-overflow attack happens, we need a graph:

We are required to modify the return address stored at the top of getbuf‘s stack frame, which is at address 0x5561dca0.
The saved return address is 40 bytes after the beginning of the buffer. Therefore, the payload nees 40 characters(each takes up 1 byte) of padding followed by the 8-byte address of touch1.
Immediately can we use hex2raw to generate an ASCII representation of the hexadecimal address of the target function touch1, which is 0x4017c0. Following 40 characters taking up 40 bytes which fills up the prepared buffer, we can override the return address to where we want the next instruction pointer points to.
Keep in mind the on a litte-edian machine, data is stored reversed byte-wise:
1 | c0 17 40 00 00 00 00 00 /* 8-byte address reversed byte-wise */ |
Now we need to jump into another function with an parameter. Recall that the positions of the parameters of a function call:
For a standard function call, 6 of its parameters goes into
RDI, RSI, RDX, RCX, R8, R9in order.
That means we should put our desired parameter in %rdi before jumping, which requires injected instructions.
As the instruction pointer stored in %rip increments each time it completes an instuction, the injected instructions on stack should be arranged in positive order by address. As a result, the input should consist:
1 | <placeholder> + <instructions> + <override address> |
We want to execute the injected instructions when getbuf returns, so the 8 bytes at 0x5561dca0 should be the address of the first injected instruction. And after putting the desired variable into %rdi, we should override the value stored at the address pointed to by %rsp to function touch2.
By looking at the assembly, we can find that the desired cookie is located at 0x6044e4:
1 | 4017fc:43b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie> |
And its value:
1 | (gdb) x/wx 0x6044e4 |
So the instructions should be:
1 | mov $0x59b997fa,%rdi |
[!TIP]
May be we should usepushq? Well,movlworks well this time as the upper 32 bits are filled with 0 at the moment.
Each of the mov instruction takes 7 bytes, and 1 byte for ret. That leaves a 40 - 15 = 25 bytes space remaining in the buffer that needs to be filled up.
And the starting address of the injected instructions is 0x5561dca0 - 0xf = 0x5561dc91.
[!TIP]
May be we should place the injected instructions at the beginning of the buffer? That way we wouldn’t need to calculate the start address of the instructions.
This time we are required to implant a string into the memory in order to pass the strncmp in hexmatch.
Turns out the string required in this phase is cookie in hexadecimal without the prefix ‘0x’, 59b997fa, with a trailing \0.
The most tricky thing about this phase is that calling hexmatch and touch3 would cause the program to push some of the callee-saved registers’ values onto the stack, which may override our injected string as %rsp increases after each return from our injected instructions and move on to touch3.
Especially the large local array allocated by hexmatch: char cbuf[110], which completely annihilate the feasibility to put the string inside the getbuf buffer as it would be override completely at the moment.
We can observe the program’s behavior and record the values of %rsp to see where we can get a memory area untouched to save our injected string:
1 | 0x5561dc78 ... 0x5561dca0 : buffer / padding |
Then touch3 and hexmatch would push registers onto the stack and allocate local array, meaning that we cannot choose any address lower than 0x5561dcb0 to store our string.
Let’s place the string at 0x5561dcb0.
strncmp does not compare the integer cookie directly. hexmatch first formats the cookie as an eight-character hexadecimal string, and then compares that string pointed to by sval:1 | int hexmatch(unsigned val, char *sval) { |
We can clearly see that we are required to put a pointer to the injected string into %rdi before jumping to touch3, and the actual value that is being compared to that injected string is the value of the cookie: 59b997fa as a string (\0 as terminator ofc) instead of actual value in memory, as sprintf would convert that val into ASCII representation.
So there’s no way we can get the ready-made ASCII representation from the memory. We’ll have to inject the string ourselves.
Quite like the previous phase, we still need to override the return address of getbuf to our injected code. Then put the address of the injected string to %rdi, then modify the return address again to touch3, then ret.
1 | mov $0x5561dcb0,%rdi /* address of the injected string */ |
Then we’ll override the return address to 0x5561dc91, the same as level 2 as the length of the injected instructions doesn’t change.
Moreover we need to gap the space between 0x5561dca8 to 0x5561dcb0, which is 8 byte long. Just fill in anything in hexadecimal representation.
At last the ASCII string. Checking man ascii:
1 | 35 39 62 39 39 37 66 61 /* injected string */ |
Now we’ve constructed the whole thing, put them together and use hex2raw to generate the final input.
Though previous injection won’t work this time, the logic of the attack is the same:
0x59b997fa into %rditouch3We can find useful two gadgets from the farm provided in this new rtarget:
1 | 00000000004019a0 <addval_273>: |
The first one (addval_273) contains:
1 | 00000000004019a2 <addval_273 + 2>: |
While the second contains:
1 | 00000000004019ab <addval_219 + 4> |
Now we have a plan: inject the cookie on to the stack, pop it to %rax then move it to %rdi, finally jump to touch2.
Since the ret at the end of each gadget would increment %rsp by 8, there’s no need of jumping to injected instructions.
1 | ab 19 40 00 00 00 00 00 /* 219+4: popq %rax */ |
You know what, I’m scared when the handout says:
That may not seem significantly more difficult than using an ROP attack to invoke touch2, except that we have made it so.
So I’m stopping here.
Nice little lab.
Actually I think buffer-overflow attacks are more interesting than ROP, though they do not work these days. It feels kinda boring to find all the gadgets across the program(without a farm) and composing them together.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。