Polar Reverse(中等)

JunkCode

就一处花指令,比较简单

瞬间看出是XOR算法,用厨子写出即可

RevMethod

看到这个函数蒙了,不知道哪个是真正的flag了。

再次参考WPPolar靶场reverse方向通关题解-CSDN博客

又是没看仔细,这个比的是不同数组的值,我们按照它的规则找出来即可

逆一下子

利用ResourceHack修改,让flag按钮可以被点击即可

点击flag出现,c68d-d262-5b91-2541-1ba7-1f3d-8103-41c4,将中间的连接号去除就是flag

可以为师

和上一题一样的思路

第二行在前,第一行在后拼接即可

左右为难

写出脚本提取迷宫即可,是16列

最终路线:sssssdddddwwaawwdddwddsssddwwddssdss

maze='00000000000000000@00000111000000010011110101110001001000010101000100111001110110010000100000001001111110000000$00000000000000000'
sum=0
for i in maze:
    print(i,end='')
    sum+=1
    if sum == 16:
        sum = 0
        print('\n')

混淆Code?

第一处加密:

第二处:

这一处就是混淆人的,动调可以发现只是初始化无意义的值。

后面的函数里面的字符串直接md5即可,可惜我的草稿丢失了,没有配图,真是遗憾。

Java_Tools

用jadx看

main类:

package main.java;

import java.util.Scanner;

/* loaded from: Java_project.jar:main/java/Test.class */
public class Test {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.println("Welcome to Polar_Ctf!,come to play!");
        System.out.println("Please Input : ");
        String name = in.next();
        char[] Strings = name.toCharArray();
        Tools.Add_1(Strings, 3);
        Tools.Re(Strings);
        Tools.Judge(Strings);
    }
}

先调用Add1,然后是Re,Judge就是判断

package main.java;

import java.util.ArrayList;

/* loaded from: Java_project.jar:main/java/Tools.class */
public class Tools {
    public static int j = 6;

    public static void Re(char[] str) {
        for (int i = 0; i < (str.length / 2) - 1; i++) {
            char temp = str[i];
            str[i] = str[(str.length - i) - 1];
            str[(str.length - i) - 1] = temp;
        }
    }

    public static void Xor(char[] str) {
        for (int i = 0; i < str.length; i++) {
            str[i] = (char) (str[i] ^ j);
        }
    }

    public static void Add_1(char[] str, int x) {
        for (int i = 0; i < str.length; i++) {
            str[i] = (char) (str[i] + x);
        }
    }

    public static void Judge(char[] str) {
        ArrayList<Character> Result = new ArrayList<>();
        ArrayList<Character> Flag = new ArrayList<>();
        for (char c : str) {
            Character i = Character.valueOf(c);
            Result.add(Character.valueOf(i.charValue()));
        }
        String sttr = new String(str);
        if ("$gourZroohK".contains(sttr)) {
            System.out.println("You Are Right!MD5!");
        } else {
            System.out.println("You Are Wrong! please try it again!");
        }
        char[] Strings = "$gourZroohK".toCharArray();
        for (char c2 : Strings) {
            Flag.add(Character.valueOf(c2));
        }
        if (Result.equals(Flag)) {
            System.out.println("You Are Right!MD5!");
        } else {
            System.out.println("You Are Wrong! please try it again!");
        }
    }
}

分析judge函数可知,$gourZroohK是密文。Add是每个字符ASCII+3.Re应该是反转数组的意思

我们先反转再-3即可。

a='$gourZroohK'
b=''
c=a[::-1]
for i in c:
    b+=chr(ord(i)-3)
print(b)

PY_RE

给了两个PY文件,能清晰地看到judge函数的逻辑,即可清晰地知道密文的值,接下来就是主要看加密函数

这个是主要的函数:
 

 for dict in Dict:
            if Input_Str[i] == str(dict):
                Input_Str[i] = Dict[dict]
                break

遍历字典的键,如果输入的数组有它,就把输入数组的换成键对应的值

简单来说,就是通过这样一个字典,实现了字符替换的加密,我们只需要根据字典,反带回去即可

根据思路,让豆包完善我的脚本如下:
 

# 构建字典:A-Z 对应 26-1
key = 'A'
value = 26
Dict = {}
for i in range(1, 27):
    Dict[key] = value  # 直接赋值更简洁,setdefault在此场景没必要
    key = chr(ord(key) + 1)
    value -= 1
print("原字典:", Dict)  # 输出 {'A':26, 'B':25, ..., 'Z':1}

# 反转字典:26-1 对应 A-Z
mydict = {v: k for k, v in Dict.items()}  # 用字典推导式更简洁
print("反转字典:", mydict)  # 输出 {26:'A', 25:'B', ..., 1:'Z'}

FLAG = ['H', 'E', 'L', 'L', 'O', '_', '_', 11, 2, 7, 19, 12, 13]
# 修正1:将前7个字符(除'_'外)转换为对应的数值(原代码用==是判断,而非赋值)
for i in range(7):
    if FLAG[i] != '_':  # 只转换字母,跳过'_'
        FLAG[i] = Dict[FLAG[i]]  # 'H'对应的值是19(因为H是第8个字母,26-7=19)

flag = ''
for item in FLAG:
    # 修正2:循环逻辑错误,原代码会对每个未匹配项添加'_',导致重复
    if item in mydict:
        flag += mydict[item]
    else:
        flag += '_'  # 只在完全未匹配时添加一个'_'

print("最终FLAG:", flag)  # 输出:HELLO_PYTHON

最后md5下即可

二层防御

先UPX脱壳

主函数很好看:
密文是allo_PWN n

int __fastcall main(int argc, const char **argv, const char **envp)
{
  _main();
  puts("Input :");
  gets(input);
  Check_Length();
  Strlen();
  Reverse(len);
  check();
  return 0;
}
__int64 __fastcall Reverse(int len)
{
  char v2; // [rsp+2Bh] [rbp-5h]
  int i; // [rsp+2Ch] [rbp-4h]

  for ( i = 1; len / 2 > i; ++i )
  {
    v2 = input[i];
    input[i] = input[len - i - 1];
    input[len - i - 1] = v2;
  }
  return Xor_8();
}
__int64 Xor_8(void)
{
  __int64 result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  v1 = 1;
  x1 = j;
  while ( 1 )
  {
    result = (len - 1);
    if ( result == v1 )
      break;
    input[v1] ^= x1;
    --input[v1++];
  }
  return result;
}

需要注意XOR_8函数的范围,首尾是没有的!

最后MD5一下即可

a='allo_PWN n'
b=''
for i in a[1:len(a)-1]:
    b+=chr((ord(i)+1)^8)
c='a'+b[::-1]+'n'
print(c)

猜猜我在哪

经过反编译,key取值只有01234五种情况,而且是一个凯撒加密

随波逐流秒了

  

易位

这道题没有思路了!进去后也没有明显提示和字符串,看看题解吧

原来是这里有一个隐藏函数:
  

san和yi这两个函数运行都是乱码

  

看题目设置的大小,memset设置的比原始数组大小大,结合WP知道了是交叉解,获得第一部分:在这个变迁的

第二部分如下图:

我这里少一个“世界”,估计是粘贴代码的时候的问题。

只取正确的编码,在这个变迁的世界仍有长存之物md5下即可

EasyGo

Go语言逆向确实不会,看官方WP

看了官网WP知道了,抓主要过程

// main.encode
__int64 main_encode()
{
  unsigned __int64 i; // rax
  __int64 v1; // rax
  unsigned __int64 i_1; // [rsp+0h] [rbp-20h]
  __int128 v4; // [rsp+8h] [rbp-18h] BYREF

  for ( i = 0; (__int64)i < 16; i = i_1 + 1 )
  {
    if ( i >= ::i )
      runtime_panicIndex();
    *((_DWORD *)main_flag + i) += 2;
    if ( i >= ::i )
      runtime_panicIndex();
    *((_DWORD *)main_flag + i) ^= 3u;
    v4 = 0;
    if ( i >= ::i )
      runtime_panicIndex();
    i_1 = i;
    v1 = runtime_convT32(0);
    *(_QWORD *)&v4 = &RTYPE_int32;
    *((_QWORD *)&v4 + 1) = v1;
    fmt_Fprint(go_itab__ptr_os_File_comma_io_Writer, os_Stdout, &v4, 1, 1);
  }
  return fmt_Fprintln(go_itab__ptr_os_File_comma_io_Writer, os_Stdout, 0, 0, 0);
}

所以我们主要就是看加密过程,不看Index(),因为runtime.panicIndex()Go 语言运行时(runtime)系统中的一个内部函数,专门用于处理数组、切片和字符串的索引越界(index out of range)错误

所以大概思路就是先+=2再异或3,这里要注意这是加密逻辑,我们提取的数据是加密之前的,所以我们应该对数据进行同样操作

再看看加密前的数据在哪里

这就像一个指针一样,存着数据地址,双击进去导出即可

下面是解题脚本

a = [0x6a, 0x69, 0x71, 0x6e, 0x6e, 0x6b, 0x73, 0x73, 0x67, 0x68, 0x77, 0x69, 0x6b, 0x6a, 0x68, 0x67]
b=''
for i in a:
    b+=chr((i+2)^3)
print(b)

c2

大概算法我经过边变量改名字放在下面了,恐怕是比较易懂的:

