TCP远程命令执行

ops/2024/12/22 20:00:13/

目录

一.   命令集

二.   命令执行模块实现

三.   服务端模块实现

四.   服务端调用模块实现

五.   客户端模块实现

六.   效果展示


此篇教大家如何利用TCP进行远程命令执行。

一.   命令集

将值得信任的命令放进一个txt文件中,执行命令时,就去这个文件里面找,有就执行命令,没有就不执行。

ls -a -l
pwd
tree
whoami
who
uname -a
cat
touch

注意我们是用空格隔开的。

二.   命令执行模块实现

依然封装成类,将上述命令集写进类中。

class Command
{
public:Command(const string& cond_path):_cond_path(cond_path){}~Command(){}
private:set<string> _safe_cmd;string _cond_path;
};

有着两个成员,首先是set类型,即命令集。其次是上述文件集的路径。

 初始化路径之后,我们再来将命令写进集合中。

const string sep=" ";string PrefixCommand(const string& cmd)
{if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}
}void LoadConf(const string& conf)
{ifstream in(conf);if(!in.is_open()){LOG(FATAL,"open %s error\n",conf);return;}string line;while(getline(in,line)){LOG(DEBUG,"load command [%s] success\n",line.c_str());_safe_cmd.insert(PrefixCommand(line));}in.close();
}

LoadConf函数即初始化集合函数,利用文件读取流打开文件,一行一行读取命令集,并且只取第一个空格前面的字符插入进集合中。下面会讲为什么。

这个操作可以在构造函数的时候实现,所以可以将这个函数加入到构造函数中。

Command(const string& cond_path)
:_cond_path(cond_path)
{LoadConf(_cond_path);
}

 此处肯定需要检测输入的命令是否在命令集中。我们只检查输入命令第一个空格前的字符是否相同即可。例如"ls -a -l"只需检测ls即可。为什么呢?

例如touch指令,后面肯定要加创建的文件名字,这样就需要连着文件名一起检查在不在命令集中,这显然是不合理的,文件名有无数个,怎么能列举完呢。所以只需检测第一个空格前面相不相同即可。

来看如何检查:

const string sep=" ";string PrefixCommand(const string& cmd)
{if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}
}bool SafeCheck(const string& cmd)
{string prefix=PrefixCommand(cmd);auto iter=_safe_cmd.find(prefix);if(iter==_safe_cmd.end()){return false;}return true;
}string Excute(const string& cmd)
{string result;if(SafeCheck(cmd)){FILE* fp=popen(cmd.c_str(),"r");if(fp==nullptr){return "failed";}char buffer[1024];while(fgets(buffer,sizeof(buffer),fp)!=NULL){result+=buffer;}pclose(fp);}else{result="坏人\n";}return result;
}

Excute函数就是具体如何处理输入的正确指令,重点就是运用popen函数

FILE *popen(const char *command, const char *type);

popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。 

参数说明:

command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls

type: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:

如果调用 fork() 或 pipe() 失败,或者不能分配内存将返回NULL,否则返回一个读或者打开文件的指针。

 最后合起来就是命令指令模块:

const string sep=" ";class Command
{
private:void LoadConf(const string& conf){ifstream in(conf);if(!in.is_open()){LOG(FATAL,"open %s error\n",conf);return;}string line;while(getline(in,line)){LOG(DEBUG,"load command [%s] success\n",line.c_str());_safe_cmd.insert(PrefixCommand(line));}in.close();}
public:Command(const string& cond_path):_cond_path(cond_path){LoadConf(_cond_path);}string PrefixCommand(const string& cmd){if(cmd.empty()){return string();}auto pos=cmd.find(sep);if(pos==string::npos){return cmd;}else{return cmd.substr(0,pos);}}bool SafeCheck(const string& cmd){string prefix=PrefixCommand(cmd);auto iter=_safe_cmd.find(prefix);if(iter==_safe_cmd.end()){return false;}return true;}string Excute(const string& cmd){string result;if(SafeCheck(cmd)){FILE* fp=popen(cmd.c_str(),"r");if(fp==nullptr){return "failed";}char buffer[1024];while(fgets(buffer,sizeof(buffer),fp)!=NULL){result+=buffer;}pclose(fp);}else{result="坏人\n";}return result;}~Command(){}
private:set<string> _safe_cmd;string _cond_path;
};

其中LOG函数是封装的日志功能:

