SECUINSIDE CTF 2013 - lockd
Table of Contents
0x00. Introduction
)
0x01. Vulnerability
Buffer Overflow
int
To use the core lock() and unlock() functionalities in main(), you must input floor_804A4C0 and room_804A0A0 values within the valid range, and the result of read_password_8048A7D() must be True.
int
At this point, it reads 40 bytes to a 20 bytes buffer buf, allowing us to overwrite local variable password.
FSB in syslog
int
Once the password leak is successful, we can use the lock() and unlock() functionalities.
Taking a closer look at syslog(),
void ;
The second argument format is a format string, and related information can be found in the Linux manual page.
Never pass a string with user-supplied data as a format, use the following instead:
syslog(priority, "%s", string);
However, since lock() uses the format syslog(13, fmt_0804A0C0);, so FSB occurs if we insert a format string into fmt_0804A0C0.
Fortunately, we can pass a format string to fmt_0804A0C0 through name_804A2C0, so we can exploit the vulnerability.
0x02. Exploit
Info Leak
int
Looking at read_password_8048A7D() again, although we can manipulate the value of the local variable password by reading 40 bytes in read(), it’s meaningless because lock() and unlock() compare with the value of the global variable password_804A0A4.
However, another attack is possible: if we leave the last byte of password and overwrite the front part with the same value as buf, we can brute force byte by byte.
So I wrote the payload as follows.
=
=
= b *
+=
+=
+= b * 4
+= b *
break
continue
return
FSB
Normally, to utilize FSB, I would construct the payload like %p %p %p %p ... to check which format string index corresponds to the part pointed to by $esp. But since syslog() only logs to /var/log/syslog, I couldn’t check the results. Eventually, I manually fuzzed when and where values were written by increasing the ? value in %?$n.
As a result, I confirmed that the n-th memory from $esp can be accessed with %(n + 2)$n.
Also, not only our input format string is output, but the format string goes into the %s part of the "LOCK %d-%d by %s" string, so 0xc bytes of additional values are written. Therefore, I wrote the payload as follows:
# %n$ -> pointing (n + 2)th dword from esp
=
= 26
Now, looking at the stack when calling syslog() for exploitation:
Since all input is received in global variables, we need to exploit by making good use of the values on the stack. Initially, I didn’t realize that 4 bytes would be written at once, so:
- Write
0x00to0xffffddb4
0xffffddb4: 0xffffde00
- Write 2 bytes to
0xffffde00(lower 2 bytes)
0xffffde00: 0x0000a03c
- Write
0x02to0xffffddb4
0xffffddb4: 0xffffde02
- Write 2 bytes to
0xffffde00(upper 2 bytes)
0xffffde00: 0x0804a03c
- Write 2 bytes to
0x0804a03c(sprintf got)
I tried to proceed with the exploit this way, but the situation changed when I turned on ASLR.
When ASLR is off, 0xffffddb4 points to 0xffffdec4, allowing control of the 0xffffde?? area.
When ASLR is on, 0xff8ca714 points to 0xff8caec4, allowing control of the 0xff8cae?? area.
This creates a problem where the ? value when accessing with %?$n is not consistent, even though we’ve carefully constructed the sprintf() got address on the stack. After struggling with probability issues for a while, I discovered that 0x0804a03c is written all at once, which makes the exploit much simpler.
- Write
0x0804a03c(sprintf got) to0xffffddb4
0xffffddb4: 0x0804a03c
- Write
0x080485e0(system plt) to0x0804a03c
0x0804a03c: 0x080485e0
By the way, the idea of overwriting sprintf() with system() came from the fact that sprintf() was the only function where I could control the value in the first argument.
;
if
return -1;
;
The password must be in the first argument fmt_0804A0C0, but fortunately, while memcmp() only compares 16 bytes, the input receives 20 bytes, creating 4 bytes of free space. Therefore, if we add ;sh after the key, when the GOT overwrite succeeds, the function executes as follows:
// sprintf(fmt_0804A0C0, "./lock UNLOCK %d %d", floor_804A4C0, room_804A0A0);
;
The c39f30e348c07297 part is ignored as it’s not a legit command, and the next command sh is executed, spawning a shell.
0x03. Payload
= True
=
=
= f
=
return
=
=
= b *
+=
+=
+= b * 4
+= b *
break
continue
return
=
=
=
=
# [+] key : c39f30e348c07297
# key = ''.join(guess_key(s))
# log.success(f"key : {key}")
= b
# %n$ -> pointing (n + 2)th dword from esp
=
= 26
=
= 62