feat: finish p32
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -1052,6 +1052,20 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p32"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"axum",
|
||||||
|
"common",
|
||||||
|
"hex",
|
||||||
|
"rand",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p33"
|
name = "p33"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
14
problems/p32/Cargo.toml
Normal file
14
problems/p32/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "p32"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
axum = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
common = { path = "../../common/" }
|
||||||
|
hex = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
anyhow = { workspace = true }
|
||||||
143
problems/p32/src/main.rs
Normal file
143
problems/p32/src/main.rs
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use common::sha1;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use axum::{Router, extract::Query, http::StatusCode, routing::get};
|
||||||
|
use rand::Rng;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
static HMAC_KEY: LazyLock<[u8; 20]> = LazyLock::new(|| {
|
||||||
|
// 生成随机密钥
|
||||||
|
let mut rng = rand::rng();
|
||||||
|
let key: [u8; 20] = rng.random();
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Params {
|
||||||
|
file: String,
|
||||||
|
signature: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_handler(Query(params): Query<Params>) -> StatusCode {
|
||||||
|
let my_signature = hmac_sha1(&*HMAC_KEY, params.file.as_bytes());
|
||||||
|
|
||||||
|
let provided_signature = match hex::decode(¶ms.signature) {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Hex decode error: {:?}", e);
|
||||||
|
return StatusCode::BAD_REQUEST;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match insecure_compare(&my_signature, &provided_signature).await {
|
||||||
|
true => StatusCode::OK,
|
||||||
|
false => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn insecure_compare(a: &[u8], b: &[u8]) -> bool {
|
||||||
|
if a.len() != b.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for i in 0..a.len() {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(5)).await;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hmac_sha1(key: &[u8], message: &[u8]) -> [u8; 20] {
|
||||||
|
let mut key = match key.len() {
|
||||||
|
0..=64 => key.to_vec(),
|
||||||
|
_ => sha1(key).to_vec(),
|
||||||
|
};
|
||||||
|
key.extend(vec![0; 64 - key.len()]);
|
||||||
|
let mut part_1 = key.iter().map(|x| x ^ 0x5c).collect::<Vec<u8>>();
|
||||||
|
let mut part_2_input = key.iter().map(|x| x ^ 0x36).collect::<Vec<u8>>();
|
||||||
|
part_2_input.extend(message);
|
||||||
|
let part_2 = sha1(&part_2_input);
|
||||||
|
part_1.extend(part_2);
|
||||||
|
sha1(&part_1)
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_hmac_sha1() {
|
||||||
|
let output = hmac_sha1("123".as_bytes(), "helloworld".as_bytes());
|
||||||
|
let output = hex::encode(output);
|
||||||
|
let correct = "8849fb5557760d35f19e2100aa250bfb97152bd9";
|
||||||
|
assert_eq!(output, correct);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_attack() -> Result<()> {
|
||||||
|
let mut signature = vec![0u8; 20];
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut last_max_time = Duration::from_millis(0);
|
||||||
|
let min_time_increase = Duration::from_millis(1); // 最小耗时增量阈值
|
||||||
|
|
||||||
|
while pos < 20 {
|
||||||
|
let mut max_time = Duration::from_millis(0);
|
||||||
|
let mut best_byte = 0u8;
|
||||||
|
|
||||||
|
for byte in 0x00..=0xff {
|
||||||
|
signature[pos] = byte;
|
||||||
|
let url = format!(
|
||||||
|
"http://localhost:3000/verify?file={}&signature={}",
|
||||||
|
"helloworld",
|
||||||
|
hex::encode(&signature)
|
||||||
|
);
|
||||||
|
let start = Instant::now();
|
||||||
|
let status = reqwest::get(&url).await?.status();
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
|
||||||
|
if status == StatusCode::OK {
|
||||||
|
println!("✓ 找到完整签名: {}", hex::encode(&signature));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if elapsed > max_time {
|
||||||
|
max_time = elapsed;
|
||||||
|
best_byte = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature[pos] = best_byte;
|
||||||
|
println!(
|
||||||
|
"位置 {} 确定为: {:02x}, 耗时: {:?}",
|
||||||
|
pos, best_byte, max_time
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查耗时是否有足够增长
|
||||||
|
if pos > 0 && max_time.saturating_sub(last_max_time) < min_time_increase {
|
||||||
|
println!("⚠ 耗时增长不足,回退1位重试",);
|
||||||
|
pos = pos.saturating_sub(1);
|
||||||
|
last_max_time = Duration::from_millis(0); // 重置基准
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_max_time = max_time;
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let app = Router::new().route("/verify", get(verify_handler));
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
|
||||||
|
|
||||||
|
dbg!(hex::encode(*HMAC_KEY));
|
||||||
|
|
||||||
|
// 后台启动服务器
|
||||||
|
tokio::spawn(async move {
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
});
|
||||||
|
start_attack().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user