#pragma once//日志
#include<iostream>
#include<fstream>
#include<cstdio>
#include<string>
#include<ctime>
#include<unistd.h>
#include<sys/types.h>
#include<stdarg.h>
#include<pthread.h>
#include"LockGuard.hpp"using namespace std;bool gIsSave=false;
const string logname="log.txt";void SaveFile(const string& filename,const string& message)
{ofstream out(filename,ios::app);if(!out.is_open()){return;}out<<message;out.close();
}//1.日志是有等级的
enum Level
{DEBUG=0,INFO,WARNING,ERROR,FATAL
};string LevelToString(int level)
{switch(level){case DEBUG: return "Debug";break;case INFO: return "Info";break;case WARNING: return "Warning";break;case ERROR: return "Error";break;case FATAL: return "Fatal";break;default: return "Unknown";break;}
}string GetTimeString()
{time_t curr_time=time(nullptr);struct tm* format_time=localtime(&curr_time);if(format_time==nullptr) return "None";char time_buffer[64];snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",format_time->tm_year+1900,format_time->tm_mon+1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//2.日志是由格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容...
void LogMessage(string filename,int line,bool issave,int level,const char* format,...)
{string levelstr=LevelToString(level);string timestr=GetTimeString();pid_t selfid=getpid();//可变参数部分处理char buffer[1024];va_list arg;va_start(arg,format);vsnprintf(buffer,sizeof(buffer),format,arg);va_end(arg);LockGuard lockguard(&lock);string message;message="["+timestr+"]"+"["+levelstr+"]"+"[pid: "+to_string(selfid)+"]"+"["+filename+"]"+"["+to_string(line)+"]"+buffer+"\n";if(!issave){cout<<message;}else{SaveFile(logname,message);}
}void Test(int num,...)
{va_list arg;va_start(arg,num);while(true){int data=va_arg(arg,int);cout<<"data: "<<data<<endl;num--;}va_end(arg);//arg==NULL
}//C99新特性 __VA_ARGS__
#define LOG(level,format,...) do {LogMessage(__FILE__,__LINE__,gIsSave,level,format,##__VA_ARGS__);} while(0)
#define EnableFile() do {gIsSave=true;} while(0)
#define EnableScreen() do {gIsSave=false;} while(0)

三.   服务端模块实现

具体的实现跟前面TCP服务端模块实现一样(点此查看)。此处我们采用多线程来实现,就不过多赘述。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<pthread.h>
#include<functional>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>#include"Log.hpp"
#include"InetAddr.hpp"const static int defaultsockfd=-1;
const static int gbacklog=10;class TcpServer;struct ThreadData
{
public:ThreadData(int fd,InetAddr addr,TcpServer* s):sockfd(fd),clientaddr(addr),self(s){}
public:int sockfd;InetAddr clientaddr;TcpServer* self;
};enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR
};using func_t=function<string(const string&)>;class TcpServer
{
public:TcpServer(int port,func_t func):_port(port),_listensock(defaultsockfd),_isrunning(false),_func(func){}void InitServer(){//1.创建流式套接字_listensock=::socket(AF_INET,SOCK_STREAM,0);if(_listensock<0){LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket create success, sockfd is: %d\n",_listensock);//2.bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_port);local.sin_addr.s_addr=INADDR_ANY;int n=::bind(_listensock,(struct sockaddr*)&local,sizeof(local));if(n<0){LOG(FATAL,"bind error\n");exit(BIND_ERROR);}LOG(DEBUG,"bind success,sockfd is: %d\n",_listensock);//3.tcp是面向连接的,所以通信之前,必须先建立连接,服务器是被连接的//tcpserver启动,未来首先要一直等待客户的连接到来n=::listen(_listensock,gbacklog);if(n<0){LOG(FATAL,"listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG,"listen success,sockfd is: %d\n",_listensock);}void Service(int sockfd,InetAddr client){LOG(DEBUG,"get a new link,info %s:%d,fd: %d\n",client.Ip().c_str(),client.Port(),sockfd);string clientaddr="["+client.Ip()+":"+to_string(client.Port())+"]# ";while(true){char inbuffer[1024];ssize_t n=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);if(n>0){inbuffer[n]=0;cout<<clientaddr<<inbuffer<<endl;string result=_func(inbuffer);send(sockfd,result.c_str(),result.size(),0);}else if(n==0){//client退出&&关闭连接了LOG(INFO,"%s quit\n",clientaddr.c_str());break;}else{LOG(ERROR,"read error\n",clientaddr.c_str());break;}}::close(sockfd);//不关闭会发生文件描述符泄露}static void* HandlerSock(void* args)//IO和业务解耦{pthread_detach(pthread_self());ThreadData* td=static_cast<ThreadData*>(args);td->self->Service(td->sockfd,td->clientaddr);delete td;return nullptr;}void Loop(){_isrunning=true;//4.不能直接接收数据,应该先获取连接while(true){struct sockaddr_in peer;socklen_t len=sizeof(peer);int sockfd=::accept(_listensock,(struct sockaddr*)&peer,&len);if(sockfd<0){LOG(WARNING,"accept error\n");continue;}//采用多线程//此处不能像多进程一样关闭文件描述符,因为多线程文件描述符表是共享的pthread_t t;ThreadData* td=new ThreadData(sockfd,InetAddr(peer),this);pthread_create(&t,nullptr,HandlerSock,td);//将线程分离}_isrunning=false;}~TcpServer(){if(_listensock>defaultsockfd){::close(_listensock);}}
private:uint16_t _port;int _listensock;bool _isrunning;func_t _func;
};

其中InetAddr.hpp文件是我们封装的。

#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>using namespace std;class InetAddr
{
private:void GetAddress(string* ip,uint16_t* port){*port=ntohs(_addr.sin_port);//网络字节序转为主机字节序*ip=inet_ntoa(_addr.sin_addr);//将网络字节序IP转为点分式十进制IP}
public:InetAddr(const struct sockaddr_in &addr):_addr(addr){GetAddress(&_ip,&_port);}string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}
private:struct sockaddr_in _addr;string _ip;uint16_t _port;
};

四.   服务端调用模块实现

只需创建出服务端类的对象,依次调用InitServer和Loop函数即可。并创建出执行命令类对象。

#include"CommandExcute.hpp"
#include"TcpServer.hpp"
#include<memory>using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" local_port\n"<<endl;
}// ./tcpserver port
int main(int argc,char *argv[])
{if(argc!=2){Usage(argv[0]);return 1;}EnableScreen();uint16_t port=stoi(argv[1]);Command cmd("./safe.txt");func_t cmdExec=bind(&Command::Excute,&cmd,placeholders::_1);unique_ptr<TcpServer> tsvr=make_unique<TcpServer>(port,cmdExec);tsvr->InitServer();tsvr->Loop();return 0;
}

五.   客户端模块实现

客户端还是没有变化。不懂得可看此处(点此查看)。

#include<iostream>
#include<string>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>#include"Log.hpp"using namespace std;void Usage(string proc)
{cout<<"Usage:\n\t"<<proc<<" serverip serverport\n"<<endl;
}// ./tcpclient serverip serverport
int main(int argc,char *argv[])
{if(argc!=3){Usage(argv[0]);exit(1);}string serverip=argv[1];uint16_t serverport=stoi(argv[2]);int sockfd=::socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){cerr<<"socket error\n"<<endl;exit(2);}//与udpclient一样,不需显式bind//构建目标主机的socket信息struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));if(n<0){cerr<<"connect error\n"<<endl;exit(3);}while(true){cout<<"Please Enter#";string outstring;getline(cin,outstring);ssize_t s=send(sockfd,outstring.c_str(),outstring.size(),0);//writeif(s>0){char inbuffer[1024];ssize_t m=recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);if(m>0){inbuffer[m]=0;cout<<inbuffer<<endl;}else{break;}}else{break;}}
}

六.   效果展示

可以看见只要在命令集中的命令都能执行。


总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)


http://www.ppmy.cn/ops/107895.html

相关文章

芯旺微,车规级32位MCU KF32A芯片简介

文章目录 1. 产品功能特点2. 行业应用3. 开发环境(IDE)4. 开发资源5. KungFu 内核参考1. 产品功能特点 2. 行业应用 汽车照明汽车车窗控制汽车空调面板汽车控制器3. 开发环境(IDE)

人工智能中的RAG指的是什么

目录 RAG的工作原理 RAG的优势 应用场景 例子 总结 在人工智能领域&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种结合检索和生成技术的模型架构。它将外部知识库中的信息检索与大规模语言模型&#xff08;如GPT&…

【大疆 SDR 图传 P1 】 功能拆解,通信功能剖析

大疆 SDR 图传 P1 拆解视频P1 SoC1、哲酷2、小米3、大疆&#xff08;文章主角&#xff09; 一、为什么说SDR技术1、sdr 软件无线电2、影视博主的测评方法3、第一个说自己SDR的还是这个老登 二、大疆的图传发展历程1、FPGA AD93632、 P1 自研1、2个DSP和一个CPU A72、音频子系统…

WordPress的安装与简单开发教程

WordPress是目前世界上最受欢迎的开源内容管理系统&#xff08;CMS&#xff09;&#xff0c;它以简便易用、扩展性强和庞大的生态系统著称。通过它&#xff0c;你可以轻松构建博客、企业网站、电子商务平台等多种类型的网站。本文将为你介绍WordPress的安装过程&#xff0c;以及…

串口通信协议(UART)

简介 uart通讯协议&#xff0c;是一种成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信的协议&#xff1b;是一种全双工&#xff0c;设备点对点通信的协议。下面从硬件电路、电平标准和串口参数等方面来了解uart通信协议。 硬件电路 硬件电路非常简单&am…

.gitnore | git

前言 新创建工程的时候, 我们将现有业务代码提交到git仓库后. 但是后面发现有一些文件你不想要每次都提交, ,比如你的编译链接临时文件。 这个时候你需要创建一个.gitignore 来取消文件追踪 所遇问题 使用gitignore 帮我写把根目录下的文件夹Listings和Objects都忽略追踪 …

★ 算法OJ题 ★ 力扣1004 - 最大连续 1 的个数 III

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;诺亚将和大家一起做一道滑动窗口算法题-- 最大连续 1 的个数 III~ ★ 主页 ★&#xff1a;椎名澄嵐-CSDN博客 ★ 专栏 ★&#xff1a;★ 优选算法100天 ★_椎名澄嵐的博客-CSDN博客 目录 一 题目 二 算法解析 三 …

Android Manifest 权限描述大全对照表

115工具网&#xff08;115工具网-一个提供高效、实用、方便的在线工具集合网站&#xff09;提供Android Manifest 权限描述大全对照表&#xff0c;可以方便andriod开发者查看安卓权限描述功能 权限名称描述android.permission.ACCESS_CHECKIN_PROPERTIES访问登记属性读取或写入…