一个exe文件,用exeinfope先看看,Pyinstaller编译的,
可以使用pyinstxtractor反编译,这个网上搜一下很多的,具体使用就是
python pyinstxtractor.py 待反编译文件.exe
有一些pyc需要修补一下文件头,可以和struct.pyc一起拖入WinHex中对比一下。这里不需要,直接反编译就好,可以使用uncompyle6也可以网上找在线工具。这里随便找了个网站
# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.9
def hello():
art = '\n ___ \n // ) ) / / // ) ) // | | / / // | | \\ / / \\ / / \n //___/ / / / // //__| | / / //__| | \\ / \\ / / \n / ____ / / / // ____ / ___ | / / / ___ | / / \\/ / \n // / / // / / // | | / / // | | / /\\ / / \n// / /___ ((____/ / // | | / /____/ / // | | / / \\ / / \n \n / / // / / || / / // / / / / /__ ___/ || / | / / // ) ) \n / / //____ || / / //____ / / / / || / | / / // / / \n / / / ____ || / / / ____ / / / / || / /||/ / // / / \n / / // ||/ / // / / / / ||/ / | / // / / \n / /____/ / //____/ / | / //____/ / / /____/ / / / | / | / ((___/ / \n'
print(art)
return bytearray(input('Please give me the flag: ').encode())
enc = [
115,
121,
116,
114,
110,
76,
37,
96,
88,
116,
113,
112,
36,
97,
65,
125,
103,
37,
96,
114,
125,
65,
39,
112,
70,
112,
118,
37,
123,
113,
69,
79,
82,
84,
89,
84,
77,
76,
36,
112,
99,
112,
36,
65,
39,
116,
97,
36,
102,
86,
37,
37,
36,
104]
data = hello()
for i in range(len(data)):
data[i] = data[i] ^ 21
if bytearray(enc) == data:
print('WOW!!')
else:
print('I believe you can do it!')
input('To be continue...')
一个简单的异或,写个exp
enc = [115, 121, 116, 114, 110, 76, 37, 96, 88, 116, 113, 112, 36, 97, 65, 125, 103, 37, 96, 114, 125, 65, 39, 112, 70, 112, 118, 37, 123, 113, 69, 79, 82, 84, 89, 84, 77, 76, 36, 112, 99, 112, 36, 65, 39, 116, 97, 36, 102, 86, 37, 37, 36, 104]
data = []
for i in range(len(enc)):
data.append(chr(enc[i] ^ 21))
print("".join(data))
# flag{Y0uMade1tThr0ughT2eSec0ndPZGALAXY1eve1T2at1sC001}
JEB打开,看左侧,大概就是RC4+Base64的加密,点击MainActivity Tab反编译先
new int[]{0x7D, 0xEF, 101, 0x97, 77, 0xA3, 0xA3, 110, 58, 230, 0xBA, 206, 84, 84, 0xBD, 0xC1, 30, 0x3F, 104, 0xB2, 130, 0xD3, 0xA4, 94, 75, 16, 0x20, 33, 0xC1, 0xA0, 120, 0x2F, 30, 0x7F, 0x9D, 66, 0xA3, 0xB5, 0xB1, 0x2F, 0, 0xEC, 106, 107, 0x90, 0xE7, 0xFA, 16, 36, 34, 91, 9, 0xBC, 81, 5, 0xF1, 0xEB, 3, 54, 150, 40, 0x77, 202, 150})).equals(“YnwgY2txbE8TRyQecyE1bE8DZWMkMiRgJW1=”)) {
看到RC4那行,一直拉到最右边复制,能看到一串数据和一个base64解密。再结合上面有个key
先试试RC4
from Crypto.Cipher import ARC4
arr = [0x7D, 0xEF, 101, 0x97, 77, 0xA3, 0xA3, 110, 58, 230, 0xBA, 206, 84, 84, 0xBD, 0xC1, 30, 0x3F, 104, 0xB2, 130, 0xD3, 0xA4, 94, 75, 16, 0x20, 33, 0xC1, 0xA0, 120, 0x2F, 30, 0x7F, 0x9D, 66, 0xA3, 0xB5, 0xB1, 0x2F, 0, 0xEC, 106, 107, 0x90, 0xE7, 0xFA, 16, 36, 34, 91, 9, 0xBC, 81, 5, 0xF1, 0xEB, 3, 54, 150, 40, 0x77, 202, 150]
arrBytes = bytes(arr)
key = b"genshinimpact"
enc = ARC4.new(key)
base64_table = enc.decrypt(arrBytes)
print(base64_table)
# b'BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqtsvuxwzy1032547698/+'
果然解出一个base64码表,那就base64换表解一下
import base64
str1 = "YnwgY2txbE8TRyQecyE1bE8DZWMkMiRgJW1="
string1 = "BADCFEHGJILKNMPORQTSVUXWZYbadcfehgjilknmporqtsvuxwzy1032547698/+" #替换的表
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
# b'flag{0h_RC4_w1th_Base64!!}'
这里我尝试过jnz改成jmp,或者将中间全部nop掉,都没有用,进入动态调试还是一样会弹出。
应该是反调试,
把它nop掉就好。
下断点,动调后再修改汇编,
一直单步步过就好,JZ修改成JNZ;直到call eax;sub_403040处 用F7步入。 (修改用keypatch)或者Edit-Patch program)
忘记说了,这道题是SMC,也就是动态自修改代码,主函数第七行的VirtualProtect就是授予内存写权限,再用一些加密对内存进行改写,因为会在程序运行的时候进行自解密,所以不会影响程序的正常运行;目的是干扰静态分析。
这里步入403040处,程序就已经开始自解密了;
这里选择no,应该这里ida实际上已经不能正确识别eip入口了,我们手动处理一下
先点击 用 U 一下 ; 然后选中这个函数所有内容,用C转化为代码,记得Force强制。最后用P创建函数,再F5反编译一下。
逻辑很简单,逆向就是,用403020的值-5^0x11就好,点击4033D4就知道那里放到其实是我们的输入;
data = [0x7C, 0x82, 0x75, 0x7B, 0x6F, 0x47, 0x61, 0x57, 0x53, 0x25,
0x47, 0x53, 0x25, 0x84, 0x6A, 0x27, 0x68, 0x27, 0x67, 0x6A,
0x7D, 0x84, 0x7B, 0x35, 0x35, 0x48, 0x25, 0x7B, 0x7E, 0x6A,
0x33, 0x71]
result = [chr((byte - 5) ^ 0x11 ) for byte in data]
print("".join(result))
# flag{SMC_1S_1nt3r3sting!!R1ght?}
array1 = [68,75,66,72,99,19,19,78,83,74,91,86,35,39,77,85,44,89,47,92,49,88,48,91,88,102,105,51,76,115,-124,125,79,122,-103]
test = [78,69,87,83,84,69,82]
for i in range(7):
array1[i] -= i ^ -(test[i] % 4)
array1[i+7] -= test[i] % 5
array1[i+14] -= 2*i
array1[i+21] -= i^2
array1[i+28] -= test[i] // 5 + 10
for i in range(35):
array1[i] -= i
array1[i] += 32
print(chr(array1[i]%256),end='')
# flag{45dg_ng38_d8b5_1a7d_gh47_kd5b}
可以看到一个rand随机数,因为rand是伪随机,所以尝试一下动调;会发现动调行不通,那么应该就是有反调试,这里一开始没有找到的,因为之前只接触过isdebuggerpresent,后面才知道是linux的反调试
学习:https://xz.aliyun.com/t/6882
这题的函数很少,这个a很显眼,点进去看看就知道了,ptrace反调试,可以patch掉绕过。
一开始本身是打算动调然后找到42个随机数的,但是发现很麻烦,所以选择找随机数种子,也就是下面的b;
倒回去看加密的逻辑,Table表:
这里就是用我们的输入和随机数相加作为table表的索引,最后得到的值就是s2;
由于这里随机数在windows和linux环境下得到的结果不同,所以要在linux环境中运行我们的脚本;
#include
#include
#include
__uint8_t Table[256] = { 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};
int main(){
srand(1400333646);
__uint8_t enc[42] = { 0xEE, 0xE6, 0xD7, 0xB2, 0x8A, 0xAB, 0x13, 0x35, 0x02, 0x7B, 0xC9, 0xB9, 0x9C, 0xBA, 0xED, 0x2E,
0xBD, 0x4F, 0xFA, 0xEE, 0xC8, 0xF8, 0xE4, 0x16, 0x82, 0x63, 0x3B, 0x98, 0xF4, 0x14, 0x30, 0x38,
0x07, 0x36, 0x84, 0x3D, 0x0C, 0x36, 0x32, 0xEA, 0x55, 0xA6};
for (int i = 0; i < 42; ++i){
for (int j = 0; j < 256; ++j){
if (Table[j] == enc[i]){
printf("%c", (j - rand() % 255));
}
}
}
return 0;
}
// flag{B8452786-DD8E-412C-E355-2B6F27DAB5F9}
地址爆红;
花指令,JZ和JNZ互补跳转,将call patch掉就好,
再用P创建函数 F5反编译
修补后可以看到传入的一个是我们的输入,一个是输入长度,也就是后面的a1和a2;
第一个for 前面是Byte类型的,默认一个字节,这里在后面写exp的时候:
v5[i] = (~(i ^ 25)) & 0xff
使用 & 0xff 做一个掩码操作; 0xff在二进制中是11111111,跟0xff进行与操作可以将其他位数清零,确保只剩下8bit大小的;
第二个for 就是,我们的输入作为v6的索引,将得到的值存入a1中,最后返回,a1和&unk_4020比较,也就是说unk_4020的值在v6中的索引,就是我们要的flag。
最后还有个md5
exp:
import hashlib
enc = [0xD0, 0xD0, 0x85, 0x85, 0x80, 0x80, 0xC5, 0x8A, 0x93, 0x89,
0x92, 0x8F, 0x87, 0x88, 0x9F, 0x8F, 0xC5, 0x84, 0xD6, 0xD1,
0xD2, 0x82, 0xD3, 0xDE, 0x87]
v5 = [0] * 256
for i in range(256):
v5[i] = (~(i ^ 25)) & 0xff
flag = ''
for i in range(len(enc)):
flag += chr(v5.index((enc[i]) % 256))
print(flag)
print(hashlib.md5(flag.encode()).hexdigest())
# flag{d780c9b2d2aa9d40010a753bc15770de}
观察下面:整体的逻辑就是对输入的字符串进行四次加密,然后和buf1和v9比较;成功就是flag的;
这里注意的是Buf1 和 v9 在内存上的布局是连在一起的,也就是v9也可以看作是Buf1的一部分;
观察一下四个加密过程;
第一个:
分三个if,分别是大写字母,小写字母和数字,简化一下就是分别做加法;
第二个:
和“NewStarCTF”里的值循环相加,用了一个 i % 10;
第三个和第四个分别用了一个取反和,一个*52;
因为 第四个加密 *52 没有办法逆向,所以这里采用单字符爆破的方式;
#include
#include
int main(void){
int enc1[100] = {0xE8, 0x80, 0x84, 0x08, 0x18, 0x3C, 0x78, 0x68, 0x00, 0x70,
0x7C, 0x94, 0xC8, 0xE0, 0x10, 0xEC, 0xB4, 0xAC, 0x68, 0xA8,
0x0C, 0x1C, 0x90, 0xCC, 0x54, 0x3C, 0x14, 0xDC, 0x30};
char enc2[20] = "NewStarCTF";
char flag[50];
int i, j;
for ( i = 0; i < 29; ++i){
for (j = 32; j < 127; j++){
int tmp = j;
if (j >= 'A' && j <= 'Z'){
j = (j - 'A' + 13) % 26 + 'A';
}
else if (j >= '0' && j <= '9'){
j = (j - '0' + 3) % 10 + '0';
}
else if (j >= 'a' && j <= 'z'){
j = (j - 'a' + 8) % 26 + 'a';
}
j += enc2[i % strlen(enc2)];
j = ~j;
j = (j*52)&0xff;
if (j == enc1[i]){
if ((tmp >= 'A' && tmp <= 'Z') ||(tmp >= 'a' && tmp <= 'z') ){
printf("%c", tmp);
}
}
j = tmp;
}
}
return 0;
}
上面是爆破脚本;
有一点要注意;代码处加密时都用了 (BYTE) 限制了 数据的大小为 一个字节;
所以最后这里要 &0xff 以确保52后不会超出可读ascii的范围;
j = (j*52)&0xff;
也可以使用__uint8_t来声明变量,__uint8_t 是一个无符号 8 位整数类型;通常用于确保一个变量始终被解释为一个 8 位无符号整数,而不受编译器的默认类型推断影响;但是我电脑配置环境不允许我用/(ㄒoㄒ)/~~哎;
没了没了,还有最后一题安卓逆向的写不来,博主还不会/(ㄒoㄒ)/~~加油咯。