int __fastcall main_0(int argc, const char **argv, const char **envp)
{
  char *v3; // rdi
  __int64 i; // rcx
  char v6; // [rsp+20h] [rbp+0h] BYREF
  char flag[144]; // [rsp+30h] [rbp+10h] BYREF
  char buf[144]; // [rsp+C0h] [rbp+A0h] BYREF
  char Buffer[136]; // [rsp+150h] [rbp+130h] BYREF
  char hefklijcda[56]; // [rsp+1D8h] [rbp+1B8h] BYREF
  char choose[564]; // [rsp+210h] [rbp+1F0h] BYREF
  int j; // [rsp+444h] [rbp+424h]
  int k; // [rsp+464h] [rbp+444h]
  __int64 j_1; // [rsp+6B8h] [rbp+698h]

  v3 = &v6;
  for ( i = 284; i; --i )
  {
    *(_DWORD *)v3 = -858993460;
    v3 += 4;
  }
  j___CheckForDebuggerJustMyCode((__int64)&unk_140023016, (__int64)argv, (__int64)envp);
  memset(flag, 0, 0x64u);
  memset(buf, 0, 0x64u);
  memset(Buffer, 0, 0x64u);
  strcpy(hefklijcda, "hefklijcda");
  memset(choose, 0, 0x64u);
  memset(&choose[144], 0, 0x64u);
  memset(&choose[288], 0, 0x64u);
  memset(&choose[432], 0, 0x64u);
  scanf("%s", flag);
  printf("紧接着你想让字符串每一位都与10异或吗?(yes/no)\n");
  scanf("%s", choose);
  if ( j_strcmp(choose, "yes") )
  {
    printf("异或都不行?罢了罢了\n");
    exit(0);
  }
  printf("异或成功");
  for ( j = 0; ; ++j )
  {
    j_1 = j;
    if ( j >= j_strlen(flag) )
      break;
    buf[j] = flag[j] & 127 ^ 0xA;
  }
  printf("紧接着你想让异或后的字符串每一位都减3吗吗?(yes/no)\n");
  scanf("%s", choose);
  if ( j_strcmp(choose, "yes") )
  {
    printf("切~~~~~\n");
    exit(0);
  }
  printf("减10成功\n");
  for ( k = 0; ; ++k )
  {
    j_1 = k;
    if ( k >= j_strlen(buf) )
      break;
    Buffer[k] = (buf[k] & 127) - 3;
  }
  puts(Buffer);
  if ( !j_strcmp(hefklijcda, Buffer) )
    printf("ok,flag is this\n");
  else
    printf(&Format_);
  return 0;
}

写出代码如下:

a='hefklijcda'
b=''
for i in a:
    b+=chr((ord(i)+3)^10)
print(b)

RE_jar

查看main函数,实际上实现了一个加密的逻辑,在这里可以看出有可能是AES,不管怎么说先看生成key函数部分

先java运行这部分代码,获得最终的密码:9FQxXBEE2GCG1Q+AzwVvZA==

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class Main {
    public static void main(String[] args) throws InvalidKeySpecException, NoSuchAlgorithmException {
        byte[] salt = { 1, 35, 69, 103, -119, -85, -51, -17 };
        PBEKeySpec spec = new PBEKeySpec("PolarD&N CTF".toCharArray(), salt, 10000, 128);
        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        SecretKey secretKey = secretKeyFactory.generateSecret(spec);
        System.out.println(Base64.getEncoder().encodeToString(secretKey.getEncoded()));
    }
}

这个代码逻辑就是先AES加密,然后将数值填入enc二进制文件之中,虽然JADX反编译失败了,但是大概逻辑还是能看懂,JEB可反编译成功

iv就是前16字节,cyberchef秒了/

语言不通禁止入内

给了一个ida反编译后复制的txt

我们首先搜索flag,给了提示是小端序和简单变换

搜main是不行的,我们搜main()才能定位(看官方wp学到的)

int main()
.text:004015F9                 public _main
.text:004015F9 _main           proc near               ; CODE XREF: ___tmainCRTStartup+221↑p
.text:004015F9
.text:004015F9 str             = byte ptr -12h
.text:004015F9
.text:004015F9 ; __unwind {
.text:004015F9                 push    ebp
.text:004015FA                 mov     ebp, esp
.text:004015FC                 and     esp, 0FFFFFFF0h
.text:004015FF                 sub     esp, 30h
.text:00401602                 call    ___main
.text:00401607                 mov     dword ptr [esp+1Eh], 6462607Eh
.text:0040160F                 mov     dword ptr [esp+22h], 64607569h
.text:00401617                 mov     dword ptr [esp+26h], 72726463h
.text:0040161F                 mov     dword ptr [esp+2Ah], 6B656860h
.text:00401627                 mov     word ptr [esp+2Eh], '|'
.text:0040162E                 lea     eax, [esp+1Eh]
.text:00401632                 mov     [esp+4], eax
.text:00401636                 mov     dword ptr [esp], offset __format ; "Original string: %s\n"
.text:0040163D                 call    __Z6printfPKcz  ; printf(char const*,...)
.text:00401642                 lea     eax, [esp+30h+str]
.text:00401646                 mov     [esp], eax      ; str
.text:00401649                 call    __Z13processStringPc ; processString(char *)
.text:0040164E                 lea     eax, [esp+1Eh]
.text:00401652                 mov     [esp+4], eax
.text:00401656                 mov     dword ptr [esp], offset aProcessedStrin ; "Processed string: %s\n"
.text:0040165D                 call    __Z6printfPKcz  ; printf(char const*,...)
.text:00401662                 mov     eax, 0
.text:00401667                 leave
.text:00401668                 retn
.text:00401668 ; } // starts at 4015F9
.text:00401668 _main           endp

大概看了一下,是将一串字符串(在这里是数组的形式)给了processString函数,该函数如下:

 void __cdecl processString(char *str)
.text:004015D0                 public __Z13processStringPc
.text:004015D0 __Z13processStringPc proc near          ; CODE XREF: _main+50↓p
.text:004015D0
.text:004015D0 str             = dword ptr  8
.text:004015D0
.text:004015D0 ; __unwind {
.text:004015D0                 push    ebp
.text:004015D1                 mov     ebp, esp
.text:004015D3
.text:004015D3 loc_4015D3:                             ; CODE XREF: processString(char *)+24↓j
.text:004015D3                 mov     eax, [ebp+str]
.text:004015D6                 movzx   eax, byte ptr [eax]
.text:004015D9                 test    al, al
.text:004015DB                 jz      short loc_4015F6
.text:004015DD                 mov     eax, [ebp+str]
.text:004015E0                 movzx   eax, byte ptr [eax]
.text:004015E3                 xor     eax, 6
.text:004015E6                 sub     eax, 1
.text:004015E9                 mov     edx, eax
.text:004015EB                 mov     eax, [ebp+str]
.text:004015EE                 mov     [eax], dl
.text:004015F0                 add     [ebp+str], 1
.text:004015F4                 jmp     short loc_4015D3
.text:004015F6 ; ---------------------------------------------------------------------------
.text:004015F6
.text:004015F6 loc_4015F6:                             ; CODE XREF: processString(char *)+B↑j
.text:004015F6                 nop
.text:004015F7                 pop     ebp
.text:004015F8                 retn
.text:004015F8 ; } // starts at 4015D0
.text:004015F8 __Z13processStringPc endp

整个加密逻辑很简单,先异或6再减去1.

先看第一段加密:

第一个for循环意思是反转字符串

第二个则是对每一个a1的ASCII增加了j+1

再来看第二段加密:

void __fastcall sub_140011820(const char *input, __int64 num, __int64 cipher)
{
  int Size; // [rsp+24h] [rbp+4h]
  _QWORD *Block; // [rsp+48h] [rbp+28h]
  int i; // [rsp+64h] [rbp+44h]
  int v6; // [rsp+84h] [rbp+64h]
  int v7; // [rsp+A4h] [rbp+84h]
  int j; // [rsp+C4h] [rbp+A4h]
  int v9; // [rsp+E4h] [rbp+C4h]
  int k; // [rsp+104h] [rbp+E4h]
  int m; // [rsp+124h] [rbp+104h]
  int n; // [rsp+144h] [rbp+124h]
  int i_1; // [rsp+248h] [rbp+228h]

  i_1 = num;
  j___CheckForDebuggerJustMyCode(&unk_140024014, num, cipher);
  Size = j_strlen(input);
  Block = malloc(saturated_mul(i_1, 8u));
  for ( i = 0; i < i_1; ++i )
  {
    Block[i] = malloc(Size);
    j_memset(Block[i], 0, Size);
  }
  v6 = 0;
  v7 = 1;
  for ( j = 0; j < Size; ++j )
  {
    *(Block[v6] + j) = input[j];
    if ( v6 )
    {
      if ( v6 == i_1 - 1 )
        v7 = -1;
    }
    else
    {
      v7 = 1;
    }
    v6 += v7;
  }
  v9 = 0;
  for ( k = 0; k < i_1; ++k )
  {
    for ( m = 0; m < Size; ++m )
    {
      if ( *(Block[k] + m) )
        *(cipher + v9++) = *(Block[k] + m);
    }
  }
  *(cipher + v9) = 0;
  for ( n = 0; n < i_1; ++n )
    free(Block[n]);
  free(Block);
}

关键的也就在这里。这是一个栅栏解密

根据栅栏解密的脚本改了一个解题脚本:

def decrypto(cipher,num):
    '''
    栅栏解密函数,cipher是密文,num是行数
    '''
    if num<=1:
        return cipher
    length=len(cipher)
    row_counts=[0]*num
    current_row=0
    direct=1
    for _ in range(length):
        row_counts[current_row]+=1
        current_row+=direct
        if current_row == 0 or current_row == num-1:
            direct*=-1
    row_chars=[]
    begin=0
    for count in row_counts:
        row_chars.append(cipher[begin:begin+count])
        begin+=count
    plain_text=[]
    point=[0]*num
    current_row=0
    direct=1
    for _ in range(length):
        plain_text.append(row_chars[current_row][point[current_row]])
        point[current_row]+=1
        current_row+=direct
        if current_row == 0 or current_row == num-1:
            direct*=-1
    return ''.join(plain_text)
