2014/07/18 10:53 12,800 aplib.dll
2. 提取pyc, 反编译py文件
2.1 解压CM.exe ,得到1个python 打包的exe。 用IDA静态分析了一把,然后下断点调试,里面有aPsafe_depack(), 从0x8000 偏移量开始, 解压了 0x28131 字节 (164145 字节), 解压到0x140000000, 解压后的size是2734080 bytes,也就是267 KB。 aPsafe_depack(buf, 0x28131i64, v5, (unsigned int)fp); v6 = sub_7FF7A57C128C(v5); 写了个脚本把exe提取出来: idadump.py import idaapi start_address=0x140000000 data_length=2734080 data = idaapi.dbg_read_memory(start_address, data_length) fp = open('d:\\temp\\dump\\dump.bin', 'wb') fp.write(data) fp.close()
2.2 这个dump_267k_bin.exe,看了一番, 发现是python编译pyc,然后打包做成的exe。然后发现CM.exe每次启动,会开启一个新的进程CM。 有一堆python的库解压到temp目录底下, 名字是 _MEI????? 比如 _MEI26002 的目录。 但是没发现CM.pyc文件,只有一个很小的 CM.exe.manifest 1,027 CM.exe.manifest 折腾这个dump_267k_bin.exe, 没找到突破口。 所以又继续分析CM.exe. 2.3 找python反编译的方法 python-exe-unpacker, decompile-py2exe, pyinstxtractor 等等。 试了好几个都不行。 搜到下面这篇, 帮上了忙。 https://laucyun.com/33359ed9f725529ac9b606d054c8459d.html 方法是: step 1: 通过pyi-archive_viewer工具提取出.pyc文件,然后将.pyc文件解码成.py文件。 step 2: 同时要注意,PyInstaller在打包.pyc时,会把.pyc的magic和时间戳去掉,所以需要手工修复。做法是抄一个正常的pyc文件,用winhex打开,把前面16个bytes补充到目标上。 step 3: 修复完成后,使用 uncompyle6 将.pyc文件解码成.py文件。
step 1:
C:\Python37\Lib\site-packages\PyInstaller\utils\cliutils>archive_viewer.py D:\work\pediy_2019_ctf\2019q4\02-tea-ZhongyaRing\tea1\CM.exe
pos, length, uncompressed, iscompressed, type, name
[(0, 252, 326, 1, 'm', 'struct'),
(252, 1104, 1807, 1, 'm', 'pyimod01_os_path'),
(1356, 4372, 9350, 1, 'm', 'pyimod02_archive'),
(5728, 7422, 18670, 1, 'm', 'pyimod03_importers'),
(13150, 1863, 4171, 1, 's', 'pyiboot01_bootstrap'),
(15013, 754, 1178, 1, 's', 'CM'),
(15767, 480, 1027, 1, 'b', 'CM.exe.manifest'),
(16247, 20112, 36352, 1, 'b', 'Crypto\\Cipher\\_AES.cp37-win_amd64.pyd'),
(36359, 7603, 15872, 1, 'b', 'Crypto\\Hash\\_SHA256.cp37-win_amd64.pyd'),
(2406251, 1598380, 3745280, 1, 'b', 'python37.dll'),
(4004631, 49687, 137216, 1, 'b', 'pywintypes37.dll'),
... 省略若干行 ...
(5002231, 0, 0, 0, 'o', 'pyi-windows-manifest-filename CM.exe.manifest'),
(5002231, 214384, 781191, 1, 'x', 'base_library.zip'),
(5216615, 1467669, 1467669, 0, 'z', 'PYZ-00.pyz')]
?
U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit
? X CM
to filename? CM.pyc
? O PYZ-00.pyz
Name: (ispkg, pos, len)
{'CMpub': (0, 17, 2250),
'Crypto': (1, 2267, 597),
... 省略若干行 ...
'general': (0, 387075, 918),
'genericpath': (0, 387993, 1775),
'getopt': (0, 389768, 3121),
... 省略若干行 ...
'xml.sax.xmlreader': (0, 1431869, 5400),
'zipfile': (0, 1437269, 23205)}
?
U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit
? X CMpub
to filename? CMpub.pyc
? X general
to filename? general.pyc
?
step 2
: 修复pyc;
C:\Python37\Lib\site-packages\PyInstaller\utils\cliutils>archive_viewer.py D:\work\pediy_2019_ctf\2019q4\02-tea-ZhongyaRing\tea1\CM.exe
pos, length, uncompressed, iscompressed, type, name
[(0, 252, 326, 1, 'm', 'struct'),
(252, 1104, 1807, 1, 'm', 'pyimod01_os_path'),
(1356, 4372, 9350, 1, 'm', 'pyimod02_archive'),
(5728, 7422, 18670, 1, 'm', 'pyimod03_importers'),
(13150, 1863, 4171, 1, 's', 'pyiboot01_bootstrap'),
(15013, 754, 1178, 1, 's', 'CM'),
(15767, 480, 1027, 1, 'b', 'CM.exe.manifest'),
(16247, 20112, 36352, 1, 'b', 'Crypto\\Cipher\\_AES.cp37-win_amd64.pyd'),
(36359, 7603, 15872, 1, 'b', 'Crypto\\Hash\\_SHA256.cp37-win_amd64.pyd'),
(2406251, 1598380, 3745280, 1, 'b', 'python37.dll'),
(4004631, 49687, 137216, 1, 'b', 'pywintypes37.dll'),
... 省略若干行 ...
(5002231, 0, 0, 0, 'o', 'pyi-windows-manifest-filename CM.exe.manifest'),
(5002231, 214384, 781191, 1, 'x', 'base_library.zip'),
(5216615, 1467669, 1467669, 0, 'z', 'PYZ-00.pyz')]
?
U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit
? X CM
to filename? CM.pyc
? O PYZ-00.pyz
Name: (ispkg, pos, len)
{'CMpub': (0, 17, 2250),
'Crypto': (1, 2267, 597),
... 省略若干行 ...
'general': (0, 387075, 918),
'genericpath': (0, 387993, 1775),
'getopt': (0, 389768, 3121),
... 省略若干行 ...
'xml.sax.xmlreader': (0, 1431869, 5400),
'zipfile': (0, 1437269, 23205)}
?
U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit
? X CMpub
to filename? CMpub.pyc
? X general
to filename? general.pyc
?
step3: uncompyle6 反编译pyc,最后得到3个文件: CM.py CMpub.py general.py
3. 分析算法, 分解 RSA Numbers
发现CMpub里有10组768 bits的 RSA-N 和 E, 记作 n[0] ~ n[9] , e[0] ~ e[9]。 其中n[0], e[0]又被分成4组192 bit的small n使用。
一共 9个768 bits的N, 4个192bits的N
。
修改CM.py 让它打印中间过程数据,判定出算法主要流程: 取username的SHA256值, 生成了一个序列seq,大概70多个字节,序列每一项的值为0-9。 0只用到一次,而1~9可以用到多次。 估计作者意图是9个RSA-768有一些弱点,让人通过各种方法分解n得到p,q; 或者通过攻击特定的e,n得到d。 于是就用各种工具去分解,去测试。把google到github上的CTF RSATools都轮了一遍。 从factordb 或者wiki的 RSA_number 分解了n[9], 是RSA Lab公布的挑战中,以及被分解的RSA-768; 通过两两组合求公约数, gcd(n[i], n[j]) (i, j 从1到8) ,分解了 n[7] 和 n[8] ; 通过RDLP轮了一遍,得到n[1]是小因子组合; 通过msieve -v -e 分解了 n[2] 和 n[3] , 大概花了15分钟每个。 通过CTFTools 的boneh_durfee.sage 从e/n 分解了 n[6]; 于是只剩下n[4]和n[5]; 一边分解,一边又刷了一下factordb,发现n[4]已经有了p,q, 不知道谁上传的,发现p,q很解近, p/q = 384 bits, p-q ~= 206 bits ,应该使用Fermat 分解法。 于是只剩下n[5]。 这里面n[5]也是蒙了很久。
一直在想:作者的成语接龙,循环的脑洞是什么? 以为9个n有什么联系, 比如前一个n的Low Bits 和后一个n的 High Bits能连起来,最终形成一个圈。 反正试了很久都没对准脑电波。 可能是:不需要d,就只用e,n 也能解?
这时候碰巧也搜到关键字: cycling attack RSA 或者 Cycle attack on RSA 的网页。
就是拿e,n 不断循环powmod,密文循环若干次,能得到明文。
试了一下3000次循环, 发现存在固定周期973。伪代码:
/// find cycle ////
int maxk = 3000;
Big c0 = rand();
Big c = c0;
int i = 5;
for(int j = 1; j <= maxk; j++)
{
c = pow(c, e[i], n[i]);
// k = 973
if(c == c0)
{
printf("c = c0, find i= %d, j = %d\n", i, j);
}
if(c == 1)
{
printf("c = 1, find i= %d, j = %d\n", i, j);
}
}
c = c0, find i= 5, j = 973 c = c0, find i= 5, j = 1946 c = c0, find i= 5, j = 2919
就考虑, 那么就是 e*d = e^973 满足 e*d = 1 mod (lcm(p-1)*(q-1)) , 从而可用的 d=e^972
但是这个d太大了, 不方便。所以还是得从 e,d 分解出n, 求得合适的 d < n才好。
/// find cycle ////
int maxk = 3000;
Big c0 = rand();
Big c = c0;
int i = 5;
for(int j = 1; j <= maxk; j++)
{
c = pow(c, e[i], n[i]);
// k = 973
if(c == c0)
{
printf("c = c0, find i= %d, j = %d\n", i, j);
}
if(c == 1)
{
printf("c = 1, find i= %d, j = %d\n", i, j);
}
}
c = c0, find i= 5, j = 973 c = c0, find i= 5, j = 1946 c = c0, find i= 5, j = 2919
方法一: 原理可以参考 我2017年的贴: https://bbs.pediy.com/thread-218631.htm 从 (N,E,D) 计算P ,Q 。 也有至少2种方法。 伪代码描述如下: // 通过求解 w^2 = 1 mod n, we get p=gcd(w-1, n),有1/2概率求出非平凡因子p,一般2次即可 // k*phi = ed -1 = r * 2^s // a = rand(n) // w = a^r (mod n) // for ( j = 0 ; j < s ; j++) // if (w != 1) && (w != n-1 ) // if w^2 = 1 (mod n) // gcd(w-1,n) or gcd(w+1 ,n ) will have 1/2 chance to get p.q // 随机选择a,一般 2-3次, 即可求出p
///// method 1 ////////////
// find w^2 = 1 mod n, then gcd(w + 1, n) = p or gcd(w - 1, n) = p
printf("for p[5], method 1 \n", tm);
tm = time(NULL);
ek973 = pow(e[5], 973);
ed = ek973 - 1; // k*phi = ed - 1
int s = 0;
while(ed % 2 == 0)
{
s++;
ed /= 2;
}
printf("s=%d, get: ed - 1 = r * 2^s \n", s);
find = 0;
for(int t = 2; t <= 7; t++) // try t=2, 3, 4, 5 ,...
{
w = pow(t, ed, n[5]);
int is = 0;
while(is < s)
{
is++;
w = pow(w, 2, n[5]);
Big p = gcd(w + 1, n[5]);
if(p > 1)
{
char buf[1024] = {0};
printf("try t =%d, s = %d ,get p\n", t, s);
buf << p;
printf("%s\n", buf);
find = 1;
break;
}
}
if(find == 1)
break;
}
tm = time(NULL) - tm;
printf("method 1, time cost: %d seconds\n\n", tm);
///// method 1 ////////////
// find w^2 = 1 mod n, then gcd(w + 1, n) = p or gcd(w - 1, n) = p
printf("for p[5], method 1 \n", tm);
tm = time(NULL);
ek973 = pow(e[5], 973);
ed = ek973 - 1; // k*phi = ed - 1
int s = 0;
while(ed % 2 == 0)
{
s++;
ed /= 2;
}
printf("s=%d, get: ed - 1 = r * 2^s \n", s);
find = 0;
for(int t = 2; t <= 7; t++) // try t=2, 3, 4, 5 ,...
{
w = pow(t, ed, n[5]);
int is = 0;
while(is < s)
{
is++;
w = pow(w, 2, n[5]);
Big p = gcd(w + 1, n[5]);
if(p > 1)
{
char buf[1024] = {0};
printf("try t =%d, s = %d ,get p\n", t, s);
buf << p;
printf("%s\n", buf);
find = 1;
break;
}
}
if(find == 1)
break;
}
tm = time(NULL) - tm;
printf("method 1, time cost: %d seconds\n\n", tm);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2019-12-9 16:27
被readyu编辑
,原因:
上传的附件: