Windows 初级
拖到 ida 里,看着就像把输入值和一个常量的每个字节右移 2 位作比较,然后输出 Success。
Array.from(`98 01 00 00 B0 01 00 00 84 01 00 00 9C 01 00 00
EC 01 00 00 D4 00 00 00 C8 00 00 00 40 01 00 00
BC 01 00 00 28 01 00 00 A4 01 00 00 94 01 00 00
C8 00 00 00 C0 00 00 00 C8 00 00 00 CC 00 00 00
20 01 00 00 84 01 00 00 C0 01 00 00 C0 01 00 00
E4 01 00 00 38 01 00 00 94 01 00 00 DC 01 00 00
64 01 00 00 94 01 00 00 84 01 00 00 C8 01 00 00
F4 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00`.matchAll(/.. .. .. ../g))
.map(x => x[0])
.map(x => x.split(' ').reverse().join(''))
.map(x => parseInt(x, 16) >> 2)
.map(x => String.fromCodePoint(x))
.join('')
Android 初级题 1
拖到 jadx-gui 中,把那个字符串每个 ASCII 码减 2。
"hnci}|jwfclkczkppkcpmwckng\u007f".split('').map(x => x.codePointAt(0) - 2).map(x => String.fromCharCode(x)).join('')
Android 初级题 2
拖到 jadx-gui 中,uid + "Wuaipojie2023" 从后往前异或 5252...,然后 base64,然后 md5,然后恺撒密码挪动 5 位。
const script = document.createElement("script");
script.setAttribute("src", "https://unpkg.com/blueimp-md5/js/md5.min.js");
document.body.appendChild(script);
const uid = '765171';
`flag{${md5(btoa(`${uid}Wuaipojie2023`.split('').reverse().map((c, i) => String.fromCodePoint(c.codePointAt(0) ^ '52'.codePointAt(i % 2))).reverse().join(''))).replace(/[A-Za-z]/g, (c) => String.fromCodePoint((c.codePointAt(0) & 0xe0) + ((c.codePointAt(0) & 0x1f) + 5) % 26))}}`;
A 是 0b01000001,a 是 0b01100001,所以可以使用 0b11100000 (0xe0) 和 0b00011111 (0x1f) 进行位运算。
其他
Windows 中级和 Android 中级其实也做出来了,不调试过程太复杂了,不想写了。
Windows 中级
用 Rust 写的编码程序
fn new_uid_secret_table(k: u32) -> [u32; 4] {
let mut result = [0u32; 4];
for i in 0..4 {
result[i] = (i as u32 + 1).wrapping_mul(k + 1);
}
result
}
const FLAG_TEMPLATE: &[u8] = b"flag{!!!!_HAPPY_NEW_YEAR_2023!!!!}";
fn main() {
println!("Hello, world!");
let flag = BASE_TABLE.iter().enumerate().map(|(i, x)| *x ^ ((i as u32 + 1) * 0x11111111)).collect::<Vec<u32>>();
let flag_bytes = unsafe { &*slice_from_raw_parts(flag.as_ptr() as *const u16, BASE_TABLE.len() * 2) };
println!("{}", String::from_utf16_lossy(flag_bytes));
let k = generate_k(765171);
let lparam = k.wrapping_mul(32);
println!("k = 0x{:x} lparam = 0x{:x}", k, lparam);
let uid_secret_table = new_uid_secret_table(k);
println!("uid_secret_table = {:#x?}", uid_secret_table);
let mut flag_u32v = Vec::from(unsafe { &*slice_from_raw_parts(FLAG_TEMPLATE.as_ptr() as *const u32, 8) });
println!("flag_u32v = {:08x?}", flag_u32v);
for i in (0..8).step_by(2) {
let mask = encrypt_key_u32vec(&mut flag_u32v[i..], &uid_secret_table, k);
println!("mask = {}", mask);
}
println!("flag_u32v = {:08x?}", flag_u32v);
for i in (0..8).step_by(2) {
println!("k.wrapping_mul(32) = {}", k.wrapping_mul(32));
decrypt_key_u32vec(&mut flag_u32v[i..], &uid_secret_table, k, k.wrapping_mul(32));
}
println!("flag_u32v = {:08x?}", flag_u32v);
}
fn encrypt_key_u32vec(key_u32vec: &mut [u32], uid_secret_table: &[u32; 4], k: u32) -> u32 {
let mut key0 = key_u32vec[0];
let mut key1 = key_u32vec[1];
let mut mask = 0u32;
for _ in 0..32 {
mask = mask.wrapping_add(k);
key0 = key0.wrapping_add((uid_secret_table[1].wrapping_add(key1 >> 5)) ^ (mask.wrapping_add(key1)) ^ (uid_secret_table[0].wrapping_add(key1 << 4)));
key1 = key1.wrapping_add((uid_secret_table[3].wrapping_add(key0 >> 5)) ^ (mask.wrapping_add(key0)) ^ (uid_secret_table[2].wrapping_add(key0 << 4)));
}
key_u32vec[0] = key0;
key_u32vec[1] = key1;
return mask;
}
fn decrypt_key_u32vec(key_u32vec: &mut [u32], uid_secret_table: &[u32; 4], k: u32, mut mask: u32) -> u32 {
let mut key0 = key_u32vec[0];
let mut key1 = key_u32vec[1];
for i in 0..32 {
key1 = key1.wrapping_sub((uid_secret_table[3].wrapping_add(key0 >> 5)) ^ (mask.wrapping_add(key0)) ^ (uid_secret_table[2].wrapping_add(key0 << 4)));
key0 = key0.wrapping_sub((uid_secret_table[1].wrapping_add(key1 >> 5)) ^ (mask.wrapping_add(key1)) ^ (uid_secret_table[0].wrapping_add(key1 << 4)));
mask = mask.wrapping_sub(k);
}
key_u32vec[0] = key0;
key_u32vec[1] = key1;
mask
}
Android 中级
用 Golang 写的解码程序。
package main
import (
"bytes"
"crypto/aes"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os"
)
func main() {
image1, err := os.ReadFile("aes.png")
if err != nil {
panic(err)
return
}
image2, err := base64.StdEncoding.DecodeString(string(image1))
if err != nil {
return
}
err = os.WriteFile("aes2.png", image2, 0644)
if err != nil {
panic(err)
return
}
key1 := []byte("|wfkuqokj4548366")
key2 := []byte{0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe, 0xfb, 0xfe}
key := []byte{}
for i, b := range key1 {
key = append(key, b+key2[i])
}
fmt.Println(string(key))
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
return
}
image3 := make([]byte, len(image2))
for i := 0; i < len(image2); i += block.BlockSize() {
block.Decrypt(image3[i:i+block.BlockSize()], image2[i:i+block.BlockSize()])
}
image3 = image3[:(len(image3) - int(image3[len(image3)-1]))]
err = os.WriteFile("aes3.png", image3, 0644)
if err != nil {
panic(err)
return
}
decoder := hex.NewDecoder(bytes.NewReader(image3))
f, err := os.OpenFile("aes4.png", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
return
}
_, err = io.Copy(f, decoder)
if err != nil {
panic(err)
return
}
}
关于 Android 调试的一点有趣的事
jadx-gui 和 ida 真的太牛逼了,Android 简单和中等题我根本都没安装过 apk,也没装 Android Studio,直接看 java 或者 so 代码就行。
Android 高级题我就不会了,ida 反编译出错,我自己也不会 arm64 指令集,指令都不认识。
想过利用 https://crates.io/crates/jni-sys 自己实现一下 JavaVM 的 RegisterNatives 接口,放在安卓手机上跑,JNI_OnLoad 确实可以正常调用,但是再往下就麻烦了,动不动就 Segment fault。感觉工作量太大了,就算了。而且就算找到了 RegisterNatives 函数的位置,ida 依然没法反编译,我依然看不懂指令。
这时我已经装了 ndk 和 platform-tools(这两个下载解压就行),但是仍然不想装 Android Studio。
后来看别人的题解才知道有 unidbg 这种东西。估计调试量也不小。