if __name__ == '__main__':
    cipher='\"hwGwg88Y'
    for i in range(30):
        print('num='+str(i)+':',end='')
        num=i
        str1=decrypto(cipher,num)
        flag=''
        for j in range(len(str1)):
            flag+=chr(ord(str1[j])-j-1)
        flag1=reversed(flag)
        print(''.join(flag1),end='\n')

最终结果应该是P01arCtf!

delf

看加密算法,应该是先异或,再让相邻两字节的进行交换

with open('encrypted.bin', 'rb') as en, open('decrypt.txt', 'wb') as decrypt:
    cipher = en.read()
    cipher = bytearray(cipher)
    for i in range(0, len(cipher) - 1, 2):
        cipher[i], cipher[i+1] = cipher[i+1], cipher[i]  # 更简洁的交换方式
    flag = bytes(b ^ 0x5A for b in cipher)
    decrypt.write(flag)

MD5进行加密结果即可

aomo

这是一个很大的文件,而且没有明显的字符的提示

binwalk提取出一个zip文件,里面有个py打包的exe文件,提取出来反编译:
 

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: d.py
# Bytecode version: 3.8.0rc1+ (3413)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

from cryptography.fernet import Fernet
import sys
encrypted_pet_e_content = b'gAAAAABnk1_D3rOa0vXjaNpRLdkKeWZpTqAznzhpezC5MRuvD5leDMo6PR-VJik8IoMuZ6_iG322CO6hCR6sT26M73VUldMlRNm2s4e-EoXbRUIVvAtDAjIdUVfa2-fl8ekGWirzfTC7BthxY2C72wo5vxKVLkrKXtz106Cog9T3TBWYjcZeQICigmzX3KXDcDXclgiQcCnJjo-yCKpm78dheBx0YMawh0995ar8XAFeJ8bYL9JeEHQeCQ_9wbWTMf-Wa6QHLAtajC8tIY94AMQ5ZtlzMRfABK1f3wlUCIDu33YDWPRPF0wCL2_qlrisMJrhnlR782DPQZJvuBH4hEvijry2AXxENdz2pqlpIKKCUoOLStCQFdWOq_BjTEM9-lHQzSBfMAM3HSbR_UJvNQ74CTXe3ecxPP3zAt4ChRmV_1SmtedjZUnTEqplQhRXJipqJ5IJPTP0h4-dzACC8UOHTmL7GrpZJYtg4WluMgMcz-1WialbDDR_Yq0Q5SaKthysK8zPPubPOlk__KrgQmkXNfy0bnEexUNgSji09fk2Tgsvaq_WB0qQLckwIAIwUkkfJRv5tGo9-S5Eltf7CIrg1AM3vytTwO5jjxt2WKhY4p9JV8kPJItqB14JPhtD9gGH2c4tBmz938Us7p0pqNLHY0M2eKwATPR5ks7-kKiinjM5IdYvid_g5afLJCJjrmlUM6ypaK5NbpGSk0lOZUrbXqrdfOoxeCRPyaC6ZboCJ01nRcA9igcsWNVderOwQ3iNlmb17GZOzXa8QK0gMgGOXESRR3COaZRNnxQzsAIt-4wIVPH-C5cb-88FmGrGEYVaIWAHQ8IQsCOh4BWY0mnalKA_fUgWmLgXitPAU8rgkLUQKGxEABGmCwhiuSd_qZnX7vXinQsmapGGqVs_xhy7K_uQdCdgXDGFlh5xZDVZrKJvmYqlmOe8Ewgdo7J28_8iOF6fTuRM6JebqLFEkzia7b0k5_tYWQvB0fRzOIXM5QdRfWu8bJScOl-oiNgrtbG1Um7iy2l5oOiexkjpbmwxFGped54MsLo4s7DjwhkMD5Jj6Oxa9By54UpACtDs9TqwKMOfMvZlAk_bXMFqQq6tnCMdGXkH4pQ2c25Y_RKZfd4CBbJuQT_ezL0vdr6NyGeQaJ3_UZQnryjrRCciamaCJj6HjgsL4nuEfBYxE5vAi3tmF_CYS-d0Yj1bIVXsdt5I5-Fp1ua8sXiU-nUjh7N0yNvvbt_PZm7xoH-_NJzljDPBPWYGd0aFz783EuKfwZ4aaTlRXOU1D5g-WUCHNukZzOjmgvZEYuwB_txKCZSISBs99rYrd6wyKFHEz4h1aqipNn1LxOoOZhRgyOOunkbUcvmw7GndcikZtRm2L8CQJkKcAZQ7UYSpJlfGLdDhdf7banDuNot2dr78lab9isGUtkTfo120bYuCngUyTVuq314gt7LSkr0P8tI1kmkyTmP8aKSSIyEX0uQAJiJBfg-Q9xgbsBgDomM4ukouChM4__C41CD4tnKRCXJePgys5Pb7VpIxovxNG-jbqLsR6uVeefHVUaxCDRZvV7AelW3D5ccmCtFiC53Qb47uPPk5ppDbX7Us3LAPAoOhqY-mObazVWIur4NpgZjA7aecn6Sp56PCXj_a46zbigAKGrWpkItbpo2WYRs9Cw0mf4d773NvaRrwotTOpeOXux1DHsR5htOSvMNUUoBeFQ0bUxK4Vb_eLp9fOWaSi_t8prHAGuSWqVP5brRo99olWzcqMrI4gCl20qH7e2-e-imaF0iTj9gS2SiQYWqqI3omo31YIRtvf7dp6FxfwuZzqO4rn9nUBjlKegSBx_eVSXv2xY2Hvnp_squZ_V1A01hcx4OW_GWLQ0Bi3JawMDykgaRvKz331w9gGDxdMD1zgzylKi3-0sUdgw9t8BKpoBFz8xY_K4lC2fXRQRRYW1rk4HqxWuFYVtd-yeg_SBE1wBxRTT2X9NqP0d8CauS3LmAwV8ITadkN2rTR5EJKIdXBdfWTZsrMzjpvRlsJvTc_ceqIhqzd6156RWe5dZE1rfJ2OhYTyyOJNmHfQjlfNJndoPM0Dp1hN-uCxr5hut657MnEoJezdMI5wEkUz3bwjBu8Fl4DVf-F3rQoE1N5InPd2cAw-xR884itQR845V_vG1Lp6c6gP1x0ymDLMleVHCaVy4lkf-jR1AG7pQZIlFm01RDTstRw9V92x4B3q0fODlOguRHls7oBquDFImblRTS7zqUur47G43pKsuScShx0iCYV_LxrIySXBFF76VnP6vC8JUAeiinr1vD3vF1-fhjLh-4LJzuPWywNoKd4bqGIOvobvi_EW4hQq0rTa1be1G9H3Zl5NkUK2GZJe-nhU14vGJUJc37DkwsDiWHYSuI4bFdu5MHOqA-0dCt7CT3C4ADtKHecUxu2COaSSngte09V_OgE5IcdtUlUpanMkFlpe8QEaOYR-HhavdWUaZDeC_kUjr3j7u5Prn5TssFt0LKUO1UtCzoIkaZWJb3sQRphQYbNHvq-l1x25pIvb7sbGixNldrndNLbs9hp9rRNz-qVR4rptpLnOyLoUptit1WWzeGZS6-W4X3gTBKP1zC6Ri5xZV6zvw2TC48kSsajQCQbHgmVSkvKu3ObbZ5AoxxfuhFbk7rxWUUBOVm5MqqhTPEf2c9qtuyrTZ3D28Szr8vzdFgRyDPxQlGu5zP4rUevvPN3ZbCI8gPceypy1uchOYigVVTQ9molzz-qOXHX3AvSQ12-zwfMsHe4WIgR1mL-uwgZuRKGfbktdF5OjXUuFGkbGhHjL_OJmrZcJkIHZGwkfyCQCa7H0r9lfOf89XGnb9JbzD7k28rt3eJpJlJ4UqYGU_uUwHpgc2pqGRnzruwbxOqxGRfIuGOjSs2gQELGFSUbGvEtXmTxVaiTQ5mNgfHwyD4AB63-1_TOpdezkkLVi7L1Ho6dasmPBAX9YbvSvjF5hDPmCAi9TvE9Ht49kJ9OyWaSmq_N8iPptFUABXZi5NjpEiVT59HlTBG4agozvGF7A778l02omLxwYy7iLO_3PssUowexprChrKloMMNYgxcK_X6szRXlmftPsFRnGD8M2aD6i-grF8bRprKWO6SJGxH5OzinSnGt1XacUhxjJ42TQzwvGoP6hLcTr6mkplflPT75YEmuOmNqIyvT0mZzE8zF2Ipl-BtBCuQ3KHd326XQsmz4V-9MCNwVgt7UQp2Znh7a25vvtTh9sjmq0PJktVS80XbkI2kbc_0SbjlTc8VyWWe72T5RhkmYcxA8klGg2TD3plwyiKUzlBHw2cq8TxhnoSX7-K5j8sj7CJH2JOxnvgupG_QrlsqS6as_JnI8qjuw_3w76cZ3DfcDzd9qjhSmSN-SrRbhx8Xi21bW6Ac3BxkvVQvYi97w4wZGtEFisgd9QcgoeCbtYch45PtxaVq5kKs9m7B9nXsf3vKgqwWDghlZ6l2-U4dx8qQQ4-enIHquQkbLjtAgp9zhUxC43Z0LYLCWUxZ2pRzaQQh2JTzOhv2JOYw35Qzma91pqIKIv9-xLq9T2QIMFzcZez7YHyP98mxz5RTWUeGh6Y9Fw9jgpZGTWA-4bq6v3PMxJX49cq4MibfRGfJlO8cOsbV0rwJaTSILAnbLarBvIdUOymzQU8iQLvLZU0daKQvGphKP2WNlDDZjW4Vjr-pnM6IpzosYEl49G2TolNZZyV4Sn4BLn1kln_wLUfXxjv1nMwWqpry-q7_pZmSf08Sy2yPSFgw_heNZr7lq7WHcnHdHVuVC-fqOHk-D1qSpFUYbUVviq0J63kKDAw2chV2JHjiufjjIOrXF9-VWNrrXGNytuSBrT1HsymvX22pcIfU-7XJ7x_3c3XA28BrIdwko1Ej6jDzTiRSR39UDE1qbuEE-aiKNXnTuhPKhfi8sQAwi0NjiBCWsGzFJd0NAyTOusmaEwqRHpYr7GlFe5odJSFDmmwCZv4rIGf5SxdGzHGph6xvosB0DRB7dNVybZ9Xvb9UQOEpvRQf6dkNUjuU9D3TYXibxymtMX-LTtREkOWY3qqK1cTH1Ho8sZc7-FabtPAW8Y0vc9O8YgHrrxCdq565us2BxcFmRaiAiQkUDFybSxiUlE9UYCJi_TqU4RJyfRo5RTvLCxsC4uVzyeRIxRSoMr9h3tCz0LnO2asKOB-c4TKTUGozo0hpmOqn5mlP0Kgf9r9wO5xvSstVPVRGerdnz831JQ8pdVZw_TumjTEQXFv6d2U2zkeCKlAIUfFpplcZs3Oty_0wA4Jc2nbgC1olPly0a-qtUweuHtAWTElrW9v06p28cIs_llf7GV6-X9Iydf3cnlBXaInpyQ__dt6N_t46dV5qiPi0tp22dwBDaV9jTnXGHuEzot3HS2XMEo3O2VRlWt3rxRq30tVzAB9JoAKaW2gzxhRaoaotXYVPULhUK_Cs-UtNTcsWr1JlXUIOqA644E6xO01-qiYzbXC13Uy6nWgGQQw7LdmuGBWW6-n3qsYRgr9SJCUqkXs4ttUIO0B5vvZWAXuMEAQMA5n5xtVqZC33cRnadv82LjaTenLdgkRw4M14Bca7GRrckOUI0O6ZbVXv660AIxnngsKHo4tSXVKT7LpaAczp-m18L7jKkGd3eJcC7FnCkJNu6ZfAwy2VZpP6abQyfGpYy7J7jpXUNoOQJbaIQxfglkW-DUaAXS4SVsdSBr_MmaTyajrBNVCcyPXiIHpbUPkrQ7ez6GOY70PrfjYqV-WgatmrzbtiFzzz4SpoBbIyafyhMNh1orcylNNpQVaUeCeIqumRGWAtTdl1wLQWwU8T6t8ryQnsvu4ZR-rcwGLokc1yiDBj0iWKZQCqhY_Zta_R6_e7nmT1eoqu6Y9f4BeLGYAp9S_5iOVn_7da28prh0-kyU8OpFVRIlq7wcFQqtpsUrgMd9oEwjbl_InZh-CzSMMxHx5kGZWQRAAvOFMX84n4epW82TiBGT130WS6cF0oZ3kAv0vFD5IZyEuIfAhAemdLdtaTNlUQcH18EtFs35JL43_pZXXL-9S-nXtw8LvyrLjFfSm8fwTrAtznc6rpCZsJ6nuw6mz3fQmu8btMbAYn-Lc-WDuY08fmD7lWgdbBFzbASIXEL9b3C-7VXa9Cy7O0F0bERbFRkPTTfv0Ay-XSgxo-FzpiOLs3-1bgcV0VPD799xAjWVRZ5zWxw4VOfvWUElfIx43CUP0-nHOseIOKpwbgY7d53WWeuA8_vT6HPitsWcPOgOCtAvJDVNH6naNKCYF_N8-L8miNKiGr7TdXlfMmlxHYlF6aGMNcbfIEVfh4pSFpeaXSZMn_YAhIPvVGcYIodRHaIZTDXQoto7KIDzh_X3RcGIRD_LIZjXWAQlzZ3OyHi8sTdbd3iVWt0WmdAzfy96s7IUmhdamYFL-YAMwOzqtxEHYZ9O8hopgdF6lFqUrdix_lQPry-4b9mTgoYb3aIGlyekOQ-LnazseZWBfoDxPqhxyfg1Sqrd3cJ15LIeoDbg0UNp7996n7vX1itnk1cbJ63Ek7QDT2yCyX-b5Gn3swXbmsYqh_x8Wn3c3ItgA5qMPsYVTVjNhS8u3W164lxfW83jtlFU3n0kswGGT2KGMETW7p_Wmq43_9c0RMiilALz9dZRdSuKUuMgQcFi_7kIMhb5AsFK9ookOWBGFLND5GfZ5XcGmI3XwuKE3qqmyYOf272vcUfnGLNejFv6P87c4dUBD7JMLEBA_dBpqIuGKld6E2hpx53fTic-upnlvjYfbUq3tUC5AO9W28lPiKYDkQ9_DpInp-8Y9T6QIpOfDEsktFrxZy2epCuZvDlM0mK653UuaTbiXnez73Hft5UZJc3I9ASU5xFDncfQO0jcoA9zwcxvklnyFtM7Mq_pMoBrEv5YCGl9D9DeGclE2_mXt-AHvHYvLx5LUPJLcKyXwNzu4GkYkJRL6WUuOzlp8G9wMfXPPp8HBLWf-8qvAydktv6Jwvb0w1gh6ApmeV6H4IK7Qf8MwYv7q4azNZb-mgyMVgOywyydL2Jw-Eh83qF0eCQwvUBB1V7bqBNPivL9Bc6YIipMYY7pJrAwodlBw03iyFzuEIBKIMdPmqm_XbQgFX6kgw_fWr2PtC0MMwmj2NJ6tBw8-s9kxO3z3P1wQk7nuq2Secl0ZlnqpFmrJ2dz5cln8ltHYHv6ZC_z-2fY4fBSvvOuGpsDy5konbDpZMuYZUg9xQdOvpWUb5N9Neh4eq5Fhv9rgQQpnB7esxmp6w2dtptrWCN0ri1m_fL7RUBZIG95LN3ioL9WztqNmjMSJ5nxfHUHNLHIm0nd1xOxGCC026G1RvoT-wIejYwZisW8sJ7rIC94MVnfFO94-pE5S6nOiXXUvem8nr7ZOL437LbFUWtkX864n760aj4lyEgzzvG6ugQKLqsJYr-SWGjjo6_g5GnrNgH5isJ4h3j-R3RW1UUXyaA0DYMuQq0VvRICA631K1oVUEXMQGgN-ZJD8OVwGNuR5csf5gHZyR-0y5_WPBjC6M7J1bZ_4TDnjW1MRVvVnSSGZc2g3mSQyq8PayQ7PypL2t9B2vCt6rz5RMlqTpR39545E6PSc1jp7goaiqJHsSQwkX4AfPP4E9dg0300e0z842T4oJughDohkrYdRrZb92qonCJSp2m3UybJpUGBQJp2MEvRAd_Q1XLTjp62i9TLQPSOZ-EofuZsAGKwQZ0IBQKBMqjpc6r-MS-F76_YvhoVGWHAXuNt_BL7-0whfTH_qWZK5v6M-4qXEosQnfMiJZpNwYfGOCNzbO2c6de4bjy0hw2bTy700erLTdPyW2R7DD-4A8FjIMwlSNOYKg7BlDsqQbPNcakWLVDiZ2iRQQlU3qHXRgaLe3WyhHGoHXcuNKs_Ebox01GQH3krsw3Jq8uECcCk3Abgrn2LUIJC3jm267zDuo7tNsYuf1oW35F-LWJXvsk2Fy-nuLlGBYwfqEpzAKhUyzsy3qGe1v5mfImolfL8uEMyhc0Ob5M3H-yxJEXjiZeh4M2Fm8gLgn4F-BilaoWu-LDggp-k1MOzdKBpvljM2jo12ELDtl1MElKabHcGlacHLRJoGsYOcChS8dhfzUiHnaKUtOtG8qKdHZBpyg6pvculBSOH1E8stJkHz2XCN58u3iWqpqhi0FlqTR4_On4DzONWw9y-uuDfbhRkVGzL1x4z5dc9WDgbVxUbcCNOP3Ai9oODg0HT4S2poDc7w_sSMl4dPWJbqDzFDhQ0jk27ki5bQobKl0WjXcK4OqXJnq1IQYbS9PVC7doM8rSeDWsBJm7awom4laT1bYknu7Yk1TehII9t7OSRLiZnNtD7IODvymskn2XZAqcVPtniZSNmXfSggbG_29q5myWZhoniPaH_nfWN63zXXFa_cdno9ldjoAPIttwaq-5UC3j84Ictsn36uVpnwQOnXhulm0oZGxaoAQ0b8v_CtqK7aLgqcmQfdFWPqrGYPEVV5yBpqJKWBDyUC1cgRsUAJJAz3M35v-Osx7Xt5K2hKUl-PJaf0qP9F9Zlcwobli_CgHVIRlsIr9_fWwzEFz8-ZmCLnn5xe1peu7-i46kUZ4z5cNuaucgnMf2wbBi4JHR9Rbt4e4byklv_B2Z0lg3b17hduncLgWAtRMSnWYcTwqEd1ziEQQMBFDsQvBY-nnGqDQR3vkR9Vmd93LQvgZeDAfSq4UQ4EPmWIkyXniY4rs-JQbhMA1PEPsUKs_5wJ2DBHOA_lUHHMqznKYeNZ_Jkhqoz_c5fLH3fPQLNbrcfKQieEc8U9chZQ3aTt0XaIZJNySc9jSX7ett3UDmMMYOrSmxLMnf_LqJtGPyO41vMPHnQiCPMboIZGqHiZEBLrfJY07qMMbrp8q-DtnXRIg7lxITv37_Vg6kyGeNNz733U358Kun4RP-ex03XptcfW3Axb0kfIp4U9DXmRM34MGxQNG4ebQHFZKljXwXqQAanI52-bFmX1It1w1E0CUCX1tv-gTzvNGurrlvzW4QW6MmZbMwTbbez3QjFuAHmJMg_q2m_3ll4xQqcbXeYSqcfAKAIrcfvF16MLUMyqGd3JGt8uMNXee9v2EnoB5hA2_EHQz1ueniNTQu2FgvndmlTaC-znvo1udLU-L6z9rMjees6uIP-5ykxOSHgUoQunh70u89iMooWtFbwJNfhB9EYJiBOh-uXIz9YEeZ3_jmXPlHTCSGCNAS6OUI-W_NvmKFU0HB0LuPB45cVcfsT6zfjrCbuT9pVlDKNr5rI0yabVGTMuUJqCzZy149JEpmFctvPo2Lek01YsfWWb9DEaJg2is9HtLg_ra40KlYc0E_KPhnxYyEFBQ7dBpfsQL3jFKsGsJA58h7-cerKHuNWYDqKyqS7XVpW7jv_Nu-ARJgRMuzy_UcJfYWmalEMMwjl6wauQ6Ge7h1dHPQHtOhPNAtGLmnmEvVnhKda-0hadn_LxkDo8iL6fS4DYHCA6l2rNtiHpFZVASLzDnvG4Ru2fvUW-ZodHN4OfApBBL3voWZzLMvJYXnn2a286Nt-mv6TTptuHAa5DQmEqrHBMte78owr6DY5shLsr4SfQ0oLimcrHKd-CLCFzRjfRW-107DV9GbhAbZE1pNdJzvK_QC8RYoL6M02L2XSp6eYYtejzC1EmisBtqDg-GroE3aY_Jsv8ce_D_rgt6dehQVNVnTonVNB3VMWS_0XAegkanOrVIEni9LPPIO78yu54eXfd1C-DkEjC0vn253LRx7r49h8SiS9v5U8UhHH1wovvTLQ61BuWshIuKBxeLffIHqwQnO12PRxeIebU8ABG2dGpxdBhz1qT0KXaYQQA98CmEorudPsMYckW4nZlU3_Ww7-65-z7CoyAHWhgn0JsSV5f7IN8yN-Dp6LOCjCeg8bPopeXs0wQ_53Jyat91I1oB1tXnRBFBKxaNRqtk9EbkA1wWvy7N5WDcNkXcry5Jz1Kmg6BRKhFbMG4ZHF1GVJo6S_PO6A075I_0XrYitkhRkDNWVXGBVzoRa6xmMApfhhTTNEUkGYCzc_4noWqqSS25VHI282-VphSyvdvPTJCraf8FSvYaLfTk2zN9flmqCLefGRsBtU27_ooH_pVOposGFJHnUQITIKzWwbBQ3vMq2DLCGdpYgbmDaPKFxLA8mf3zhAOWFXRGJlrZxFZn_UGxiPBnPv68FCoQxLc0YPSfLgQx9QDvZmWNPIigys3DSbZg8Hv29BBLSP4WckD2IisToYWVseHqFyJ5U9sfwkQzhvqe6G4N83QZyrUa18ihnDHRT0nB8SnKKQ05Sbt2J484X6dcvfehpmD2y1FeuqPzPvhzaZOghUvy7SRZkAmIpkAsvjHII6f2rcK_nlU1QsVt0fo1-oq30Uig45mIFZweZv30bPnZ5XAf3dIdDcOCXApqu8cUw1ScwQHhGo_gtZz9yWytrZgA5r4BNJb0hntc0XkcvtGutX1ycITWCeMUbpE0cjkAS_edtSZAAUSC7PWllaN4Fki5n__JzL9jaIJmu9Cki47m6MFCScbmCbUd9'
with open('7XdJjIn.txt', 'rb') as key_file:
    key = key_file.read()
