This part is for format string challenges.
realtime data monitor
Attachment: realtime-data-monitor
Description: When Xiao A was scanning a certain pharmaceutical factory, he found a large real-time database system. Xiao A realizes that the real-time database system collects and stores data of thousands of nodes related to industrial processes. As long as you log in, you can get valuable data. In the process of trying to log in to the real-time database system, Xiao A was unable to find a way to modify the login system key. Although she has now collected the value of the key that can log in to the system, she can only find other ways to log in.
Information
Check:
1 | $ file realtime-data-monitor |
Pseudocode:main:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
locker:
1 | int locker() |
imagemagic:
1 | int __cdecl imagemagic(char *format) |
Analysis
A format string vulnerability in imagemagic() has to be used to overwrite key as 0x2223322, and this happens to appear as the last example in Format String Vulnerability Intro (to reach Path 3).
There I provided two payloads:
- the format built on my own – sort numbers from smallest to largest.
- use functions that other people provided – use integer overflow.
Clearly my own payload can solve this kind of challenge, here we have to write 0x02, 0x22, 0x33 and 0x22 to 0x0804A048, 0x0804A049, 0x0804A04a and 0x0804A04b, and the basic techniques I have already told before, however we have to get used to using corresponding module to be more effective (just want to slack off).
First we have to determine the parameter order of our input:
1 | $ ./realtime-data-monitor |
We can see our input aaaaaaaa locates at the 12th parameter of format string (13th parameter of printf function).
Then use payload to write exploit.
Exploit
It uses some codes/functions in the last example of Format String Vulnerability Intro.
1 | from pwn import * |
Using fmtstr_payload of pwntools has the same result.
1 | payload = fmtstr_payload(12,{key_addr:0x02223322}) |
Mary Morton
Attachment: mary_morton
Description: A very simple warm-up pwn.
Information
Check:
1 | $ file mary_morton |
Pseudocode:main:
1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |
In the front of main, sub_4009FF is for setting alarm, sub_4009DA is for printing context.
sub_4008EB:
1 | unsigned __int64 sub_4008EB() |
sub_400960:
1 | unsigned __int64 sub_400960() |
sub_4008DA (not in the main logic):
1 | int sub_4008DA() |
Analysis
If you run this, you will see the interface and know what the vulnerabilities are:
1 | $ ./mary_morton |
In subfunction 1 (sub_400960), read(0, &buf, 0x100uLL); is the vulnerability, which reads too much and cause stack overflow. The tricky part is to overcome Canary mechanism (aims to prevent stack overflow), and we have seen it in ADworld stack2 write-up.
But we still have chance, if we can remain the value of canary unchangable, program is also able to run normally.
In subfunction 2 (sub_400960), printf(&buf, &buf); is the format string vulnerability, we can use it to leak Canary content, then rewrite it when performing stack overflow.
First of all, for format string vulnerability part, we have to indentify the order of Canary (input aaaaaaaa,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p):
1 | $ ./mary_morton |
From the output we can see our input (buf) is the 6th parameter on the stack, while buf is at the [rbp-90h] and v2 (that is Canary) is at the [rbp-8h], so it needs (0x90-0x8)/0x8 = 0x11 = 17 parameters more to get to v2 (Canary). So Canary is the 17 + 6 = 23rd parameter. Pyaload should be %23$p.
Then we do stack overflow, see exploit for more detailed action.
Exploit
My former exploits always use recv() to get output and do interaction. While using recvuntil() to replace recv() is a better way and will make exploit script common to both local and remote.
1 | from pwn import * |
greeting-150
Attachment: greeting-150
Description: None
It’s a format string vulnerability and is related to GOT hijacking, and needs a little tip.
Information
Check:
1 | $ file greeting-150 |
Pseudocode:main:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
getnline:
1 | size_t __cdecl getnline(char *s, int n) |
Analysis
It has a classic format string vulnerability, but cannot be exploited by just writting some number to some place.
Since it has Canary and enable the NX, we have to use payload to get shell, then we think about hijacking GOT item. Obviously we have to hijack some item as system_plt, and the /bin/sh still needs to input.
Only one time of input is not enough, we have to control the program flow for more input chances. But we cannot waste this one chance by just leaking ebp or something, so we do not know base address of the stack, right? We cannot overwrite ret addr as main to drop into main loop, but there is another choice: .fini_array.
Before main function, the codes in .init and all func pointers in .init_arrary will be called, and after main the codes in .fini and all func pointers in .fini_arrary will be called. Overwriting 1st item in .fini_array as start will achieve the main loop.
The whole process of a program (visit linuxProgramStartup for details):
So the first time we input, we hijack .fini; when we back to main we hijack strlen in GOT as system, so that when we input /bin/sh into this program, it happens to be the first parameter of strlen (written as system); then we get shell.
Sadly, after trying in this way I found that I can only go back to start once. So we have to overwrite .fini_array and strlen in GOT in one time.
Note: Using gdb or gef to debug it is fine, while using pwngdb and peda will meet an error:
1 | pwndbg> b main |
That is because the program forked a child process and it will mess up pwngdb and peda. The way to solve it:
1 | pwndbg> set follow-fork-mode parent |
Exploit
The limits for payload are:
- The length must be less than
64 - Can only return to
startonce (cant figure it out)
With the limit 1, our former gadgets like fmt_str and fmtstr_payload of pwntools cannot meet the needs, so we have to write a more compact payload (with hn to write every double bytes, instead of hnn to write every byte).
1 | from pwn import * |