这里说的“后门”并不是教你做坏事,而是让你做好事,搭建自己的调试工具更好地进行调试开发。我们都知道,当程序发生异常错误时,我们需要定位到错误,有时我们还想,我们在不修改程序的前提下,就能通过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线程,作为我们的程序后门,我们跟程序交互就是从这里开始的。
  1. if(pthread_create(&debug_thread_id, NULL, (void*)debug_center, NULL))
  2. {
  3. MY_LOG(FATAL,"create debug center fail!\n");
  4. return -;
  5. }
二、创建FIFO
为什么要创建FIFO(有名管道)?因为我们需要跟我们的程序进行通信,我们需要把我们的指令告诉程序,那就需要一个通信途径,FIFO就是一个很好的选择。我们把我们的指令写进管道,程序将指令从管道出,然后执行该指令,这样子我们程序后门的通信模型就出来了。why解决了,是时候解决how了。
 
对于管道的操作,我是这么做的:
  1. system("rm /vob/ljsdpoenew3/exercise/debug_log"); //每次进入debug center我们都将原来的fifo文件删除,避免影响后面操作
  2. rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", ); //创建fifo
  3. if(rc < )
  4. {
  5. MY_LOG(DEBUG, "make fifo fail!\n");
  6. pthread_exit();
  7. }
  8.  
  9. fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r"); //打开fifo,读取指令
  10. if(fp == NULL)
  11. {
  12. MY_LOG(DEBUG, "open debug_log fail!\n");
  13. pthread_exit();
  14. }

读fifo我们解决了,那怎么将我们的指令写进fifo呢?这里我打算使用shell的read指令,文章后面会解释如何实现。

三、解析指令
解析指令又可以分为两个步骤:
  1. 将从fifo取得数据进行格式解析,比如我定义了d d的意思是display debug,即显示现在的debug级别,那么我们程序就得首先对原始数据进行格式处理。
  2. 将指令进行命令解析,执行相应操作。
 
格式处理:
  1. static int get_args(FILE *inputFile)
  2. {
  3. char tmpBuffer[];
  4. char *line = tmpBuffer;
  5. char separator[] = " ,\n\t";
  6. char *token;
  7. int i;
  8. char eof;
  9.  
  10. int num = ;
  11.  
  12. eof = !fgets(line, sizeof(tmpBuffer), inputFile);
  13. if (eof)
  14. return num;
  15.  
  16. token = strtok(line, separator);
  17. while (num < MAX_NUM_ARGS && token)
  18. {
  19. strcpy(args[num], token);
  20. num++;
  21. token = strtok(NULL, separator);
  22. }
  23.  
  24. for (i = num; i < MAX_NUM_ARGS; i++)
  25. args[i][] = ;
  26.  
  27. return num;
  28. }
 
命令解析:
  1. switch(args[][]) //解析指令,看每个指令对应哪些意思
  2. {
  3. case 'd': //display
  4. switch(args[][])
  5. {
  6. case 'q': //display queue
  7. show_MQ(fd);
  8. break;
  9. case 'd': //display debug
  10. show_debug_level(fd);
  11. break;
  12.  
  13. default:
  14. help_manual(fd);
  15. break;
  16. }
  17. break;
  18.  
  19. case 's': //set
  20. switch(args[][])
  21. {
  22. case 'd': //set debug level
  23. n = atoi(args[]); //将字符串转化为整数
  24. fprintf(fd," debug level change from %d to %d",global.debug_level,n);
  25. global.debug_level = n; //更改log级别
  26. break;
  27.  
  28. default:
  29. help_manual(fd);
  30. break;
  31. }
  32. break;
  33.  
  34. default:
  35. help_manual(fd);
  36. break;
  37. }
四、搭建debug界面
先上界面图:

 
我们敲的每个命令都是通过该界面传递到程序那头的,比如“d d”就表示展示出现在系统运行时的log级别,而“s d”就是设置我们想要看的log级别,这样我们就可以实现通过程序后门动态修改程序走向了。
 
那该如何实现这个看似简单的交互界面呢?实现该交互界面,有几个关键点:
  • 我们需将程序的打印输出重定向到一个文件里
  • 使用shell脚本读出文件的内容
  • 我们输入的命令需写入到fifo中