f = Fernet(key)
decrypted_pet_e_content = f.decrypt(encrypted_pet_e_content)
decrypted_code = decrypted_pet_e_content.decode('utf-8')
exec(decrypted_code, {'__name__': '__main__', '__file__': 'pet_e.py', '__package__': None})

这是对一串代码的解密操作,我们改最后一行为print,得到原始代码:
 

from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QLineEdit, QPushButton, QVBoxLayout
import os
import random
import base64
import hashlib

class DesktopPet(QWidget):
    tool_name = '桌面宠物'

    def __init__(self, parent=None, **kwargs):
        super(DesktopPet, self).__init__(parent)
        self.initUI()

    def initUI(self):
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setAutoFillBackground(False)
        self.resize(300, 300)

        self.actions = {
            'shetou': ['aomo_6.png', 'aomo_5.png', 'aomo_4.png', 'aomo_3.png', 'aomo_2.png', 'aomo_1.png', 'aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_1.png','aomo_2.png', 'aomo_3.png', 'aomo_4.png', 'aomo_5.png', 'aomo_6.png'],
            'hello': ['aomo_11.png', 'aomo_12.png', 'aomo_13.png', 'aomo_14.png', 'aomo_15.png', 'aomo_16.png', 'aomo_17.png', 'aomo_18.png', 'aomo_19.png', 'aomo_20.png', 'aomo_21.png', 'aomo_22.png', 'aomo_23.png', 'aomo_24.png', 'aomo_25.png', 'aomo_26.png', 'aomo_27.png', 'aomo_28.png', 'aomo_29.png', 'aomo_30.png', 'aomo_31.png', 'aomo_32.png', 'aomo_33.png', 'aomo_34.png', 'aomo_35.png', 'aomo_36.png', 'aomo_37.png', 'aomo_38.png', 'aomo_16.png', 'aomo_15.png', 'aomo_14.png', 'aomo_13.png', 'aomo_12.png', 'aomo_11.png'],
            'ele': ['aomo_114.png', 'aomo_115.png', 'aomo_116.png', 'aomo_117.png', 'aomo_118.png', 'aomo_119.png', 'aomo_120.png', 'aomo_121.png', 'aomo_122.png', 'aomo_122.png', 'aomo_123.png', 'aomo_124.png', 'aomo_125.png', 'aomo_126.png', 'aomo_127.png', 'aomo_128.png', 'aomo_129.png', 'aomo_130.png', 'aomo_131.png', 'aomo_132.png', 'aomo_133.png', 'aomo_134.png', 'aomo_135.png', 'aomo_136.png', 'aomo_137.png', 'aomo_138.png', 'aomo_139.png', 'aomo_140.png', 'aomo_141.png', 'aomo_142.png', 'aomo_143.png', 'aomo_144.png', 'aomo_145.png', 'aomo_146.png', 'aomo_147.png', 'aomo_148.png', 'aomo_149.png', 'aomo_150.png', 'aomo_151.png', 'aomo_151.png', 'aomo_153.png', 'aomo_154.png', 'aomo_155.png', 'aomo_156.png', 'aomo_157.png'],
            'flag': ['aomo_drag_start_1.png', 'aomo_drag_start_2.png', 'aomo_drag_start_3.png', 'aomo_drag_start_4.png', 'aomo_drag_start_5.png', 'aomo_drag_start_6.png', 'aomo_drag_start_7.png', 'aomo_drag_start_8.png', 'aomo_drag_start_9.png', 'aomo_drag_start_10.png',"aomo_drop_7.png","aomo_drop_8.png","aomo_drop_9.png","aomo_drop_1.png","aomo_drop_2.png","aomo_drop_10.png","aomo_drop_11.png","aomo_drop_12.png","aomo_drop_13.png","aomo_drop_14.png","aomo_drop_15.png","aomo_drop_16.png","aomo_drop_4.png","aomo_drop_5.png","aomo_drop_6.png"],
            }
        self.action_keys = list(self.actions.keys())
        self.random_actions = ['shetou', 'hello', 'ele']
        self.pet_images = self.loadPetImages()

        self.image_label = QLabel(self)
        self.initImage()

        self.chat_input = QLineEdit(self)
        self.chat_input.setFixedWidth(350)
        self.chat_button = QPushButton("发送", self)
        self.chat_button.setFixedWidth(350)
        self.chat_button.clicked.connect(self.handleChat)

        self.layout = QVBoxLayout()
        self.layout.addWidget(self.image_label)
        self.layout.addWidget(self.chat_input)
        self.layout.addWidget(self.chat_button)
        self.setLayout(self.layout)

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.updateImage)
        self.action_timer = QTimer(self)
        self.action_timer.timeout.connect(self.switchAction)
        self.action_timer.start(8000)

    def initImage(self):
        image_path = os.path.join("images", 'aomo_40.png')
        image = self.loadImage(image_path)
        if image:
            self.image_label.setPixmap(QPixmap.fromImage(image))

    def loadImage(self, imagepath):
        image = QImage()
        if not image.load(imagepath):
            print(f"无法加载图像: {imagepath}")
            return None
        return image

    def loadPetImages(self):
        pet_images = {}
        for action, frames in self.actions.items():
            pet_images[action] = [self.loadImage(os.path.join("images", frame)) for frame in frames]  
        return pet_images

    def updateImage(self):
        action_frames = self.pet_images[self.action_keys[self.current_action]]
        self.index += 1
        if self.index < len(action_frames):
            self.image_label.setPixmap(QPixmap.fromImage(action_frames[self.index]))
        else:
            self.initImage()
            self.timer.stop()

    def switchAction(self):
        self.current_action = self.action_keys.index(random.choice(self.random_actions))
        self.index = 0
        self.updateImage()
        self.timer.start(50)

    def handleChat(self):
        user_message = self.chat_input.text()
        if user_message == "糖果":
            self.current_action = self.action_keys.index('flag')
            self.index = 0
            self.updateImage()
            self.timer.start(50)
            self.chat_input.clear()
            encoded_candy = base64.b64encode('糖果'.encode())
            md5_hash = hashlib.md5(encoded_candy).hexdigest()
            encrypted_flag = f"flag{{{md5_hash}}}"
            self.chat_input.setPlaceholderText(encrypted_flag)
        else:
            responses = ["奥姆?", "奥姆奥姆。", "奥姆!", "奥?姆?", "奥姆,奥姆奥姆,奥姆,奥姆奥姆。", "flagflagflagflagflagflag"]
            pet_response = random.choice(responses)
            self.chat_input.clear()
            self.chat_input.setPlaceholderText(pet_response)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.mouse_dragging = True
            self.drag_position = event.globalPos() - self.frameGeometry().topLeft()
            event.accept()

    def mouseMoveEvent(self, event):
        if self.mouse_dragging:
            self.move(event.globalPos() - self.drag_position)
            event.accept()

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.mouse_dragging = False
            event.accept()

    # Initialize attributes
    def __getattr__(self, name):
        if name == 'current_action':
            return 0
        if name == 'index':
            return 0
        if name == 'mouse_dragging':
            return False
        if name == 'drag_position':
            return QPoint()
        raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    pet = DesktopPet()
    pet.show()
    sys.exit(app.exec_())

