本文最后更新于:2024年7月24日 凌晨
Week 5, 01/31 - 02/06, BUUCTF
reverse1
用 ExeinfoPE 看一下, 是一个 x64 程序.
IDA, 入口是sub_140012170
, 一路跟踪下去到sub_140012190
, 然后分析不能
从 Strings window 看一下, 发现一条明显的提示语 wrong flag
, 看一下, 是sub_1400118C0
引用了它, 跳过去康康.
第18行的 for
循环中, 把 Str2[]
中的o
全部替换为了0
, 然后和输入的Str1
进行比较, 判断是否正确.
Str2[]
的内容是{hello_world}
, 替换后输入程序检查, 确定正确.
flag{hell0_w0rld}
reverse3
IDA, 找到 main
之后 F5, 大概修改一些名称之后得到如下.
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 int __cdecl main_0 (int argc, const char **argv, const char **envp) { size_t StrLen; const char *v4; size_t destLen2; char v7; char v8; signed int j; int i; signed int destLen; char Destination[108 ]; char Str[28 ]; char v14[8 ]; for ( i = 0 ; i < 100 ; ++i ) { if ( (unsigned int )i >= 0x64 ) j____report_rangecheckfailure(); Destination[i] = 0 ; } alert("please enter the flag:" , v7); input("%20s" , (char )Str); StrLen = j_strlen(Str); v4 = (const char *)sub_4110BE(Str, StrLen, v14); strncpy (Destination, v4, 0x28 u); destLen = j_strlen(Destination); for ( j = 0 ; j < destLen; ++j ) Destination[j] += j; destLen2 = j_strlen(Destination); if ( !strncmp (Destination, aE3nifih9bCNDh, destLen2) ) alert("rigth flag!\n" , v8); else alert("wrong flag!\n" , v8); return 0 ; }
在得到输入之后, sub_4110BE
对输入的 Str
进行了一些处理, 同时 27 行的 for
循环也对 Destination[]
进行了移位, 之后两者比较判断是否正确.
观察 sub_4110BE
, 发现其中调用了一个 base64 的编码表, 结合行为确定是对输入进行 base64 编码.
把 Destination[]
的内容进行反移位, 得到e2lfbDB2ZV95b3V9
, base64 解码后得到 {i_l0ve_you}
flag{i_l0ve_you}
另外题中的"right"打错了…
SimpleRev
DIE 检查, 发现是一个 x64 ELF 文件. IDA 打开, 定位到 main
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int v3; char v4; while ( 1 ) { while ( 1 ) { printf ("Welcome to CTF game!\nPlease input d/D to start or input q/Q to quit this program: " ); v4 = getchar(); if ( v4 != 'd' && v4 != 'D' ) break ; Decry(); } if ( v4 == 'q' || v4 == 'Q' ) Exit("Welcome to CTF game!\nPlease input d/D to start or input q/Q to quit this program: " , argv); puts ("Input fault format!" ); v3 = getchar(); putchar (v3); } }
明显, 在 Decry
函数中进行了 flag 的验证…
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 unsigned __int64 Decry () { char currChar; int v2; int v3; int i; int keyLen; char src[8 ]; __int64 v7; int v8; char *v9; __int64 v10; int v11; unsigned __int64 v12; v12 = __readfsqword(0x28 u); *(_QWORD *)src = 'SLCDN' ; v7 = 0LL ; v8 = 0 ; v9 = (char *)'wodah' ; v10 = 0LL ; v11 = 0 ; text = join(key3, (const char *)&v9); strcpy (key, key1); strcat (key, src); v2 = 0 ; v3 = 0 ; getchar(); keyLen = strlen (key); for ( i = 0 ; i < keyLen; ++i ) { if ( key[v3 % keyLen] > '@' && key[v3 % keyLen] <= 'Z' ) key[i] = key[v3 % keyLen] + 32 ; ++v3; } printf ("Please input your flag:" ); while ( 1 ) { currChar = getchar(); if ( currChar == '\n' ) break ; if ( currChar == ' ' ) { ++v2; } else { if ( currChar <= 96 || currChar > 122 ) { if ( currChar > '@' && currChar <= 'Z' ) { str2[v2] = (currChar - 39 - key[v3 % keyLen] + 97 ) % 26 + 97 ; ++v3; } } else { str2[v2] = (currChar - 39 - key[v3 % keyLen] + 97 ) % 26 + 97 ; ++v3; } if ( !(v3 % keyLen) ) putchar (' ' ); ++v2; } } if ( !strcmp (text, str2) ) puts ("Congratulation!\n" ); else puts ("Try again!\n" ); return __readfsqword(0x28 u) ^ v12; }
在 37 行的 while
循环后, 通过计算 str2[]
的值并判断是否与text
相等来确定 flag 正确与否, 写个脚本爆破即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 upperTable='ABCDEFGHIJKLMNOPQRSTUVWXYZ' lowerTable = upperTable.lower() key=list ('ADSFKNDCLS' .lower()) klens=len (key) text='killshadow' flag='' flag2='' for i in range (len (text)): str2=text[i] for c in upperTable: if str2== chr ((ord (c) - 39 - ord (key[i % klens]) + 97 ) % 26 + 97 ): flag+=cfor i in range (len (text)): str2=text[i] for c in lowerTable: if str2== chr ((ord (c) - 39 - ord (key[i % klens]) + 97 ) % 26 + 97 ): flag2+=cprint ('flag{' +flag+'}' )print ('flag{' +flag2+'}' )
因为并未对大小写进行限定, 所以大小写理论上均可…大概, 总之大写是可以的.
flag{KLDQCUDFZO}
Week 6, 02/07 - 02/14, BUUCTF
CrackRTF
看题目说明, 在文件中藏了一个 rtf 文件, 不过用 binwalk 扫描没发现什么. 可能是加密了…
用 IDA 打开, 定位到 main_0
, 发现需要两个密码, 长度均为 6 个字符, 其中第一个为纯数字, 与 @DBApp
链接后计算一个哈希值, 与 6E32D0943418C2C33385BC35A1470250DD8923A9
比较.
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 48 49 50 51 52 int __cdecl main_0 (int argc, const char **argv, const char **envp) { DWORD v3; DWORD v4; char Str[260 ]; int v7; char String1[260 ]; char Destination[260 ]; memset (Destination, 0 , sizeof (Destination)); memset (String1, 0 , sizeof (String1)); v7 = 0 ; printf ("pls input the first passwd(1): " ); scanf ("%s" , Destination); if ( strlen (Destination) != 6 ) { printf ("Must be 6 characters!\n" ); ExitProcess(0 ); } v7 = atoi(Destination); if ( v7 < 100000 ) ExitProcess(0 ); strcat (Destination, "@DBApp" ); v3 = strlen (Destination); getSHA1((BYTE *)Destination, v3, String1); if ( !_strcmpi(String1, "6E32D0943418C2C33385BC35A1470250DD8923A9" ) ) { printf ("continue...\n\n" ); printf ("pls input the first passwd(2): " ); memset (Str, 0 , sizeof (Str)); scanf ("%s" , Str); if ( strlen (Str) != 6 ) { printf ("Must be 6 characters!\n" ); ExitProcess(0 ); } strcat (Str, Destination); memset (String1, 0 , sizeof (String1)); v4 = strlen (Str); getMD5((BYTE *)Str, v4, String1); if ( !_strcmpi("27019e688a4e62a649fd99cadaafdb4e" , String1) ) { if ( !(unsigned __int8)sub_40100F(Str) ) { printf ("Error!!\n" ); ExitProcess(0 ); } printf ("bye ~~\n" ); } } return 0 ; }
进入 sub_401230
, 定位到 CryptCreateHash(phProv, 0x8004u, 0, 0, &phHash)
, 搜一下这个函数的文档 , 发现第二个参数控制了计算的哈希种类, 0x8004
是 SHA1, 0x8003
是 MD5. 由此我们可以对在 [100000, 1000000) 的前半部分进行爆破, 写个脚本.
1 2 3 4 5 6 7 8 9 10 11 12 import hashlib prefix = "" postfix = "@DBApp" for i in range (100000 , 1000000 ): prefix = str (i) shaObj = hashlib.sha1(bytes (prefix + postfix, encoding="UTF-8" )) if (shaObj.hexdigest() == "6E32D0943418C2C33385BC35A1470250DD8923A9" .lower()): print (prefix + postfix)
这样, 我们得到了第一个密码 1233321
, 这时 Destination == "123321@DBApp"
, 输入的第二个密码后面会连接 Destination
, 然后计算 MD5 值, 和 27019e688a4e62a649fd99cadaafdb4e
比较, 判断是否正确.
需要注意的是, 第二个密码只限制了长度, 没有限制是纯数字…没注意到的话你可能就会和我一样卡住了
到这里, 我们有两种方法.
充分利用你电脑的计算能力
我们已知第二个 MD5 原文的后半部分和前半部分的长度, 相当于一个加盐的 MD5 爆破 - HashCat 很适合干这个事儿, 特别是你的 GPU 比较强的时候.
用如下的命令行进行一次 md5($pass.$salt)
掩码攻击. 其实就是暴力穷举, 不过更高级一点, 大概
.\hashcat.exe -m 10 "27019e688a4e62a649fd99cadaafdb4e:123321@DBApp" -a 3 ?a?a?a?a?a?a
在我的电脑上, 大概十秒就得到了结果…
前半部分是 ~!3a@0
, 把俩密码输入程序, 得到一个 RTF 文档, 打开就是 flag.
不过, 我认为这应该不是出题人的最初想法吧
充分利用你自己的 计算能力 解题能力
在 main_0
的 44 行, 我们发现在两个密码验证正确后, 调用了 sub_40400F
, 参数是 Str
.
sub_40400F
会再调用 sub_4014D0
, 参数变为 LPCSTR
类型.
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 char __cdecl sub_4014D0 (LPCSTR lpString) { LPCVOID lpBuffer; DWORD NumberOfBytesWritten; DWORD nNumberOfBytesToWrite; HGLOBAL hResData; HRSRC hResInfo; HANDLE hFile; hFile = 0 ; hResData = 0 ; nNumberOfBytesToWrite = 0 ; NumberOfBytesWritten = 0 ; hResInfo = FindResourceA(0 , (LPCSTR)0x65 , "AAA" ); if ( !hResInfo ) return 0 ; nNumberOfBytesToWrite = SizeofResource(0 , hResInfo); hResData = LoadResource(0 , hResInfo); if ( !hResData ) return 0 ; lpBuffer = LockResource(hResData); sub_401005(lpString, (int )lpBuffer, nNumberOfBytesToWrite); hFile = CreateFileA("dbapp.rtf" , 0x10000000 u, 0 , 0 , 2u , 0x80 u, 0 ); if ( hFile == (HANDLE)-1 ) return 0 ; if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0 ) ) return 0 ; CloseHandle(hFile); return 1 ; }
首先是 FindResourceA
这个函数, 从文档 来看, 它会尝试寻找名称为 0x65
, 类型为 AAA
的资源, 然后通过SizeofResource
计算长度, 通过LoadResource
加载到lpBuffer
中并上锁, 之后调用了sub_401005
进行了什么操作, 然后创建dbapp.rtf
并写文件, 最后关闭.
看来sub_401005
就是解密函数了.
sub_401005
会继续调用sub_401420
, 主要是一个异或的循环…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 unsigned int __cdecl sub_401420 (LPCSTR key, int fileContentOffset, int a3) { unsigned int result; unsigned int i; unsigned int keyLen; keyLen = lstrlenA(key); for ( i = 0 ; ; ++i ) { result = i; if ( i >= a3 ) break ; *(_BYTE *)(i + fileContentOffset) ^= key[i % keyLen]; } return result; }
在这里, 会将现有数据的每一位和密钥进行异或作为解密, 而原文件是一个 RTF 格式的文件, 文件头是 7B 5C 72 74 66 31
, 正好六个字节, 和第二段密码一样长.
参考这篇题解 , 用 ResourceHacker 看一下资源,果然找到了这个. (0x65 == 101)
加密后的文件头是 05 7D 41 15 26 01
, 把它和原来的文件头进行异或就得到了第二段密码: ~!3a@0
flag{N0_M0re_Free_Bugs}
Week 11, 03/14 - 03/20, BUUCTF
[SUCTF2019]SignIn
好像摸鱼摸的太狠了… 从开学到现在啥也没干…
IDA 打开, 定位到main
, 发现两个大整数.
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 __int64 __fastcall main (int a1, char **a2, char **a3) { char v4[16 ]; char v5[16 ]; char v6[16 ]; char v7[16 ]; char input[112 ]; char coded[1000 ]; unsigned __int64 v10; v10 = __readfsqword(0x28 u); puts ("[sign in]" ); printf ("[input your flag]: " ); __isoc99_scanf("%99s" , input); sub_96A(input, coded); __gmpz_init_set_str(v7, "ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35" , 16LL ); __gmpz_init_set_str(v6, coded, 16LL ); __gmpz_init_set_str(v4, "103461035900816914121390101299049044413950405173712170434161686539878160984549" , 10LL ); __gmpz_init_set_str(v5, "65537" , 10LL ); __gmpz_powm(v6, v6, v5, v4); if ( (unsigned int )__gmpz_cmp(v6, v7) ) puts ("GG!" ); else puts ("TTTTTTTTTTql!" ); return 0LL ; }
sub_96A
会把输入进行处理, 得到 16 进制字符串. 之后的__gmpz_
系列函数是 GNU 的高精度计算库.
16 ~ 19 行初始化了四个整数, 查看文档 发现__gmpz_powm(mpz_t rop, const mpz_t base, const mpz_t exp, const mpz_t mod)
实际上等价于rop = (base ^ exp) % mod
. 另外, 据说这个函数经常在 RSA 算法中出现…
RSA! 这些大整数正好就是 RSA 中会用到的! 对应一下, v7
是密文, v6
是明文, v5
是e
, v4
就是n
. 把v4
放到 factor.db 分解一下, 得到了p
和q
.
这样, RSA 算法的所有变量我们都拿到了, 直接写个脚本解密就能拿到 flag 了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import gmpy2import binascii n = 103461035900816914121390101299049044413950405173712170434161686539878160984549 p = gmpy2.mpz(282164587459512124844245113950593348271 ) q = gmpy2.mpz(366669102002966856876605669837014229419 ) e = gmpy2.mpz(65537 ) eula = (p-1 )*(q-1 ) d = gmpy2.invert(e, eula) c = gmpy2.mpz(0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 ) dec = gmpy2.powmod(c, d, n)print ("十进制:\n%s" %dec) dec_hex = hex (dec)[2 :]print ("十六进制:\n%s" %(dec_hex,))print ("ascii:\n%s" %(binascii.a2b_hex(dec_hex).decode(encoding="utf8" )))
由于gmpy2
在我的 Windows 下装不上 (需要gmp
mpfr
mpc
), 因此我在 Ubuntu 虚拟机下面装了.
拿到结果.
flag: suctf{Pwn_@_hundred_years}
Week 13, 03/28 - 04/03, BUUCTF
[FlareOn6] Overlong
PE32 程序, 没发现有壳, 直接用 IDA 打开, 只有三个函数, 而且没有 .init_array
和.fini_array
段, 没有发现有价值的明文字符串 - 也就是说有某种形式的加解密.
运行一下试试, 注意到下图最后是个冒号… 推测应该是长度限制了.
IDA 打开start
函数, 很容易定位到解密函数, 它的第二个参数是密文, 第三个参数就是长度.
1 2 3 4 5 6 7 8 9 10 int __stdcall start (int a1, int a2, int a3, int a4) { CHAR Text[128 ]; int v6; v6 = sub_401160(Text, &unk_402008, 28 ); Text[v6] = 0 ; MessageBoxA(0 , Text, Caption, 0 ); 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 27 28 .text:004011C0 start proc near .text:004011C0 .text:004011C0 Text = byte ptr -84h .text:004011C0 var_4 = dword ptr -4 .text:004011C0 .text:004011C0 push ebp .text:004011C1 mov ebp, esp .text:004011C3 sub esp, 84h .text:004011C9 push 1Ch .text:004011CB push offset unk_402008 .text:004011D0 lea eax, [ebp+Text] .text:004011D6 push eax .text:004011D7 call sub_401160 .text:004011DC add esp, 0Ch .text:004011DF mov [ebp+var_4], eax .text:004011E2 mov ecx, [ebp+var_4] .text:004011E5 mov [ebp+ecx+Text], 0 .text:004011ED push 0 ; uType .text:004011EF push offset Caption ; "Output" .text:004011F4 lea edx, [ebp+Text] .text:004011FA push edx ; lpText .text:004011FB push 0 ; hWnd .text:004011FD call ds:MessageBoxA .text:00401203 xor eax, eax .text:00401205 mov esp, ebp .text:00401207 pop ebp .text:00401208 retn 10h .text:00401208 start endp
unk_402008
的长度是 0xAF
, 尝试修改一下. 但是由于某些原因, 在用 KeyPatch 修改push 1Ch
的时候会干扰下一条指令…
用动态调试, 在0x4011C9
下个断点, 在 push 之后直接把栈上数据改成 0xAF
, 如下图.
然后继续执行, 看到了 好康的 答案.
flag{I_a_M_t_h_e_e_n_C_o_D_i_n_g@flare-on.com}
[GXYCTF2019] simple CPP
果然题面里有 simple 的不是送分题就是送命题
IDA打开, 按顺序把main
分成三部分
第一部分 异或加密
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 48 v3 = 0 ; v40 = 0 i64; v41 = 15 i64; LOBYTE(Block[0 ]) = 0 ; v4 = std ::operator<<<std ::char_traits<char >>( std ::cout , (__int64)"I'm a first timer of Logic algebra , how about you?" );std ::ostream::operator<<(v4, std ::endl <char ,std ::char_traits<char >>);std ::operator<<<std ::char_traits<char >>(std ::cout , (__int64)"Let's start our game,Please input your flag:" );std ::operator>><char >(std ::cin , (std ::string *)Block);std ::ostream::operator<<(std ::cout , std ::endl <char ,std ::char_traits<char >>);if ( v40 - 5 > 25 ) { v35 = std ::operator<<<std ::char_traits<char >>(std ::cout , (__int64)"Wrong input ,no GXY{} in input words" ); std ::ostream::operator<<(v35, std ::endl <char ,std ::char_traits<char >>); goto LABEL_41; } v5 = (unsigned __int8 *)operator new(0x20 ui64); v6 = v5;if ( v5 ) { *(_QWORD *)v5 = 0 i64; *((_QWORD *)v5 + 1 ) = 0 i64; *((_QWORD *)v5 + 2 ) = 0 i64; *((_QWORD *)v5 + 3 ) = 0 i64; }else { v6 = 0 i64; } v7 = 0 ;if ( v40 ) { v8 = 0 i64; do { v9 = Block; if ( v41 >= 0x10 ) v9 = (void **)Block[0 ]; v10 = &qword_7FF73C476048; if ( (unsigned __int64)qword_7FF73C476060 >= 0x10 ) v10 = (void **)qword_7FF73C476048; v6[v8] = *((_BYTE *)v9 + v8) ^ *((_BYTE *)v10 + v7 % 27 ); ++v7; ++v8; } while ( v7 < v40 ); }
这里会首先对输入进行一次异或加密并保存在v6
, key 是 *qword_7FF73C476060
, 实际内容未知, 等到动态调试时再确定.
第二部分 分组
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 v11 = 0 i64; v12 = 0 i64; v13 = 0 i64; v14 = 0 i64; if ( (int )v40 > 30 ) goto LABEL_27; v15 = 0 ; if ( (int )v40 <= 0 ) goto LABEL_27; v16 = v6; do { v17 = *v16 + v11; ++v15; ++v16; switch ( v15 ) { case 8 : v14 = v17; goto LABEL_23; case 16 : v13 = v17; goto LABEL_23; case 24 : v12 = v17; LABEL_23: v17 = 0 i64; break ; case 32 : std ::operator<<<std ::char_traits<char >>(std ::cout , (__int64)"ERRO,out of range" ); exit (1 ); } v11 = v17 << 8 ; } while ( v15 < (int )v40 );
在这里把输入按照 8 个字符为一组 (正好 64 位)分开, 分别赋给v14
v13
v12
v11
.
第三部分 校验
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 if ( v14 ) { v18 = (__int64 *)operator new(0x20 ui64); *v18 = v14; v18[1 ] = v13; v18[2 ] = v12; v18[3 ] = v11; goto LABEL_28; } LABEL_27: v18 = 0 i64; LABEL_28: v38 = v18[2 ]; v19 = v18[1 ]; v20 = *v18; v21 = (__int64 *)operator new(0x20 ui64); v22 = v19 & v20; *v21 = v19 & v20; v23 = v38 & ~v20; v21[1 ] = v23; v24 = ~v19; v25 = v38 & v24; v21[2 ] = v38 & v24; v26 = v20 & v24; v21[3 ] = v26; if ( v23 != 0x11204161012 i64 ) { v21[1 ] = 0 i64; v23 = 0 i64; } v27 = v23 | v22 | v25 | v26; v28 = v18[1 ]; v29 = v18[2 ]; v30 = v25 & *v18 | v29 & (v22 | v28 & ~*v18 | ~(v28 | *v18)); v31 = 0 ; if ( v30 == 0x8020717153E3013 i64 ) v31 = v27 == 0x3E3A4717373E7F1F i64; if ( (v27 ^ v18[3 ]) == 0x3E3A4717050F791F i64 ) v3 = v31; if ( (v23 | v22 | v28 & v29) == (~*v18 & v29 | 0xC00020130082C0C i64) && v3 ) { v32 = std ::operator<<<std ::char_traits<char >>(std ::cout , (__int64)"Congratulations!flag is GXY{" ); }
所以我们可以得到如下的方程组…
1 2 3 4 z & ~x == 1176889593874 z & ~x | y & x | z & ~y | x & ~y == 4483974544037412639 z & ~y &x | z &(y & x | y & ~x | ~(y | x)) == 577031497978884115 (z & ~x | y & x | z & ~y | x & ~y) ^ w == 4483974543195470111
用 z3 解之, 得到
1 2 3 4 w = 842073600 y = 290580315766788 x = 4483973367147818765 z = 577031497978884115
v6
就是x y z w
按顺序连接得到的加密后的 flag.
最后一击
接下来我们在0x13DF
的位置下个断点, 观察一下qword_7FF73C476048
指向的内容.
1 .data:00007FF73C476048 qword_7FF73C476048 dq 1D5FD108F30h ; DATA XREF: main+DF↑o
选中0x1D5FD108F30h
处的数据, Edit - Strings, 得到i_will_check_is_debug_or_not
1 2 3 4 5 6 7 enc =[0x3e ,0x3a ,0x46 ,0x05 ,0x33 ,0x28 ,0x6f ,0x0d ,0x1 ,0x08 ,0x48 ,0x00 ,0x00 ,0x80 ,0x00 ,0x04 ,0x8 ,0x02 ,0x07 ,0x17 ,0x15 ,0x3e ,0x30 ,0x13 ,0x32 ,0x31 ,0x06 ,0x00 ] key = "i_will_check_is_debug_or_not" flag = "" for i in range (len (enc)-1 ): flag += chr ((ord (key[i%27 ])) ^ enc[i])print (flag)
用y
手动修正一下, 得到 flag.
flag{We1l_D0ne!P0or_algebra_am_i}