以上三点就是做成界面的最重要的技术问题。
 
  1. 重定向输出

  一开始我是打算将标准输出、标准错误都重定向到文件里的额,但是想了想,还是想直接把打印内容直接输出到文件就好了。比如这样:

  1. fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+");
  2. if(fd == NULL)
  3. {
  4. MY_LOG(DEBUG, "open debug_log2 fail!\n");
  5. pthread_exit();
  6. }
  7.  
  8. 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就可以做到以上的事了,而且只需花费几行代码。

  1. #!/bin/bash
  2.  
  3. my_inp_fifo=/vob/ljsdpoenew3/exercise/debug_log # 指定fifo文件
  4. stty erase ^H
  5.  
  6. while [ true ];
  7. do
  8.  
  9. read inp #循环读入用户输入
  10. if [ "$inp" == "quit" ];then #quit代表结束该界面
  11. exit
  12. fi
  13. echo $inp > $my_inp_fifo #写入fifo
  14. cat debug_log2.txt #显示log的内容
  15. 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. #define MAX_NUM_ARGS 20
  15. #define MAX_ARGS_SIZE 56
  16.  
  17. static char args[MAX_NUM_ARGS][MAX_ARGS_SIZE];
  18.  
  19. static int get_args(FILE *inputFile,FILE *fd)
  20. {
  21. char tmpBuffer[];
  22. char tmpBuffer2[];
  23. char *line = tmpBuffer;
  24. char separator[] = " ,\n\t";
  25. char *token;
  26. int i;
  27. char eof;
  28.  
  29. int num = ;
  30.  
  31. eof = !fgets(line, sizeof(tmpBuffer), inputFile);
  32. if (eof)
  33. return num;
  34.  
  35. memcpy(tmpBuffer2,tmpBuffer,);
  36.  
  37. token = strtok(line, separator);
  38. while (num < MAX_NUM_ARGS && token)
  39. {
  40. strcpy(args[num], token);
  41. num++;
  42. token = strtok(NULL, separator);
  43. }
  44.  
  45. for (i = num; i < MAX_NUM_ARGS; i++)
  46. args[i][] = ;
  47.  
  48. if(num > )
  49. {
  50. fprintf(fd, "%s", tmpBuffer2);
  51. }
  52.  
  53. return num;
  54. }
  55.  
  56. static void help_manual(FILE* fd)
  57. {
  58. fprintf(fd,"\nd d : display current debug level\n");
  59. fprintf(fd,"d q : display msg queue length, head and tail\n");
  60. fprintf(fd,"s d [level] : set debug [level] \n");
  61. }
  62.  
  63. static void show_MQ(FILE* fd)
  64. {
  65. fprintf(fd," msg queue length:%d head:%d tail:%d \n",abs(MsgQueue.head-MsgQueue.rear),MsgQueue.head,MsgQueue.rear);
  66. }
  67.  
  68. static void show_debug_level(FILE* fd)
  69. {
  70. fprintf(fd," current debug level: %d\n", global.debug_level);
  71. }
  72.  
  73. void debug_center()
  74. {
  75. int rc,num,n;
  76. FILE* fp;
  77. FILE* fd;
  78.  
  79. MY_LOG(DEBUG,"Hi,debug!\n");
  80.  
  81. system("rm /vob/ljsdpoenew3/exercise/debug_log");
  82. system("rm /vob/ljsdpoenew3/exercise/debug_log2");
  83. rc = mkfifo("/vob/ljsdpoenew3/exercise/debug_log", );
  84. if(rc < )
  85. {
  86. MY_LOG(DEBUG, "make fifo fail!\n");
  87. pthread_exit();
  88. }
  89.  
  90. fp = fopen("/vob/ljsdpoenew3/exercise/debug_log", "r");
  91. if(fp == NULL)
  92. {
  93. MY_LOG(DEBUG, "open debug_log fail!\n");
  94. pthread_exit();
  95. }
  96.  
  97. fd = fopen("/vob/ljsdpoenew3/exercise/debug_log2", "w+");
  98. if(fd == NULL)
  99. {
  100. MY_LOG(DEBUG, "open debug_log2 fail!\n");
  101. pthread_exit();
  102. }
  103. //freopen("/vob/ljsdpoenew3/exercise/debug_log2.txt", "w+", fd);
  104.  
  105. fprintf(fd," \n==================================================\n");
  106. fprintf(fd," Welcme to Debug Center!");
  107. fprintf(fd," \n==================================================\n\n");
  108. help_manual(fd);
  109. //fflush(fd);
  110.  
  111. while()
  112. {
  113. fflush(fd);
  114. fprintf(fd,"\n\nmy_diag>");
  115. num = get_args(fp,fd);
  116. if(num < )
  117. {
  118. freopen("/vob/ljsdpoenew3/exercise/debug_log", "r", fp);
  119. fflush(fd);
  120. continue;
  121. }
  122. fprintf(fd,"\n\nmy_diag>");
  123. switch(args[][])
  124. {
  125. case 'd': //display
  126. switch(args[][])
  127. {
  128. case 'q': //display queue
  129. show_MQ(fd);
  130. break;
  131. case 'd': //display debug
  132. show_debug_level(fd);
  133. break;
  134.  
  135. default:
  136. help_manual(fd);
  137. break;
  138. }
  139. break;
  140.  
  141. case 's': //set
  142. switch(args[][])
  143. {
  144. case 'd': //set debug level
  145. n = atoi(args[]);
  146. fprintf(fd," debug level change from %d to %d",global.debug_level,n);
  147. global.debug_level = n;
  148. break;
  149.  
  150. default:
  151. help_manual(fd);
  152. break;
  153. }
  154. break;
  155.  
  156. default:
  157. help_manual(fd);
  158. break;
  159. }
  160.  
  161. }
  162. }