只需要base64加密糖果,再md5加密

Snake 2025

MEW加壳

使用Xvolkolak脱壳

分析main函数,得分到达2025的时候才会得到flag。我初步想法是用CE

但是我打开这个软件就是黑屏,所以只能静态分析

我的第一个思路是粘贴代码然后进行复制

GPT生成代码:

import struct

K = [
    0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE,
    0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501,
    0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE,
    0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821,
    0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA,
    0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8,
    0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED,
    0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A,
    0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C,
    0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70,
    0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x04881D05,
    0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665,
    0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039,
    0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1,
    0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1,
    0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391,
]

def _F(B,C,D,k):
    if k < 16:
        return (D & ~B) | (C & B)
    elif k < 32:
        return (C & ~D) | (B & D)
    elif k < 48:
        return B ^ C ^ D
    else:
        return C ^ (B | ~D)

def _g(k):
    if k < 16:
        return k
    elif k < 32:
        return (5*k + 1) % 16
    elif k < 48:
        return (3*k + 5) % 16
    else:
        return (7*k) % 16

def _s(k):
    if k < 16:
        return 7 * (k % 4) + 7
    elif k < 32:
        return 5 * ((k - 16) % 4) + 5
    elif k < 48:
        return 4 * ((k - 32) % 4) + 4
    else:
        return 6 * ((k - 48) % 4) + 6

