CSHOP
按一下回车就通关了
如果要查看逻辑的话,需要先使用 de4dot 脱壳,然后用 dnSpy 打开
private void Form1_Load(object sender, EventArgs e)
{
this.lblSu.Text = " ";
this.lblGu.Text = " ";
this.lblNu.Text = " ";
this.lblKu.Text = " ";
this.lblZu.Text = " ";
this.lblMu.Text = " ";
this.lblTu.Text = " ";
this.ppppp.Text = " ";
this.lblGu.Text = " ";
this.lblQu.Text = " ";
this.ppppp.Text = " ";
this.lblTu.Text = " ";
this.lblXu.Text = " ";
}
// Token: 0x06000004 RID: 4 RVA: 0x000021B0 File Offset: 0x000003B0
private void btnStart_Click(object sender, EventArgs e)
{
this.lblSu.Text = "W";
this.lblGu.Text = "5";
this.lblNu.Text = "4";
this.lblKu.Text = "R";
this.lblZu.Text = "E";
this.lblMu.Text = "6";
this.lblTu.Text = "M";
this.ppppp.Text = "I";
this.lblGu.Text = "P";
this.lblQu.Text = "S";
this.ppppp.Text = "P";
this.lblTu.Text = "6";
this.lblXu.Text = "S";
}
一开始的 Text
被设置为了空格,而在点击了某个键之后就会设置为具体的值,所以按一下回车就能看到 flag
Position
这道题是 MFC 逆向
利用 Right
和 Wrong
字符串找到逻辑
void __thiscall sub_401CD0(char *this)
{
int v2; // eax
CWnd *v3; // ecx
v2 = sub_401740((int)this);
v3 = (CWnd *)(this + 188);
if ( v2 )
CWnd::SetWindowTextW(v3, L"Correct!");
else
CWnd::SetWindowTextW(v3, L"Wrong");
}
sub_401740
就是验证逻辑
CWnd::GetWindowTextW(a1 + 304, &v50);
if ( *(_DWORD *)(v50 - 12) == 4 )
第一步验证输入长度是否为 4
v3 = 0;
while ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, v3) >= 0x61u
&& (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, v3) <= 0x7Au )
{
if ( ++v3 >= 4 )
{
LABEL_7:
v4 = 0;
while ( 1 )
{
if ( v1 != v4 )
{
v5 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, v4);
if ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, v1) == v5 )
goto LABEL_2;
}
随后利用双重循环,约束输入为小写字母并且没有重复
CWnd::GetWindowTextW(a1 + 420, &v51);
if ( *(_DWORD *)(v51 - 12) == 11 && (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&v51, 5) == 45 )
{
v6 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, 0);
v40 = (v6 & 1) + 5;
v48 = ((v6 & 0x10) != 0) + 5;
v42 = ((v6 & 2) != 0) + 5;
v44 = ((v6 & 4) != 0) + 5;
v46 = ((v6 & 8) != 0) + 5;
v7 = ATL::CSimpleStringT<wchar_t,1>::GetAt(&v50, 1);
v32 = (v7 & 1) + 1;
v38 = ((v7 & 0x10) != 0) + 1;
v34 = ((v7 & 2) != 0) + 1;
v8 = ((v7 & 4) != 0) + 1;
v36 = ((v7 & 8) != 0) + 1;
Buffer = (wchar_t *)ATL::CSimpleStringT<wchar_t,1>::GetBuffer(v52);
itow_s(v40 + v8, Buffer, 0xAu, 10);
v10 = ATL::CSimpleStringT<wchar_t,1>::GetAt(v52, 0);
if ( (unsigned __int16)ATL::CSimpleStringT<wchar_t,1>::GetAt(&v51, 0) == v10 )
上面的 if
语句要求了 serial,下面的 if
要求了 v40 + v8
需要等于输入的第一位
同样的逻辑,约束了 v46 + v36
等于输入的第二位
直接写个脚本爆破一下
for v6 in range(ord('a'), ord('z') + 1):
for v7 in range(ord('a'), ord('z') + 1):
v40 = (v6 & 1) + 5
v48 = ((v6 & 0x10) != 0) + 5
v42 = ((v6 & 2) != 0) + 5
v44 = ((v6 & 4) != 0) + 5
v46 = ((v6 & 8) != 0) + 5
v32 = (v7 & 1) + 1
v38 = ((v7 & 0x10) != 0) + 1
v34 = ((v7 & 2) != 0) + 1
v8 = ((v7 & 4) != 0) + 1
v36 = ((v7 & 8) != 0) + 1
if (v40 + v8 == 7 and v46 + v36 == 6 and v42 + v38 == 8 and v44 + v32 == 7 and v48 + v34 == 6):
print (chr(v6) + chr(v7))
# bu
# cq
# ft
# gp
print ()
for v19 in range(ord('a'), ord('z') + 1):
# for v20 in range(ord('a'), ord('z') + 1):
v20 = ord('p')
v41 = (v19 & 1) + 5
v49 = ((v19 & 0x10) != 0) + 5
v43 = ((v19 & 2) != 0) + 5
v45 = ((v19 & 4) != 0) + 5
v47 = ((v19 & 8) != 0) + 5
v33 = (v20 & 1) + 1
v39 = ((v20 & 0x10) != 0) + 1
v35 = ((v20 & 2) != 0) + 1
v21 = ((v20 & 4) != 0) + 1
v37 = ((v20 & 8) != 0) + 1
if (v41 + v21 == 7 and v47 + v37 == 7 and v43 + v39 == 7 and v45 + v33 == 7 and v49 + v35 == 6):
print (chr(v19) + chr(v20))
# mp
本题多解,但平台好像只认 bump
Ransomware
脱壳,然后发现有大量的无效指令
push eax
pop eax
push ebx
pop ebx
pusha
popa
要查看 main 的逻辑直接把开头几条指令 patch 下来就行
重新生成一个函数就能看到反编译结果了
根据 readme 可知,被加密的是一个 exe 文件,所以把 file
的前十六个字节作为明文,run.exe
的前十六个字节作为密文,算一下密钥
a = 'DE C0 1B 8C 8C 93 9E 86 98 97 9A 8C 73 6C 9A 8B'.split()
a = [int(i, 16) for i in a]
# a = [222, 192, 27, 140, 140, 147, 158, 134, 152, 151, 154, 140, 115, 108, 154, 139]
p = '4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00'.split()
p = [int(i, 16) for i in p]
# p = [77, 90, 144, 0, 3, 0, 0, 0, 4, 0, 0, 0, 255, 255, 0, 0]
for i, j in zip(a, p):
print (chr((~i & 0xff) ^ j), end='')
# letsplaychesslet
显然 Key 是 letsplaychess
运行一下得到新的 exe,脱壳反汇编
HateIntel
根据字符串找到关键函数
查看加密函数
总共 4 轮,加密是单字节循环左移 4 位
解密
cipher = [
0x44, 0xF6, 0xF5, 0x57, 0xF5, 0xC6, 0x96, 0xB6, 0x56, 0xF5,
0x14, 0x25, 0xD4, 0xF5, 0x96, 0xE6, 0x37, 0x47, 0x27, 0x57,
0x36, 0x47, 0x96, 0x03, 0xE6, 0xF3, 0xA3, 0x92
]
def dec_byte(a, b):
return ((a >> b) | (a << (8 - b))) & 0xff
for c in cipher:
print (chr(dec_byte(c, 4)), end='')
# Do_u_like_ARM_instructi0n?:)
Multiplicative
反编译结果
public class JavaCrackMe {
public static final strictfp void main(String[] arg6) {
Class v1 = JavaCrackMe.class;
__monitor_enter(v1);
try {
System.out.println("Reversing.Kr CrackMe!!");
System.out.println("-----------------------------");
System.out.println("The idea came out of the warsaw\'s crackme");
System.out.println("-----------------------------\n");
if(((long)Long.decode(arg6[0])) * 0x6869L == -1536092243306511225L) {
System.out.println("Correct!");
}
else {
System.out.println("Wrong");
}
}
catch(Exception v0_1) {
try {
System.out.println("Please enter a 64bit signed int");
}
catch(Throwable v0) {
__monitor_exit(v1);
throw v0;
}
}
catch(Throwable v0) {
__monitor_exit(v1);
throw v0;
}
__monitor_exit(v1);
}
}
参数进行乘法后等于某个数字
由于并不能整除,所以运算有溢出,一个方法是直接求逆元,另一种方法是爆破溢出位
方法一:
In [3]: from libnum import *
In [5]: tar = -1536092243306511225 & 0xffffffffffffffff
In [8]: hex(tar * invmod(0x6869, 0x10000000000000000) % 0x10000000000000000)
Out[8]: '0x83676f67696c676f'
In [12]: (-0x83676f67696c676f) & 0xffffffffffffffff
Out[12]: 8978084842198767761
In [13]: hex(8978084842198767761)
Out[13]: '0x7c98909896939891'
In [14]: hex(-8978084842198767761 & 0xffffffffffffffff)
Out[14]: '0x83676f67696c676f'
方法二:
In [23]: i = 0
In [24]: while True:
...: if (0xeaaeb43e477b8487 + (i << 64)) % 0x6869 == 0:
...: break
...: i += 1
...:
In [25]: i
Out[25]: 13719
In [26]: hex((0xeaaeb43e477b8487 + (i << 64)) // 0x6869)
Out[26]: '0x83676f67696c676f'
In [27]: -((0xeaaeb43e477b8487 + (i << 64)) // 0x6869) & 0xffffffffffffffff
Out[27]: 8978084842198767761
运行结果
$ java -jar JavaCrackMe.jar -8978084842198767761
Reversing.Kr CrackMe!!
-----------------------------
The idea came out of the warsaw's crackme
-----------------------------
Correct!
LOTTO
输入需要通过的是一个随机数的逻辑
直接修改 ip
跳过即可
夜影还给出了一个有意思的方法:另写了一个程序,将相同
srand(time64(0))
生成的随机数echo
传入这个程序
ImagePrc
MFC 逆向
根据字符串 Wrong
可以定位到关键函数
注意到
GetObjectA(hbm, 24, pv);
memset(&bmi, 0, 0x28u);
bmi.bmiHeader.biHeight = cLines;
bmi.bmiHeader.biWidth = v16;
bmi.bmiHeader.biSize = 40;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = 0;
GetDIBits(hdc, (HBITMAP)hbm, 0, cLines, 0, &bmi, 0);
v8 = operator new(bmi.bmiHeader.biSizeImage);
GetDIBits(hdc, (HBITMAP)hbm, 0, cLines, v8, &bmi, 0);
v9 = FindResourceA(0, (LPCSTR)0x65, (LPCSTR)0x18);
v10 = LoadResource(0, v9);
v11 = LockResource(v10);
v12 = 0;
v13 = v8;
v14 = v11 - (_BYTE *)v8;
while ( *v13 == v13[v14] )
{
++v12;
++v13;
if ( v12 >= 90000 )
{
sub_401500(v8);
return 0;
}
}
从资源中加载 90000 字节并比较
动调或者 Resource Hacker
dump 下来
判断要求绘制的位图需要相同
PIL 转成图片
from PIL import Image
fp = open('export_results.txt', 'rb')
pic = fp.read()
im = Image.new("RGB", (0xc8, 0x96))
for i in range(0, len(pic), 3):
im.putpixel(((i // 3) % 0xc8, (i // 3) // 0xc8), (pic[i], pic[i + 1], pic[i + 2]))
im = im.transpose(Image.FLIP_TOP_BOTTOM)
im.show()
Flash Encrypt
使用 ffdec
反编译
可以看到每个帧的 AS 脚本,使用自动去混淆即可
// frame1
on(release){
if(spw == 1456)
{
gotoAndPlay(3);
}
else
{
_root.spw = "";
}
}
// frame2
on(release){
if(spwd == 8)
{
spw /= spwd;
spwd = "";
gotoAndPlay(6);
}
}
// frame3
on(release){
if(spwd == 25)
{
spw *= spwd;
spwd = "";
gotoAndPlay(4);
}
}
// frame4
on(release){
if(spwd == 44)
{
spw += spwd;
spwd = "";
gotoAndPlay(2);
}
}
// frame5
on(release){
if(spwd == 20546)
{
spw %= spwd;
spwd = "";
gotoAndPlay(7);
}
}
// frame6
on(release){
if(spwd == 88)
{
spw *= spwd;
spwd = "";
gotoAndPlay(5);
}
}
gotoAndPlay
中的数字为帧数
所以输入的顺序为 1456, 25, 44, 8, 88, 20546
因为不想装 flash,所以想自己写脚本把这个结果算出来,但是发现怎么算都不对
最后发现原因是 flash 是用字符串进行的计算,猜测 flash 中 "1" == 1
,需要根据运算符猜测是字符串运算还是整数运算还是浮点数运算
一个能得出正确答案的计算过程(加法为字符串拼接,除法保留小数)
>>> 1456 * 25
36400
>>> 3640044 / 8
455005.5
>>> 3640044 / 8 * 88
40040484.0
>>> 3640044 / 8 * 88 % 20546
16876.0
16876 就是 key