这里说的“后门”并不是教你做坏事,而是让你做好事,搭建自己的调试工具更好地进行调试开发。我们都知道,当程序发生异常错误时,我们需要定位到错误,有时我们还想,我们在不修改程序的前提下,就能通过log来定位错误呢?有人会说,我在我的程序里加多点打印就好了,程序每做一步我就加一行打印,到时一查log就知道程序在哪一步死掉的了。这个方法在小程序里也许会行得通,但是,在一个大型系统,每秒的log达到几百条,那时我们怎么能在这繁多的log里找出我们想要的那条的log的?这工作量大得夸张。工程中的解决方法就是给自己的程序开个后门专门给开发人员来调试程序。
我在上篇文章:《Linux编程之定制带级别的log》里介绍了如何自定义自己的带级别log,我们就可以在我们后门程序里修改log 的level,从而使得log的级别发生改变,而无需重新修改程序。比如我们初始化我们的log级别是FATAL,那么此时只会打印出FATAL的信息,但是有一次程序发生了错误,但是我们无法从FATAL的log里看出问题,那我们通过后台修改log的级别为ALARM,我们通过ALARM的log查出了程序错误的原因。当然我们通过后门能做的不仅仅是这些,具体来说,后门就是我们程序眼和跑着的程序交流的一道门。
搭建这么一个程序后门主要有这么几个关键点:
- 在进程里开一个线程用于充当debug center
- 该线程通过fifo接收开发人员传给它的命令
- 解析这些命令
- 用脚本搭建简单的命令行界面
一、创建debug center线程
这个就没什么好说了,我使用了上篇文章《Linux编程之自定义消息队列》所搭建的消息队列框架,将其中的msg_sender1改造为debug_center线程,作为我们的程序后门,我们跟程序交互就是从这里开始的。
if(pthread_create(&debug_thread_id, NULL, (void*)debug_center, NULL)) { MY_LOG(FATAL,"create debug center fail! "); return -1; }
二、创建FIFO
为什么要创建FIFO(有名管道)?因为我们需要跟我们的程序进行通信,我们需要把我们的指令告诉程序,那就需要一个通信途径,FIFO就是一个很好的选择。我们把我们的指令写进管道,程序将指令从管道出,然后执行该指令,这样子我们程序后门的通信模型就出来了。why解决了,是时候解决how了。
对于管道的操作,我是这么做的:
system("rm /vob/ljsdpoenew3/exercise/debug_log"); //每次进入debug center我们都将原来的fifo文件删除,避免影响后面操作 rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", 0666); //创建fifo if(rc < 0) { MY_LOG(DEBUG, "make fifo fail! "); pthread_exit(0); } fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r"); //打开fifo,读取指令 if(fp == NULL) { MY_LOG(DEBUG, "open debug_log fail! "); pthread_exit(0); }
读fifo我们解决了,那怎么将我们的指令写进fifo呢?这里我打算使用shell的read指令,文章后面会解释如何实现。
三、解析指令
解析指令又可以分为两个步骤:
- 将从fifo取得数据进行格式解析,比如我定义了d d的意思是display debug,即显示现在的debug级别,那么我们程序就得首先对原始数据进行格式处理。
- 将指令进行命令解析,执行相应操作。
格式处理:
static int get_args(FILE *inputFile) { char tmpBuffer[100]; char *line = tmpBuffer; char separator[] = " , "; char *token; int i; char eof; int num = 0; eof = !fgets(line, sizeof(tmpBuffer), inputFile); if (eof) return num; token = strtok(line, separator); while (num < MAX_NUM_ARGS && token) { strcpy(args[num], token); num++; token = strtok(NULL, separator); } for (i = num; i < MAX_NUM_ARGS; i++) args[i][0] = 0; return num; }
命令解析:
switch(args[0][0]) //解析指令,看每个指令对应哪些意思 { case 'd': //display switch(args[1][0]) { case 'q': //display queue show_MQ(fd); break; case 'd': //display debug show_debug_level(fd); break; default: help_manual(fd); break; } break; case 's': //set switch(args[1][0]) { case 'd': //set debug level n = atoi(args[2]); //将字符串转化为整数 fprintf(fd," debug level change from %d to %d",global.debug_level,n); global.debug_level = n; //更改log级别 break; default: help_manual(fd); break; } break; default: help_manual(fd); break; }
四、搭建debug界面
先上界面图:
我们敲的每个命令都是通过该界面传递到程序那头的,比如“d d”就表示展示出现在系统运行时的log级别,而“s d”就是设置我们想要看的log级别,这样我们就可以实现通过程序后门动态修改程序走向了。
那该如何实现这个看似简单的交互界面呢?实现该交互界面,有几个关键点:
- 我们需将程序的打印输出重定向到一个文件里
- 使用shell脚本读出文件的内容
- 我们输入的命令需写入到fifo中
以上三点就是做成界面的最重要的技术问题。
- 重定向输出
fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+"); if(fd == NULL) { MY_LOG(DEBUG, "open debug_log2 fail! "); pthread_exit(0); } fprintf(fd," debug level change from %d to %d",global.debug_level,n);
这样我们在debug center产生的打印都输出到文件debug_log2上了。
2.利用脚本读出内容且将内容写入fifo
要做到这点需要shell编程的简单知识,我们怎么将我们的输入放到fifo呢?我们怎么把log文件的内容读出来显示在显示屏呢?我想到首先是再写个client,进行进程间通信嘛,将命令写到fifo,同时也读出log文件的内容。但是,我发现利用shell就可以做到以上的事了,而且只需花费几行代码。
#!/bin/bash my_inp_fifo=/vob/ljsdpoenew3/exercise/debug_log # 指定fifo文件 stty erase ^H while [ true ]; do read inp #循环读入用户输入 if [ "$inp" == "quit" ];then #quit代表结束该界面 exit 0 fi echo $inp > $my_inp_fifo #写入fifo cat debug_log2.txt #显示log的内容 done
那看看这个命令行界面跑起来是怎么的吧!
首先我们运行server进程
sever进程不断产生消息并处理消息。
我们打开另一个窗口,并执行脚本./test.sh,进入界面
我们使用了d d命令看到了程序此时的debug级别,用d q看出了程序消息队列的情况。
我们使用了s d指令将debug level设置为0,此时屏幕没有任何打印输出,当我们在使用s d指令将level设置为-1(即将所有位置一),此时所有打印级别都打开了,屏幕又开始疯狂打印了。也就说,我们通过后门操控了程序,这里我们只是仅仅修改了程序的log级别,当然我们还可以做更多的事,只要依照这个框架往里面加指令,以及对应的处理操作,就可以实现了。
五、总结
所谓后门,就是一个可以操控程序的接口,这个接口仅仅用于开发者调试开发,不会开放给客户。所以这个后门的作用非常巨大,所以是开发者调试程序的一大利器。有人会想,我想用socket来代替fifo进行进程通信可以不,这样就可以做到远程主机操控程序了。我觉得是可以的,但是感觉利用telnet到目的主机再运行脚本操作比较安全。
最后给出源代码框架
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <sys/types.h> 6 #include <sys/stat.h> 7 #include <sys/prctl.h> 8 #include "msg_def.h" 9 #include "global.h" 10 11 extern Queue_t MsgQueue; 12 extern dashboard_t global; 13 14 15 #define MAX_NUM_ARGS 20 16 #define MAX_ARGS_SIZE 56 17 18 static char args[MAX_NUM_ARGS][MAX_ARGS_SIZE]; 19 20 static int get_args(FILE *inputFile,FILE *fd) 21 { 22 char tmpBuffer[100]; 23 char tmpBuffer2[100]; 24 char *line = tmpBuffer; 25 char separator[] = " , "; 26 char *token; 27 int i; 28 char eof; 29 30 int num = 0; 31 32 eof = !fgets(line, sizeof(tmpBuffer), inputFile); 33 if (eof) 34 return num; 35 36 memcpy(tmpBuffer2,tmpBuffer,100); 37 38 39 token = strtok(line, separator); 40 while (num < MAX_NUM_ARGS && token) 41 { 42 strcpy(args[num], token); 43 num++; 44 token = strtok(NULL, separator); 45 } 46 47 for (i = num; i < MAX_NUM_ARGS; i++) 48 args[i][0] = 0; 49 50 if(num > 0) 51 { 52 fprintf(fd, "%s", tmpBuffer2); 53 } 54 55 return num; 56 } 57 58 59 static void help_manual(FILE* fd) 60 { 61 fprintf(fd," d d : display current debug level "); 62 fprintf(fd,"d q : display msg queue length, head and tail "); 63 fprintf(fd,"s d [level] : set debug [level] "); 64 } 65 66 static void show_MQ(FILE* fd) 67 { 68 fprintf(fd," msg queue length:%d head:%d tail:%d ",abs(MsgQueue.head-MsgQueue.rear),MsgQueue.head,MsgQueue.rear); 69 } 70 71 static void show_debug_level(FILE* fd) 72 { 73 fprintf(fd," current debug level: %d ", global.debug_level); 74 } 75 76 77 78 void debug_center() 79 { 80 int rc,num,n; 81 FILE* fp; 82 FILE* fd; 83 84 MY_LOG(DEBUG,"Hi,debug! "); 85 86 87 system("rm /vob/ljsdpoenew3/exercise/debug_log"); 88 system("rm /vob/ljsdpoenew3/exercise/debug_log2"); 89 rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", 0666); 90 if(rc < 0) 91 { 92 MY_LOG(DEBUG, "make fifo fail! "); 93 pthread_exit(0); 94 } 95 96 fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r"); 97 if(fp == NULL) 98 { 99 MY_LOG(DEBUG, "open debug_log fail! "); 100 pthread_exit(0); 101 } 102 103 fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+"); 104 if(fd == NULL) 105 { 106 MY_LOG(DEBUG, "open debug_log2 fail! "); 107 pthread_exit(0); 108 } 109 //freopen("/vob/ljsdpoenew3/exercise/debug_log2.txt", "w+", fd); 110 111 fprintf(fd," ================================================== "); 112 fprintf(fd," Welcme to Debug Center!"); 113 fprintf(fd," ================================================== "); 114 help_manual(fd); 115 //fflush(fd); 116 117 while(1) 118 { 119 fflush(fd); 120 fprintf(fd," my_diag>"); 121 num = get_args(fp,fd); 122 if(num < 1) 123 { 124 freopen("/vob/ljsdpoenew3/exercise/debug_log", "r", fp); 125 fflush(fd); 126 continue; 127 } 128 fprintf(fd," my_diag>"); 129 switch(args[0][0]) 130 { 131 case 'd': //display 132 switch(args[1][0]) 133 { 134 case 'q': //display queue 135 show_MQ(fd); 136 break; 137 case 'd': //display debug 138 show_debug_level(fd); 139 break; 140 141 default: 142 help_manual(fd); 143 break; 144 } 145 break; 146 147 case 's': //set 148 switch(args[1][0]) 149 { 150 case 'd': //set debug level 151 n = atoi(args[2]); 152 fprintf(fd," debug level change from %d to %d",global.debug_level,n); 153 global.debug_level = n; 154 break; 155 156 default: 157 help_manual(fd); 158 break; 159 } 160 break; 161 162 default: 163 help_manual(fd); 164 break; 165 } 166 167 } 168 }