def _rotl(x, s):
    x &= 0xFFFFFFFF
    return ((x << s) | (x >> (32 - s))) & 0xFFFFFFFF

def game_hash(data: bytes) -> str:
    # padding like MD5
    bit_len = len(data) * 8
    msg = data + b'\x80'
    while len(msg) % 64 != 56:
        msg += b'\x00'
    msg += struct.pack('<Q', bit_len)

    A0, B0, C0, D0 = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476

    for i in range(0, len(msg), 64):
        block = msg[i:i+64]
        M = list(struct.unpack('<16I', block))
        A, B, C, D = A0, B0, C0, D0
        for k in range(64):
            F = _F(B, C, D, k)
            idx = _g(k)
            s = _s(k)
            tmp = (A + F + K[k] + M[idx]) & 0xFFFFFFFF
            newB = (B + _rotl(tmp, s)) & 0xFFFFFFFF
            A, B, C, D = D, newB, B, C
        A0 = (A0 + A) & 0xFFFFFFFF
        B0 = (B0 + B) & 0xFFFFFFFF
        C0 = (C0 + C) & 0xFFFFFFFF
        D0 = (D0 + D) & 0xFFFFFFFF

    return struct.pack('<4I', A0, B0, C0, D0).hex()

print(game_hash(b""))  # 5f74e936cc243f3e52d732186f9baac3

第二种思路,用ce改分数,或者用调试器改判断条件即可

你的黑客损友

看提示,我们猜测需要先解密出这位倒霉蛋的手机号,然后再用动态资源修改工具修改这个文件。

加密算法很简单,还原出即可(这部分忘记保存遗失了,抱歉!)

但是这个题目应该是有问题,MD5校验不同方法得到的不一样,只要知道思路即可

BFS

wtf,用广度优先?我真的不会这个算法,只能copy:

#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;

// 定义迷宫为66层
#define DEPTH 66
#define HEIGHT 6
#define WIDTH 6

char maze[DEPTH][HEIGHT][WIDTH] = {
    {  // Layer 0
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','.'},
            {'*','*','*','*','.','.'},
            {'*','*','s','.','.','.'},
            {'*','*','*','.','.','.'}
        },
        {  // Layer 1
            {'*','.','.','*','*','*'},
            {'*','*','*','*','.','.'},
            {'*','*','*','*','.','.'},
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'}
        },
        {  // Layer 2
            {'*','.','.','*','*','*'},
            {'*','.','.','*','*','*'},
            {'.','.','.','*','.','.'},
            {'.','*','*','*','.','.'},
            {'.','*','*','*','.','.'},
            {'*','*','*','*','.','.'}
        },
        {  // Layer 3
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'},
            {'*','*','*','*','*','*'},
            {'.','*','*','.','.','.'},
            {'*','*','*','.','.','.'}
        },
        {  // Layer 4
            {'*','*','*','*','*','*'},
            {'*','*','.','.','*','*'},
            {'*','.','.','.','*','*'},
            {'.','.','*','.','*','*'},
            {'.','*','*','.','*','*'},
            {'*','*','*','.','*','*'}
        },
        {  // Layer 5
            {'*','*','*','*','*','*'},
            {'*','*','.','.','*','*'},
            {'.','.','.','*','.','.'},
            {'.','*','*','*','.','.'},
            {'.','*','*','*','.','.'},
            {'*','*','*','*','.','.'}
        }
};

。。。。。。(中间省略)
struct Point {
    int layer, row, col;
    string path;  // 路径跟踪变量
};

bool isValid(int layer, int row, int col) {
    // 检查点是否在地图内并且是可行走的
    return layer >= 0 && layer < DEPTH && row >= 0 && row < HEIGHT && col >= 0 && col < WIDTH && maze[layer][row][col] != '*';
}

void bfs() {
    queue<Point> q;
    q.push({ 0, 4, 2, "" });  // 将起点放入队列中
    bool visited[DEPTH][HEIGHT][WIDTH] = { false };
    visited[0][4][2] = true;  // 标记起点为已访问
// 定义方向:上下左右前后
int dLayer[] = { 1, -1, 0, 0, 0, 0 };
int dRow[] = { 0, 0, 1, -1, 0, 0 };
int dCol[] = { 0, 0, 0, 0, 1, -1 };
char directions[] = { 'x', 'y', 's', 'w', 'd', 'a' };

while (!q.empty()) {
    Point p = q.front();
    q.pop();

    int layer = p.layer;
    int row = p.row;
    int col = p.col;
    string path = p.path;

    // 判断是否到终点了
    if (maze[layer][row][col] == '#') {
        cout << "Path found: " << path << endl;
        return;
    }

    // 检查每个方向,如果有效就加入队列
    for (int i = 0; i < 6; i++) {
        int newLayer = layer + dLayer[i];
        int newRow = row + dRow[i];
        int newCol = col + dCol[i];

        if (isValid(newLayer, newRow, newCol) && !visited[newLayer][newRow][newCol]) {
            q.push({ newLayer, newRow, newCol, path + directions[i] });
            visited[newLayer][newRow][newCol] = true;
        }
    }
}

cout << "No path found" << endl;
}

int main() {
    bfs();
    return 0;
}


Jathon

通过分析代码可知最后一串字符已知,而且异或密钥是BYTES类型,所以我们可以解出密钥,然后进行分析:
 

# final_str = b'The flag is in my hands'
# str=b'Dxu0v|qw0yc0y~0}i0xq~tc'
# for i in range(-128,128):
#     for j in range(min(len(str),len(final_str))):
#         if(i^str[j]==final_str[j]):
#             print(i)
# key=16
with open('./flag','rb') as f:
    exe=open('./exe',"wb")
    flag=bytearray(f.read())
    for i in range(len(flag)):
        flag[i]=flag[i]^16
    exe.write(flag)
    
                  

出来一个PYC文件。反编译直接看到flag