Linux编程之给你的程序开后门的更多相关文章

  1. 从裸机编程到嵌入式Linux编程思想的转变------分而治之:驱动和应用程序

    笔者学习嵌入式Linux也有一段时间了,很奇怪的是很多书讲驱动编程方面的知识,也有很多书将ARM9方面的知识,但是从以前51形式的(对寄存器直接操作,初始化芯片的功能模块)编程方法,和思维模式,变换为 ...

  2. 牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结 转载

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  3. 【转】牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  4. Linux 编程中的API函数和系统调用的关系【转】

    转自:http://blog.chinaunix.net/uid-25968088-id-3426027.html 原文地址:Linux 编程中的API函数和系统调用的关系 作者:up哥小号 API: ...

  5. linux编程获取本机网络相关参数

    getifaddrs()和struct ifaddrs的使用,获取本机IP 博客分类: Linux C编程   ifaddrs结构体定义如下: struct ifaddrs { struct ifad ...

  6. 面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结

    基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会 ...

  7. Linux编程简介

    Linux编程可以分为Shell(如BASH.TCSH.GAWK.Perl.Tcl和Tk等)编程和高级语言(C语言,C++语言,java语言等)编程,Linux程序需要首先转化为低级机器语言即所谓的二 ...

  8. 在桌面Linux环境下开发图形界面程序的方案对比

    在Linux下开发GUI程序的方法有很多,比如Gnome桌面使用GTK+作为默认的图形界面库,KDE桌面使用Qt作为默认的图形界面库,wxWidgets则是另一个使用广泛的图形库,此外使用Java中的 ...

  9. Linux编程return与exit区别

    Linux编程return与exit区别 exit  是用来结束一个程序的执行的,而return只是用来从一个函数中返回. return return 表示从被调函数返回到主调函数继续执行,返回时可附 ...

随机推荐

  1. CSS学习笔记:利用border绘制三角形

    在前端的笔试.面试过程中,经常会出现一些不规则图形的CSS设置,基本上多是矩形外加一个三角形.利用CSS属性可以实现三角形的生成,主要利用上下左右的边距的折叠. 1.第一种图形: .box { wid ...

  2. 增加 Java 有几个好习惯表现

    以下是一些参考网络资源中的摘要Java编程在一些地方尽可能做. 1. 尝试使用单个例如在合适的场合 使用单例可以减轻负荷的负担,缩短加载时间.提高装载效率,但并不是所有的地方都适合一个案例.简单的说, ...

  3. 关于Java String对象创建的几点疑问

    我们通过JDK源码会知道String实质是字符数组,而且是不可被继承(final)和具有不可变性(immutable).可以如果想要了解String的创建我们需要先了解下JVM的内存结构. 1.JVM ...

  4. leetcode第28题--Divide Two Integers

    Divide two integers without using multiplication, division and mod operator. 分析:题目意思很容易理解,就是不用乘除法和模运 ...

  5. 【本·伍德Lua专栏】补充的基础06:简单的错误处理

    昨天遇到另外一位独立游戏开发人员,所以多聊了一会,然后-然后就没有看书了.(小若:借口!借口! ) 今天来聊聊错误处理吧.只是毕竟这仅仅是前面的章节.书上的内容似乎有点一笔带过的味道. 没关系,简单更 ...

  6. 《STL源代码剖析》---stl_hash_set.h阅读笔记

    STL仅仅规定接口和复杂度,对于详细实现不作要求.set大多以红黑树实现,但STL在标准规格之外提供了一个所谓的hash_set,以hash table实现.hash_set的接口,hash_tabl ...

  7. NHibernate 数据查询之QueryOver<T>

    NHibernate 数据查询之QueryOver<T>   一.限制运算符 Where:筛选序列中的项目WhereNot:反筛选序列中的项目 二.投影运算符 Select:创建部分序列的 ...

  8. ASP.NET MVC IOC之Unity攻略

    ASP.NET MVC IOC之Unity攻略 一.你知道IOC与DI吗? 1.IOC(Inversion of Control )——控制反转 即依赖对象不在被依赖模块的类中直接通过new来获取 先 ...

  9. 根据首尾字节的tcp分包断包算法

    这个算是我的一点小总结吧,放出来分享给大家,原来在网上找这种算法都找了N久没找到,自己写也是走了许多弯路,就放出来遛一遛吧 大家将就这个看看, 这是其中的一个主要的方法,其余的我就不放出来了,其中的I ...

  10. Java基础之集合:概览

    Java Basic->Collections->Overview 先抛一个问题,用一个类似树形的结构,介绍下 Java 的集合类数据结构:有哪些,从简单到复杂,有怎么样的继承关系. 下面 ...