前言

这个问题是在做逆向时碰到的,就是一个猜数字的游戏,要猜对一百轮,这时,我的第一个想法是写一个自动与程序交互的脚本,于是就开始网上找有没有类似的脚本或方法了。

首先,要说明的是我并没有做出来关于这个猜数字程序的自动交互脚本,因为在这过程中遇到了个问题,这在下面讲述,所以我的方法只是面向一个简单的test程序:

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int main(){
char str[10];
printf("hello\n");
scanf("%s", str);
printf("%s hello", str);
scanf("%s",str);
printf("hello %s\n", str);
return 0;
}

其次,我只是对我的方法进行一个叙述,说实话,这个方法很臭,我是真的想不出来好的方法了T_T T_T T_T

问题

使用python的subprocess模块,

如果将程序输入输出导入subprocess模块的PIPE,即:

p = subprocess.Popen("test_fflush.exe", stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True, bufsize=0)

之后读取程序输出时使用readline()会堵塞程序的进行。

如果将程序的输出导入标准输出,然后使用终端运行该脚本,即:

p = subprocess.Popen("test_fflush.exe", stdin=subprocess.PIPE, stdout=sys.stdout, shell=True, bufsize=0)

标准输出直接返回到终端上了,我看到但无法在程序中获取。

如果将程序的输出导入到文件中,同时写入文件时的缓冲区也为0,即:

with open('file.txt', 'wb+', buffering=0) as f:

p = subprocess.Popen("test_fflush.exe", stdin=subprocess.PIPE, stdout=f, shell=True, bufsize=0)

(以下其实并不完全确定是否正确)如果程序具有缓冲区,那程序的输出也无法直接实时地推到文件中,我就无法在程序中读取文件获取程序输出了。在这种情况下,如果程序的缓冲区为0,即每次输出后有fflush()的话,就可以通过执行system命令读取文件内容实时获取输出。

简而言之就是,单纯交互的方法做不出来。

思路

本来想叫这部分内容为解决方法的,但这其实并不能称之为解决方法,毕竟要解决的是与自己写的一个简单的test程序进行交互。

所以,现在碰到的问题是什么?是无法在脚本执行过程中获取程序的输出。

在第三种情况:把输出导入到文件中,因为程序的输出在缓冲区没有刷新,文件里没有内容,所以获取不了输出。

正道走不了就走偏一点。

因此,想到的第一个方法DLL注入:让程序执行一次setvbuf(),使缓冲区为0。

好消息:注入成功了;坏消息:没用。

按理来说是可以的,但我并不清楚为啥不成功。

然后,想到的第二个方法是IAT hook:每次程序执行完一次输出,就执行fflush(),刷新缓冲区。

这里要说明的是,我现在要解决的是我的test程序,里面的输出都是由printf()来完成的,所以我清楚我需要直接hook printf()函数,这其实也是这个思路的一个局限,我不一定知道程序的输出采用哪一个输出函数。

结果同样不行。

最后,想到的第三个方法是,既然我都实现了IAT hook了,那我直接在我自己写的printf()中获取printf()的参数,然后直接将其写入一个文件中,这样我的脚本就可以读取文件的内容获取每一次输出的内容了。

结果是这个方法成功了,但麻烦无比。

代码

自己的printf()函数

重写了的printf()函数其实并不美丽,我只是简单写了一下。

实际上的printf()函数使用的是可变参数来获取其后面的参数,然后根据第一个参数格式字符串来解析后面变量的类型。

这里,我采用同样的思路,只是解析写得简单了很多,然后使用可变参数获取后面的参数。

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 MyPrintf(const char* formatOri, ...)
{
FILE* f = fopen("output.txt", "w");
if (g_OldPfnMsgA != nullptr)
{
std::string format(formatOri);
size_t pos = 0;
int parmNum = 0;
int parmType[25] = { 0 };
while ((pos = format.find('%', pos)) != std::string::npos) {
char type = format[pos + 1];
std::string type_name;
switch (type) {
case 's':
parmType[parmNum] = 0;
pos += 2;
parmNum++;
break;
case 'd':
parmType[parmNum] = 1;
pos += 2;
parmNum++;
break;
case 'c':
parmType[parmNum] = 2;
pos += 2;
parmNum++;
break;
}
}

va_list ap;
// va_start的第二个参数得是printf的第一个参数,否则找不到后续参数的地址
va_start(ap, formatOri);

int sum = 0;
int i = 0;
fputs(formatOri, f);
for (i = 0; i < parmNum; i++) {
switch (parmType[i]) {
case 0:
fprintf(f, "%s\n", va_arg(ap, char*));
break;
case 1:
fprintf(f, "%d\n", va_arg(ap, int));
break;
case 2:
fprintf(f, "%c\n", va_arg(ap, char));
break;
}
}
fclose(f);
va_end(ap);
puts("真的进去了!!!");
}
return 0;
}

交互脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import os
import subprocess
import sys
from time import sleep

p = subprocess.Popen("test.exe", stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=sys.stderr, shell=True)
while p.poll() is not None:
pass
while True:
out = open("output.txt", "r")
print(out.readlines())
out.close()
p.stdin.write((input() + '\n').encode())
p.stdin.flush()