ServerVerification

hint说贴近现实逆向。先逆向冯师傅的CMD,这是py打包的。并未发现异常处

# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.10

data1 = '\nWindows IP 配置\n\n   主机名  . . . . . . . . . . . . . : admin\n   主 DNS 后缀 . . . . . . . . . . . :\n   节点类型  . . . . . . . . . . . . : 混合\n   IP 路由已启用 . . . . . . . . . . : 否\n   WINS 代理已启用 . . . . . . . . . : 否\n\n未知适配器 BootMagix_Tap:\n\n   媒体状态  . . . . . . . . . . . . : 媒体已断开连接\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : TAP-Windows Adapter V9\n   物理地址. . . . . . . . . . . . . : 00-FF-E6-C8-99-1C\n   DHCP 已启用 . . . . . . . . . . . : 是\n   自动配置已启用. . . . . . . . . . : 是\n\n无线局域网适配器 本地连接* 1:\n\n   媒体状态  . . . . . . . . . . . . : 媒体已断开连接\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter\n   物理地址. . . . . . . . . . . . . : 00-72-EB-7C-13-F3\n   DHCP 已启用 . . . . . . . . . . . : 是\n   自动配置已启用. . . . . . . . . . : 是\n\n无线局域网适配器 本地连接* 2:\n\n   媒体状态  . . . . . . . . . . . . : 媒体已断开连接\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter #2\n   物理地址. . . . . . . . . . . . . : 01-72-EE-7C-1C-F2\n   DHCP 已启用 . . . . . . . . . . . : 否\n   自动配置已启用. . . . . . . . . . : 是\n\n以太网适配器 VMware Network Adapter VMnet1:\n\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : VMware Virtual Ethernet Adapter for VMnet1\n   物理地址. . . . . . . . . . . . . : 00-50-56-C0-00-02\n   DHCP 已启用 . . . . . . . . . . . : 否\n   自动配置已启用. . . . . . . . . . : 是\n   本地链接 IPv6 地址. . . . . . . . : fe80::ed6c:dea7:f2eb:6361%9(首选)\n   IPv4 地址 . . . . . . . . . . . . : 192.168.215.1(首选)\n   子网掩码  . . . . . . . . . . . . : 255.255.255.0\n   默认网关. . . . . . . . . . . . . :\n   DHCPv6 IAID . . . . . . . . . . . : 184569942\n   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-05-2F-36-FA-83-D9-53-BC-37-97-C7\n   TCPIP 上的 NetBIOS  . . . . . . . : 已启用\n\n以太网适配器 VMware Network Adapter VMnet8:\n\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : VMware Virtual Ethernet Adapter for VMnet8\n   物理地址. . . . . . . . . . . . . : 00-80-56-C0-01-08\n   DHCP 已启用 . . . . . . . . . . . : 否\n   自动配置已启用. . . . . . . . . . : 是\n   本地链接 IPv6 地址. . . . . . . . : fe80::7986:6f53:884a:139%18(首选)\n   IPv4 地址 . . . . . . . . . . . . : 192.168.12.1(首选)\n   子网掩码  . . . . . . . . . . . . : 255.255.255.0\n   默认网关. . . . . . . . . . . . . :\n   DHCPv6 IAID . . . . . . . . . . . : 687886422\n   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-2F-3B-FA-83-D8-53-BC-37-A7-C7\n   TCPIP 上的 NetBIOS  . . . . . . . : 已启用\n\n无线局域网适配器 WLAN:\n\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : Intel(R) Wi-Fi 6E AX211 160MHz\n   物理地址. . . . . . . . . . . . . : 00-72-FE-7C-73-F2\n   DHCP 已启用 . . . . . . . . . . . : 是\n   自动配置已启用. . . . . . . . . . : 是\n   IPv6 地址 . . . . . . . . . . . . : 240e:310:6056:8c11:7a47:256e:3a19:c3a1(首选)\n   临时 IPv6 地址. . . . . . . . . . : 240e:310:6056:8c10:6102:3e5:f7cc:ed07(受到抨击)\n   临时 IPv6 地址. . . . . . . . . . : 240e:310:6056:8c10:8df5:1c87:9250:8267(首选)\n   临时 IPv6 地址. . . . . . . . . . : 240e:310:6056:8c10:a43e:c45c:69c1:87f(受到抨击)\n   本地链接 IPv6 地址. . . . . . . . : fe80::1b76:59c3:a335:d0af%13(首选)\n   IPv4 地址 . . . . . . . . . . . . : 192.168.5.189(首选)\n   子网掩码  . . . . . . . . . . . . : 255.255.255.0\n   默认网关. . . . . . . . . . . . . : fe80::5%13\n                                       192.168.5.1\n   DHCP 服务器 . . . . . . . . . . . : 192.168.5.1\n   DHCPv6 IAID . . . . . . . . . . . : 134247150\n   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-2F-36-FA-83-D8-53-BC-57-97-C7\n   DNS 服务器  . . . . . . . . . . . : fe80::5%13\n                                       192.168.5.1\n                                       fe80::5%13\n   TCPIP 上的 NetBIOS  . . . . . . . : 已启用\n\n以太网适配器 蓝牙网络连接:\n\n   媒体状态  . . . . . . . . . . . . : 媒体已断开连接\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : Bluetooth Device (Personal Area Network)\n   物理地址. . . . . . . . . . . . . : 00-79-EE-7C-15-F6\n   DHCP 已启用 . . . . . . . . . . . : 是\n   自动配置已启用. . . . . . . . . . : 是\n\n以太网适配器 以太网:\n\n   媒体状态  . . . . . . . . . . . . : 媒体已断开连接\n   连接特定的 DNS 后缀 . . . . . . . :\n   描述. . . . . . . . . . . . . . . : Realtek PCIe GbE Family Controller\n   物理地址. . . . . . . . . . . . . : D8-53-BC-37-97-C9\n   DHCP 已启用 . . . . . . . . . . . : 是\n   自动配置已启用. . . . . . . . . . : 是\n'
data2 = '\nd8:53:bc:37:97:c9\n'
SEARCH1 = 'ipconfig /all'
SEARCH2 = 'getmac'
CMD = input('C:\\Users\\admin>').lower()
if CMD == SEARCH1:
    print(data1)
elif CMD == SEARCH2:
    print(data2)
else:
    print('不支持该命令')
continue

exe验证程序总是提示无法连接到服务器。直接分析也没找到明显字符串,于是先拿dll开始分析,未果

想到既然有验证,肯定会发送数据包,于是准备抓包看看

我是用proxifer联动BP抓包,实际上可以终端直接set代理,终端运行程序,也可以达到抓包的目的

找到对应的HTTP请求,IDA搜索

很可惜,对应的字段是JSON验证的字段,这个网址已经被禁止访问了现在,否则会直接提示机器码不匹配,就会给我们MAC验证的思路了。

观看官方WP,题目利用的是MAC+密钥一起发给服务器,我们刚才反编译得知冯师傅的 CMD实际上会给咱们他的MAC,我们抓包修改数据包,响应包就是flag!

Polar_watermark

这是2025冬季赛才上的题目,还是很崭新的。看题目名字是和盲水印相关。嫌疑人供述密码是2位数字。

这个盲水印工具是一个python程序,先解包

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: main.py
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

"""
Blind watermark generator
"""
import sys
from pathlib import Path
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton, QFileDialog, QTextEdit, QVBoxLayout, QHBoxLayout, QMessageBox
from PyQt5.QtCore import Qt
import numpy as np
import hashlib
import struct
from wm_pkg.qr_utils import make_qr_bitmap
from wm_pkg.io_utils import imread_u, imwrite_u
from wm_pkg.wm_core import embed_watermark_doublekey

def parse_key_to_u32(s: str) -> int:
    s = s.strip()
    try:
        if s.lower().startswith('0x'):
            return int(s, 16) & 4294967295
        return int(s) & 4294967295
    except Exception:
        import hashlib
        import struct
        h = hashlib.sha256(s.encode('utf-8')).digest()
        return struct.unpack('<I', h[:4])[0]

