Back
Featured image of post Reversing.kr writeups

Reversing.kr writeups

跟着大佬们一起卷

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 逆向

利用 RightWrong 字符串找到逻辑

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

Built with Hugo
Theme Stack designed by Jimmy