class EmbedGUI(QWidget):

    def __init__(self):
        super().__init__()
        self.setWindowTitle('Secret_tool')
        self.setFixedSize(620, 540)
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()
        self.host_label = QLabel('宿主图片路径:')
        self.host_path = QLineEdit()
        self.host_btn = QPushButton('选择图片')
        self.host_btn.clicked.connect(self.select_host)
        hl1 = QHBoxLayout()
        hl1.addWidget(self.host_path)
        hl1.addWidget(self.host_btn)
        self.out_label = QLabel('输出图片路径:')
        self.out_path = QLineEdit()
        self.out_btn = QPushButton('保存到...')
        self.out_btn.clicked.connect(self.select_out)
        hl2 = QHBoxLayout()
        hl2.addWidget(self.out_path)
        hl2.addWidget(self.out_btn)
        self.flag_label = QLabel('嵌入文本(会生成二维码,例如 flag{...}):')
        self.flag_edit = QLineEdit()
        self.key1_label = QLabel('水印口令(password_wm):')
        self.key1_edit = QLineEdit()
        self.key2_label = QLabel('图像口令(password_img):')
        self.key2_edit = QLineEdit()
        self.btn_embed = QPushButton('开始嵌入水印')
        self.btn_embed.setStyleSheet('font-size:16px; background-color:#4CAF50; color:white; padding:8px;')
        self.btn_embed.clicked.connect(self.do_embed)
        self.log = QTextEdit()
        self.log.setReadOnly(True)
        self.log.setPlaceholderText('日志输出...')
        layout.addWidget(self.host_label)
        layout.addLayout(hl1)
        layout.addWidget(self.out_label)
        layout.addLayout(hl2)
        layout.addWidget(self.flag_label)
        layout.addWidget(self.flag_edit)
        layout.addWidget(self.key1_label)
        layout.addWidget(self.key1_edit)
        layout.addWidget(self.key2_label)
        layout.addWidget(self.key2_edit)
        layout.addWidget(self.btn_embed)
        layout.addWidget(QLabel('输出日志:'))
        layout.addWidget(self.log)
        self.setLayout(layout)

    def select_host(self):
        path, _ = QFileDialog.getOpenFileName(self, '选择宿主图片', '', 'Images (*.png *.jpg *.jpeg)')
        if path:
            self.host_path.setText(path)

    def select_out(self):
        path, _ = QFileDialog.getSaveFileName(self, '保存输出文件', '', 'PNG 图片 (*.png)')
        if path:
            self.out_path.setText(path)

    def do_embed(self):
        host = self.host_path.text().strip()
        out = self.out_path.text().strip()
        flag_text = self.flag_edit.text().strip()
        key1 = self.key1_edit.text().strip()
        key2 = self.key2_edit.text().strip()
        if not host or not Path(host).exists():
            QMessageBox.warning(self, '错误', '请选择有效的宿主图片!')
            return
        if not out:
            QMessageBox.warning(self, '错误', '请指定输出路径!')
            return
        if not flag_text:
            QMessageBox.warning(self, '错误', '请输入要嵌入的文本(flag)!')
            return
        if not key1 or not key2:
            QMessageBox.warning(self, '错误', '请输入两个不同口令!')
            return
        try:
            self.log.append('生成二维码中...')
            wm_bw = make_qr_bitmap(flag_text)
            pw_wm = parse_key_to_u32(key1)
            pw_img = parse_key_to_u32(key2)
            self.log.append(f'嵌入中... (二维码大小 {wm_bw.shape[0]}x{wm_bw.shape[1]})')
            embed_watermark_doublekey(host, out, wm_bw, pw_wm, pw_img)
            self.log.append(f'嵌入完成!输出文件:{out}')
            QMessageBox.information(self, '完成', f'水印嵌入成功!\n文件已保存:\n{out}')
        except Exception as e:
            self.log.append(f'错误:{e}')
            QMessageBox.critical(self, '出错', str(e))
if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = EmbedGUI()
    w.show()
    sys.exit(app.exec_())

看一下加水印的代码:
 

import numpy as np
import cv2
import pywt
from pathlib import Path
from io_utils import imread_u, imwrite_u
from typing import Tuple

def embed_watermark(host_path, out_path, wm_bw01 = None, seed = None, block_shape = None, d1 = ((4, 4), 36, 20), d2 = {
    'host_path': str,
    'out_path': str,
    'wm_bw01': np.ndarray,
    'seed': int,
    'block_shape': Tuple[(int, int)],
    'd1': float,
    'd2': float,
    'return': None }):
    '''
    将二值化的水印矩阵 wm_bw01 (0/1 uint8, 黑=1) 嵌入到 host_path 的图像中并写出到 out_path。
    只对 Y 通道的 DWT 近似系数进行操作(与原实现兼容的简化版)。
    该函数不会在控制台输出 seed 或敏感信息。
    '''
    host = imread_u(host_path, cv2.IMREAD_COLOR)
    if host is None:
        raise FileNotFoundError(f'''无法读取宿主图片:{host_path}''')
    (h, w) = None.shape[:2]
    yuv = cv2.cvtColor(host, cv2.COLOR_BGR2YUV).astype(np.float32)
    pad_h = h % 2
    pad_w = w % 2
    if pad_h or pad_w:
        yuv = cv2.copyMakeBorder(yuv, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT, (0, 0, 0), **('value',))
    Y = yuv[(:, :, 0)]
    (CH, CV, CD) = (CA,)
    (bh, bw) = block_shape
    (H, W) = CA.shape
    Hb = H // bh
    Wb = W // bw
    capacity = Hb * Wb
    wm_bits = wm_bw01.flatten()
    wm_size = wm_bits.size
    if wm_size > capacity:
        raise ValueError(f'''水印过大:需要 {wm_size} 位,但容量为 {capacity} 块''')
    rs_img = pywt.dwt2(Y, 'haar').random.RandomState(int(seed & 0xFFFFFFFF))
    idx_shuffle = rs_img.random((capacity, bh * bw), **('size',)).argsort(1, **('axis',))
    rs_wm = np.random.RandomState(int(seed >> 1 & 0xFFFFFFFF))
    perm = np.arange(wm_size)
    rs_wm.shuffle(perm)
    wm_shuffled = wm_bits[perm]
    CA_new = CA.copy()
    k = 0
    for bi in range(Hb):
        for bj in range(Wb):
            block = CA[(bi * bh:(bi + 1) * bh, bj * bw:(bj + 1) * bw)].astype(np.float32)
            shuf = idx_shuffle[k]
            bit = float(wm_shuffled[k % wm_size])
            B = cv2.dct(block)
            Bf = B.flatten()[shuf].reshape(block.shape)
            (U, s, Vt) = np.linalg.svd(Bf, False, **('full_matrices',))
            s0 = (s[0] // d1 + 0.25 + 0.5 * bit) * d1
            s[0] = s0
            if len(s) > 1 and d2:
                s1 = (s[1] // d2 + 0.25 + 0.5 * bit) * d2
                s[1] = s1
            Bf2 = (U @ np.diag(s) @ Vt).flatten()
            inv = np.empty_like(Bf2)
            inv[shuf] = Bf2
            CA_new[(bi * bh:(bi + 1) * bh, bj * bw:(bj + 1) * bw)] = cv2.idct(inv.reshape(block.shape))
            k += 1
    Y_new = pywt.idwt2((CA_new, (CH, CV, CD)), 'haar')
    Y_new = Y_new[(:h, :w)]
    yuv[(:h, :w, 0)] = Y_new
    out_img = cv2.cvtColor(np.clip(yuv, 0, 255).astype(np.uint8), cv2.COLOR_YUV2BGR)
    ok = imwrite_u(out_path, out_img)
    if not ok:
        raise IOError(f'''无法写入输出文件:{out_path}''')


def embed_watermark_doublekey(host_path, out_path, wm_bw01, password_wm, password_img, block_shape, d1, d2 = ((4, 4), 36, 20)):
    import cv2
    import pywt
    import numpy as np
    imread_u = imread_u
    imwrite_u = imwrite_u
    import io_utils
    img = imread_u(host_path, cv2.IMREAD_COLOR)
    if img is None:
        raise FileNotFoundError(f'''无法读取宿主图片:{host_path}''')
    (h, w) = None.shape[:2]
    yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV).astype(np.float32)
    pad_h = h % 2
    pad_w = w % 2
    if pad_h or pad_w:
        yuv = cv2.copyMakeBorder(yuv, 0, pad_h, 0, pad_w, cv2.BORDER_CONSTANT, (0, 0, 0), **('value',))
    wm_bits = wm_bw01.flatten().astype(np.uint8)
    wm_size = wm_bits.size
    rs_wm = np.random.RandomState(int(password_wm) & 0xFFFFFFFF)
    perm = np.arange(wm_size)
    rs_wm.shuffle(perm)
    wm_shuffled = wm_bits[perm]
    (bh, bw) = block_shape
    for ch in range(3):
        (CH, CV, CD) = (CA,)
        (H, W) = CA.shape
        Hb = H // bh
        Wb = W // bw
        capacity = Hb * Wb
        if wm_size > capacity:
            raise ValueError(f'''水印过大:需要 {wm_size} 位,容量仅 {capacity}(通道 {ch})''')
        rs_img = pywt.dwt2(yuv[(:, :, ch)], 'haar').random.RandomState(int(password_img) & 0xFFFFFFFF)
        idx_shuffle = rs_img.random((capacity, bh * bw), **('size',)).argsort(1, **('axis',))
        CA_new = CA.copy()
        k = 0
        for bi in range(Hb):
            for bj in range(Wb):
                block = CA[(bi * bh:(bi + 1) * bh, bj * bw:(bj + 1) * bw)].astype(np.float32)
                shuf = idx_shuffle[k]
                bit = float(wm_shuffled[k % wm_size])
                B = cv2.dct(block)
                Bf = B.flatten()[shuf].reshape(block.shape)
                (U, s, Vt) = np.linalg.svd(Bf, False, **('full_matrices',))
                s[0] = (s[0] // d1 + 0.25 + 0.5 * bit) * d1
                if len(s) > 1 and d2:
                    s[1] = (s[1] // d2 + 0.25 + 0.5 * bit) * d2
                Bf2 = (U @ np.diag(s) @ Vt).flatten()
                inv = np.empty_like(Bf2)
                inv[shuf] = Bf2
                CA_new[(bi * bh:(bi + 1) * bh, bj * bw:(bj + 1) * bw)] = cv2.idct(inv.reshape(block.shape))
                k += 1
        yuv[(:, :, ch)] = pywt.idwt2((CA_new, (CH, CV, CD)), 'haar')
    yuv = yuv[(:h, :w, :)]
    out = cv2.cvtColor(np.clip(yuv, 0, 255).astype(np.uint8), cv2.COLOR_YUV2BGR)
    if not imwrite_u(out_path, out):
        raise IOError(f'''无法写入输出文件:{out_path}''')

AI写脚本爆破即可

真叫人头大(暂未解决)

考的PE文件结构,还在学习之中

flag:4f0b3d916399b06b2d4aff84328a37c4

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值