Reverse(八)

Take the maze

https://ctf.bugku.com/challenges/detail/id/126.html

ida32定位主函数

int __cdecl main_0(int argc, const char **argv, const char **envp)
{
  int i; // [esp+D0h] [ebp-48h]
  char Str[16]; // [esp+DCh] [ebp-3Ch] BYREF
  char v6; // [esp+ECh] [ebp-2Ch]

  sub_45E9C1("welcome to zsctf!\n");
  sub_45E9C1("show me your key:");
  sub_45D846("%s", Str);
  if ( (unsigned __int8)sub_45B1C7() )
    j___loaddll(0);
  if ( j__strlen(Str) == 24 )    // 输入为24位字符串
  {
    v6 ^= 1u;
    sub_45C748(Str);    // 做了什么修改
    for ( i = 0; i < 24; ++i )    // 循环判断是否为小写字母和数字,不是就报错
    {
      if ( (Str[i] < 48 || Str[i] > 57) && (Str[i] < 97 || Str[i] > 102) )
        goto LABEL_4;
    }
    if ( sub_45E593(Str) )    // 核心函数,判断字符串
    {
      sub_45E9C1("done!!!The flag is your input\n");
      sub_45D9C7(4);
      sub_45E1B5();
    }
    else
    {
      sub_45BF7D();
    }
    return 0;
  }
  else
  {
LABEL_4:
    sub_45BF7D();
    return 0;
  }
}

先分析判断函数sub_45E593

// attributes: thunk
int __cdecl sub_45E593(int a1)
{
  return sub_463480(a1);
}

BOOL __cdecl sub_463480(int a1)
{
  BOOL result; // eax 返回一个bool值
  int v2; // [esp+10h] [ebp-100h]
  int v3; // [esp+10h] [ebp-100h]
  int v4; // [esp+10h] [ebp-100h]
  int v5[3]; // [esp+D8h] [ebp-38h] BYREF
  int v6; // [esp+E4h] [ebp-2Ch]
  int v7; // [esp+F0h] [ebp-20h]
  int v8; // [esp+FCh] [ebp-14h]
  int v9; // [esp+108h] [ebp-8h]

  v9 = 0;
  v6 = 0;
  v5[0] = 0;
  while ( 2 )
  {
    v2 = v9++;    // 每两个加一次
    if ( v2 >= 12 )        // 输入遍历完检查v5[0]是否为311
      return v5[0] == 311;
    if ( (unsigned __int8)sub_45B1C7() )
      j___loaddll(0);
    v3 = *(char *)(v6 + a1);    // 取一个字符
    ++v6;
    switch ( v3 )
    {
      case '0':        // 判断数字
        v8 = 0;
        goto LABEL_12;
      case '1':
        v8 = 1;
        goto LABEL_12;
      case '2':
        v8 = 2;
        goto LABEL_12;
      case '3':
        v8 = 3;
        goto LABEL_12;
      case '4':
        v8 = 4;
LABEL_12:
        v4 = *(char *)(v6 + a1);    // 又取了一个字符
        ++v6;
        switch ( v4 )    // 这些字符范围为5-9,a-f,共11种
        {
          case '5':
            v7 = 5;
            goto LABEL_25;
          case '6':
            v7 = 6;
            goto LABEL_25;
          case '7':
            v7 = 7;
            goto LABEL_25;
          case '8':
            v7 = 8;
            goto LABEL_25;
          case '9':
            v7 = 9;
            goto LABEL_25;
          case 'a':
            v7 = 10;
            goto LABEL_25;
          case 'b':
            v7 = 11;
            goto LABEL_25;
          case 'c':
            v7 = 12;
            goto LABEL_25;
          case 'd':
            v7 = 13;
            goto LABEL_25;
          case 'e':
            v7 = 14;
            goto LABEL_25;
          case 'f':
            v7 = 15;
LABEL_25:
            switch ( byte_541168[v8] )        // byte_541168值为delru0123456789,v8只能为0-4,即取delru,正好对应下面的cases
            {            // byte_541168[v7]中v7范围5-15,只能取0-9,ascii值再减去48,就是0-9
              case 'd':
                sub_45CC4D(v5, byte_541168[v7] - 48);    // 由上面v5判断可知,这四个函数会修改v5值
                continue;
              case 'l':
                sub_45D0A3(v5, byte_541168[v7] - 48);
                continue;
              case 'r':
                sub_45CB0D(v5, byte_541168[v7] - 48);
                continue;
              case 'u':
                sub_45D0E9(v5, byte_541168[v7] - 48);
                continue;
              default:
                result = 0;
                break;
            }
            break;
          default:
            result = 0;
            break;
        }
        break;
      default:
        result = 0;
        break;
    }
    return result;
  }
}

查看d——sub_45CC4D函数

// attributes: thunk
int __cdecl sub_101CC4D(int *a1, int a2)
{
  return sub_1022D60(a1, a2);
}

int __cdecl sub_1022D60(int *a1, int a2)    // a1是数组第一个值,a2是0-9,表示步长
{
  int result; // eax
  int i; // [esp+E0h] [ebp-8h]

  for ( i = *a1; a2--; i += 26 )    // 每循环一次+26,大概是一行?
  {
    result = i / 26;    // 看是第几行
    if ( i / 26 > 10 )    // i/26=11的时候说明超了,即有12行
      return result;    // 返回无所谓,没东西接收
    result = i;
    if ( dword_1100548[i] != dword_1100068[i] )    // 如果不等就退出函数,说明没法再往下走,i不能再+26
      return result;
  }
  result = (int)a1;
  *a1 = i;    // a1存放的是此时的位置
  return result;
}

查看l——sub_45D0A3

// attributes: thunk
int __cdecl sub_101D0A3(int a1, int a2)
{
  return sub_10233D0(a1, a2);
}

int __cdecl sub_10233D0(int *a1, int a2)
{
  int result; // eax
  int i; // [esp+E0h] [ebp-8h]

  for ( i = *a1; a2--; --i )    // 每次-1,说明向左
  {
    result = i / 26;
    if ( i % 26 < 1 )    // 到最左边则退出
      return result;
    result = i;
    if ( dword_11004DC[i] != dword_10FFFFC[i] )    // 如果不等就退出函数,说明没法再往左走,i不能再-1
      return result;
  }
  result = (int)a1;
  *a1 = i;
  return result;
}

其他同理:总结来说就是走迷宫,方向包括上下左右,最后需要到311,即12*26矩阵最右下角

编写IDC脚本(看wp写的,ida里没法看到具体值的样子)

auto i;
for (i = 0; i < 322; i++) {
    if (Dword(0x1100548+4*i) != Dword(0x1100068+4*i))
        Message(".");
    else
        Message("D");
    if (Dword(0x11004DC+4*i) != Dword(0x10FFFFC+4*i))
        Message(".");
    else
        Message("L");
    if (Dword(0x11004E4+4*i) != Dword(0x1100004+4*i))
        Message(".");
    else
        Message("R");
    if (Dword(0x1100478+4*i) != Dword(0x10FFF98+4*i))
        Message(".");
    else
        Message("U");
    Message(" ");
    if ((i+1) % 26 == 0)
        Message("\n");
} 

image-20231206164050211.png

路线:DRDDDRDRRRRRRDDDRRRRDDRRRRRRRRRDRRRR,接着还要转为小写和步长数字,因此结果为d1r1d3r1d1r6d3r4d2r9d1r4,又因为switch做了映射,d对应0,r对应3,其他数字对应自身+5的16进制;

所以加密后的key是06360836063b0839073e0639

最后分析加密key的函数sub_101C748

// attributes: thunk
void __cdecl sub_101C748(void *Src)
{
  sub_10249E0(Src);
}

void __cdecl sub_10249E0(void *Src)
{
  _DWORD *v1; // [esp+D0h] [ebp-2Ch]
  void *v2; // [esp+DCh] [ebp-20h]
  char *v3; // [esp+E8h] [ebp-14h]
  void *Block; // [esp+F4h] [ebp-8h]

  Block = j__malloc(0x40u);
  v3 = (char *)j__malloc(0x40u);
  v2 = j__malloc(0x20u);
  v1 = j__malloc(0x10u);
  j__memset(v3, 0, 0x40u);
  j__memset(Block, 0, 0x40u);
  j__memset(v2, 0, 0x20u);
  j__memmove(Block, Src, 0x18u);
  *v1 = aP;
  v1[1] = v2;
  v1[2] = v3 + 128;
  v1[3] = Block;
  sub_101DCD3(v1);
  j__memmove(Src, Block, 0x18u);
  j__free(Block);
  j__free(v3);
  j__free(v2);
  j__free(v1);
}

根本看不懂,果断选择上道题动态调试的方法

先下断点,F9执行到要求输入(认真找一步步F7走下去直到没法执行,必须输入为止),找到后面的函数调用(push eax再call)下断点,图里第一处输入Str是计算字符串长度,会和0x18(24)比较,je来跳转

image-20231206174038201.png

根据伪代码,第二个push eax然后call,且eax值为我们输入的24个1,此处call的函数即为字符串加密函数

image-20231206174616591.png

F8单步跳过看发生什么变化,发现直接看看不出来规律只能认真分析

image-20231206174652326.png

重新执行到此处,F7单步进入,好吧。。。看了实在读不懂,wp们也都没讲解怎么分析代码分析出的按位异或,都是案例分析出来的

同时要注意Str后面的存放v6,相当于Str[16]=v6,则v6^1即Str[16]^1,相当于Str溢出把v6覆盖了?所以逆向的时候第十六位还要再异或个1

image-20231206203130371.png

python脚本还原下得到==07154=518?9i<5=6!&!v$#%.==

image-20231206204059769.png

输入程序:程序同一目录下生成二维码png,扫描说flag=input+Docupa

image-20231206204117838.png

代码阅读量真不小,汇编代码调试也花了很长时间,但是定位所需要找的函数越来越熟练了

杰瑞的奶酪

https://ctf.bugku.com/challenges/detail/id/231.html

先运行了下,发现要打印了input value,上来ida32查字符串无果,搜main也没有什么信息,但是幸亏仔细看了下里面的函数,发现对代码做了混淆;

如下的sub_4011A0出现了多次,且传入了好多常量,点击去看第一个

int wmain()
{
  int v0; // eax
  int v1; // eax
  int v2; // eax
  int v3; // eax
  char Str; // [esp+0h] [ebp-18h] BYREF

  sub_4010E0(0, 0, 0, 0, 0);
  v0 = sub_4011A0(aAfxIdM, 8);    // aAfxIdM='afx}|(~id}m' --> input value
  ((void (__cdecl *)(int))dword_417220[5])(v0);
  v1 = sub_4011A0(aU, 6);    // aU='#u' --> %s
  ((void (__cdecl *)(int, char *))dword_417220[6])(v1, &Str);
  sub_401000(&Str);
  v2 = sub_4011A0(aNgmb6, 9);    // aNgmb6='ngmb6X1353M\QUA@W' --> gndk?Q8:<:DUX\HI^
  if ( sub_401060(v2, &Str) )
    v3 = sub_4011A0(aUnOs, 7);    // aUnOs='un`os' --> right
  else
    v3 = sub_4011A0(aWw, 5);    // aWw='`ww' --> err
  ((void (__cdecl *)(int))dword_417220[5])(v3);
  ((void (__cdecl *)(void *))dword_417220[5])(&unk_411198);
  system("pause");
  return 0;
}

sub_4011A0函数:可以看到做了按位异或的操作

char *__cdecl sub_4011A0(char *Str, char a2)
{
  signed int v3; // [esp+0h] [ebp-8h]
  signed int i; // [esp+4h] [ebp-4h]

  v3 = strlen(Str);
  for ( i = 0; i < v3; ++i )
    Str[i] ^= a2;
  return Str;
}

逆向:可以看到正是程序打印的字符串

image-20231206214830096.png

继续深入分析并还原:可知Str是输入的字符串,sub_401000做了加密,sub_401060做了判断

size_t __cdecl sub_401000(char *Str)
{
  size_t result; // eax
  signed int v2; // [esp+0h] [ebp-Ch]
  signed int i; // [esp+4h] [ebp-8h]
  char v4; // [esp+Bh] [ebp-1h]

  result = strlen(Str);
  v2 = result;
  for ( i = 0; i < v2; ++i )
  {
    v4 = Str[i] + 1;
    result = i + v4;
    Str[i] = i + v4;
  }
  return result;
}
// sub_401060可以看出就是strcmp的作用
int __cdecl sub_401060(_BYTE *a1, char *a2)
{
  int result; // eax
  _BYTE *v4; // [esp+4h] [ebp-Ch]
  int i; // [esp+8h] [ebp-8h]
  char v6; // [esp+Eh] [ebp-2h]

  result = (int)a1;
  v4 = a1;
  for ( i = 0; i < 100; ++i )
  {
    v6 = *a2;
    if ( !*v4 && !v6 )
      return 1;
    if ( (char)*v4 != v6 )        // 一个个比
      return 0;
    result = (int)++v4;
    ++a2;
  }
  return result;
}

逆向sub_401000,flag去掉冒号即可

image-20231206222040308.png

杰瑞的下午茶

https://ctf.bugku.com/challenges/detail/id/233.html

难点主要是在动态调试分析,还没有熟练掌握,关键在于追踪我们的输入看传入哪个函数(先push再call),然后看返回(但这道题并没有在原字符串上进行加密,而是加密后存到内存里,后面判断?貌似是这样,因为看到了新的值mov到了ds段寄存器)

首先ida32查看伪代码读不懂,但是可以先大致了解下函数调用,可以看到好多sub_401A31函数,传入参数为97、98

image-20231207152656654.png

再使用ollydbg,断点下在scanf后,输入的是1234

image-20231207151926827.png

单步调试,发现调用了我们的输入作为参数的函数

image-20231207152028948.png

单步步入函数,直到发现开始一个个字符遍历

image-20231207152303612.png

循环过程中每次都和3异或,并存到内存,原字符串并没有改动;or eax eax来判断是否到输入字符串的结尾(\0)

跳出函数后接着向下走,来到一个常量字符串作为参数的函数

image-20231207152840197.png

单步进入,直到看到异或后的字符串开始作循环遍历比较处理

image-20231207153359006.png

被比较的字符串是==eobdxwlgbzjpdllg~==,回到函数调用完后,可以看到if做判断

image-20231207153700020.png

综上,整个代码逻辑很简单,输入字符串按位与3异或,得到的新字符串和eobdxwlgbzjpdllg~比较

python脚本解密

image-20231207153916161.png

3-babyre

https://ctf.bugku.com/challenges/detail/id/252.html

exeinfo查看可知是一个C#net程序,需要用dnspy来反编译,进去就能找到flag,关键是知道要用这个反编译软件

image-20231209200343247.png

其他wp说ce(作弊机器)也可以改点击次数

pyre

https://buuoj.cn/challenges#[GWCTF%202019]pyre

pyc文件反编译为py文件:使用uncompyle6库

image-20231210184105146.png

代码如下:

print 'Welcome to Re World!'
print 'Your input1 is your flag~'
l = len(input1)
for i in range(l):
    num = ((input1[i] + i) % 128 + 128) % 128
    code += num

for i in range(l - 1):
    code[i] = code[i] ^ code[i + 1]

print code
code = ['\x1f', '\x12', '\x1d', '(', '0', '4', '\x01', '\x06', '\x14', '4', ',', 
 '\x1b', 'U', '?', 'o', '6', '*', ':', '\x01', 'D', ';', '%', '\x13']

逆向解密即可

code = [0x1f, 0x12, 0x1d, ord('('), ord('0'), ord('4'), 0x01, 0x06, 0x14, ord('4'), ord(','), 0x1b, ord('U'), ord('?'), ord('o'), ord('6'), ord('*'), ord(':'), 0x01, ord('D'), ord(';'), ord('%'), 0x13]
l = len(code)
for i in range(l-1, 0, -1):
    code[i-1] = code[i-1] ^ code[i]
for i in range(l):
    print(chr(code[i]-i if code[i]-i>0 else code[i]-i+128), end='')

Findit

https://buuoj.cn/challenges#findit

public class MainActivity extends ActionBarActivity {
    public MainActivity() {
        super();
    }

    protected void onCreate(Bundle arg8) {
        super.onCreate(arg8);
        this.setContentView(2130903064);
        this.findViewById(2131034173).setOnClickListener(new View$OnClickListener(new char[]{'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e'}, this.findViewById(2131034174), new char[]{'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', '}'}, this.findViewById(2131034175)) {    // 这里传入了三个字符串变量,第一个input原始值,第二个我们的输入,第三个flag原始值,分别对应后面的this.val$a,this.val$text,this.val$b
            public void onClick(View arg13) {
                int v11 = 17;
                int v10 = 122;
                int v9 = 90;
                int v8 = 65;
                int v7 = 97;
                char[] v3 = new char[v11];
                char[] v4 = new char[38];
                int v0;  // 下面这个循环总结来说,就是遍历字符串,并用新的移位字母替代原来字母
                for(v0 = 0; v0 < v11; ++v0) {
                    if(this.val$a[v0] >= 73 || this.val$a[v0] < v8) {  // ascii值大于等于I或者小于A
                        if(this.val$a[v0] < 105 && this.val$a[v0] >= v7) {  // a~h
                        label_39:
                            v3[v0] = ((char)(this.val$a[v0] + 18));  // s~z/S~Z
                            goto label_44;
                        }

                        if(this.val$a[v0] >= v8 && this.val$a[v0] <= v9 || this.val$a[v0] >= v7 && this.val$a[v0] <= v10) {  // 本来A~Z/a~z,由于进来有限制,实际上I~Z/i~z
                            v3[v0] = ((char)(this.val$a[v0] - 8));  // a~r/A~R,下一句相当于continue
                            goto label_44;
                        }

                        v3[v0] = this.val$a[v0];
                    }
                    else {
                        goto label_39;  // A~H
                    }

                label_44:
                }
                if(String.valueOf(v3).equals(this.val$edit.getText().toString())) {
                    v0 = 0;
                    goto label_18;
                }
                else {
                    this.val$text.setText("答案错了肿么办。。。不给你又不好意思。。。哎呀好纠结啊~~~");
                    return;
                label_18:
                    while(v0 < 38) {
                        if(this.val$b[v0] < v8 || this.val$b[v0] > v9) {
                            if(this.val$b[v0] >= v7 && this.val$b[v0] <= v10) {
                            label_80:
                                v4[v0] = ((char)(this.val$b[v0] + 16));
                                if((v4[v0] <= v9 || v4[v0] >= v7) && v4[v0] < v10) {
                                    goto label_95;
                                }

                                v4[v0] = ((char)(v4[v0] - 26));
                                goto label_95;
                            }

                            v4[v0] = this.val$b[v0];
                        }
                        else {
                            goto label_80;
                        }

                    label_95:
                        ++v0;
                    }
                    this.val$text.setText(String.valueOf(v4));
                }
            }
        });
    }
}

做了简化修改,java代码如下,其实可以直接不管input计算flag

public class findit {
    public static void main(String[] args) {
        int v11 = 17;
        char[] v3 = new char[v11];
        char[] v4 = new char[38];
        int v0;
        char[] a = new char[]{'T', 'h', 'i', 's', 'I', 's', 'T', 'h', 'e', 'F', 'l', 'a', 'g', 'H', 'o', 'm', 'e'};
        char[] b = new char[]{'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', '}'};
        for(v0 = 0; v0 < v11; ++v0) {
            if(a[v0] <= 104 && a[v0] >= 97 || a[v0] <= 72 && a[v0] >= 65) {  // a~h
                v3[v0] = ((char)(a[v0] + 18));  // s~z/S~Z
                continue;
            }

            if(a[v0] >= 73 && a[v0] <= 90 || a[v0] >= 105 && a[v0] <= 122) {
                v3[v0] = ((char)(a[v0] - 8));
                continue;
            }
            v3[v0] = a[v0];
        }
        System.out.println(v3);

        v0 = 0;    // 记得重置
        while(v0 < 38) {
            if(b[v0] >= 97 && b[v0] <= 122 || b[v0] >= 65 && b[v0] <= 90) {
                v4[v0] = ((char)(b[v0] + 16));
                if((v4[v0] > 90 && v4[v0] < 97) || v4[v0] >= 122) {
                    v4[v0] = ((char)(v4[v0] - 26));
                }
            } else {
                v4[v0] = b[v0];
            }
            ++v0;
        }
        System.out.println(v4);
    }
}

得到input是LzakAkLzwXdsyZgew,flag是 flag{c164675262033b4c49bdf7f9cda28a75}

Re2

https://ctf.show/challenges#re2-59

这道题分析代码找密钥倒还简单,关键是得看得出加密算法(算法是RC4我没看出来,密码学太垃圾了,但其实不知道也能逆向,知道的话可以直接调用代码算)

主要是借助ollydbg,ida辅助阅读代码逻辑

首先运行下看特定字符串:

image-20231211164222483.png

接着ollydbg智能搜索找字符串下断点

image-20231211164302279.png

image-20231211164352567.png

F9运行并输入1,直到发现读入flag.txt,但文件不存在,如果不改标志位就会退出,因此手动改下

image-20231211164513932.png

接着继续运行会写enflag.txt,此时会清空文件原本内容,因此记得备份;再继续程序会要求输入密钥

image-20231211164750409.png

先随便输入1234,发现会调用一个函数并传入我们的输入,结合ida可以知道这是个密钥加密函数,里面存储有加密后的密钥字符串

image-20231211164938638.png

python逆向下可以看到密钥是正常英文单词组成

image-20231211165126117.png

再往后就借助ida分析,因为函数较多;比较完密钥后,一个关键函数传入了密钥、flag、enflag等,推测为加密算法函数

image-20231211165337970.png

如上发现sub_4010C8函数把输入的密钥复制了到256个字符存到a3,接着sub_40116D函数a3又做了处理,最后sub_4010EB函数加密

sub_4010F0函数分析:

// attributes: thunk
int __cdecl sub_4010F0(int a1, int a2, int a3)
{
  return sub_401800(a1, a2, a3);
}

int __cdecl sub_401800(int a1, int a2, int a3)
{
  int result; // eax
  int j; // [esp+D0h] [ebp-14h]
  int i; // [esp+DCh] [ebp-8h]

  result = __CheckForDebuggerJustMyCode(&unk_40B027);
  if ( a3 <= 256 )    // 密钥长度小于等于256,不停复制自身直到够256长度
  {
    for ( i = 0; i < 256; ++i )
    {
      *(_BYTE *)(i + a1) = *(_BYTE *)(a2 + i % a3);
      result = i + 1;
    }
  }
  if ( a3 > 256 )    // 大于256
  {
    for ( j = 0; j < 256; ++j )
    {
      *(_BYTE *)(j + a1) = *(_BYTE *)(j + a2);
      result = j + 1;
    }
  }
  return result;
}

sub_4010C8函数分析:

// attributes: thunk
int __cdecl sub_4010C8(int a1)
{
  return sub_401780(a1);
}

int __cdecl sub_401780(int a1)
{
  int result; // eax
  int i; // [esp+D0h] [ebp-8h]

  result = __CheckForDebuggerJustMyCode(&unk_40B027);
  for ( i = 0; i < 256; ++i )
  {
    *(_BYTE *)(i + a1) = i;        // 构造了0~256的数组存入a1,即外面调用的a2
    result = i + 1;
  }
  return result;
}

sub_40116D函数分析:

// attributes: thunk
int __cdecl sub_40116D(int a1, int a2)
{
  return sub_4018E0(a1, a2);
}

int __cdecl sub_4018E0(int a1, int a2)    // a1是256长度的密钥字符数组基地址,a2是0~256数组的基地址
{
  int result; // eax
  int i; // [esp+D0h] [ebp-2Ch]
  char v4; // [esp+EBh] [ebp-11h]
  int v5; // [esp+F4h] [ebp-8h]

  result = __CheckForDebuggerJustMyCode(&unk_40B027);
  v5 = 0;
  for ( i = 0; i < 256; ++i )
  {
    v5 = (*(unsigned __int8 *)(i + a2) + v5 + *(unsigned __int8 *)(i + a1)) % 256;    // 相当于 v5=(v5+a1[i]+a2[i])%256
    v4 = *(_BYTE *)(i + a1);
    *(_BYTE *)(i + a1) = *(_BYTE *)(v5 + a1);
    *(_BYTE *)(v5 + a1) = v4;    // 交换值,相当于 a1[i], a1[v5] = a1[v5], a1[i]
    result = i + 1;
  }
  return result;
}

sub_4010EB函数分析:前面的函数相当于初始化,这个函数是加密

// attributes: thunk
int __cdecl sub_4010EB(int a1, FILE *Stream, FILE *a3)
{
  return sub_4015E0(a1, Stream, a3);
}

int __cdecl sub_4015E0(int a1, FILE *Stream, FILE *a3)    // a1是前面加密、初始化完的密钥
{
  int result; // eax
  char v4; // [esp+103h] [ebp-35h]
  char i; // [esp+11Bh] [ebp-1Dh]
  int v6; // [esp+124h] [ebp-14h]
  int v7; // [esp+130h] [ebp-8h]

  __CheckForDebuggerJustMyCode(&unk_40B027);
  v7 = 0;
  v6 = 0;
  for ( i = fgetc(Stream); ; i = fgetc(Stream) )    // 读取flag.txt,由于没有这个文件,只有enflag,因此任务就是逆向enflag得到flag
  {
    result = i;
    if ( i == -1 )
      break;
    v7 = (v7 + 1) % 256;    // 1~255,0
    v6 = (v6 + *(unsigned __int8 *)(v7 + a1)) % 256;    // v6 = (v6 + a1[v7]) % 256
    v4 = *(_BYTE *)(v7 + a1);
    *(_BYTE *)(v7 + a1) = *(_BYTE *)(v6 + a1);
    *(_BYTE *)(v6 + a1) = v4;    // 做了个交换值,相当于 a1[v7], a1[v6] = a1[v6], a1[v7]
    fputc(*(_BYTE *)((*(unsigned __int8 *)(v6 + a1) + *(unsigned __int8 *)(v7 + a1)) % 256 + a1) ^ i, a3);    // 写入enflag.txt ((a1[v6] + a1[v7]) % 256) ^ i = enflag,由异或的可逆性,两边同时再异或((a1[v6] + a1[v7]) % 256)即可求得flag(i)
  }
  return result;
}

至此可以直接写代码还原了,enflag里的内容用winhex即可查看

def get_key(key):
    key_len = len(key)
    a1, a2 = [], []
    for i in range(256):
        a1.append(i)
        a2.append(ord(key[i % key_len]))
    v5 = 0
    for i in range(256):
        v5 = (v5 + a1[i] + a2[i]) % 256
        a1[i], a1[v5] = a1[v5], a1[i]
    return a1


def decrypt(enc_key, enflag):
    v7, v6 = 0, 0
    flag = ''
    for i in range(len(enflag)):
        v7 = (v7 + 1) % 256
        v6 = (v6 + enc_key[v7]) % 256
        enc_key[v7], enc_key[v6] = enc_key[v6], enc_key[v7]
        flag += chr(enc_key[(enc_key[v6] + enc_key[v7]) % 256] ^ enflag[i])
    return flag


if __name__ == '__main__':
    enc_key = get_key('[Warnning]Access_Unauthorized')    # 这里手动打了结果warning少了个n。。。无语死了,我说答案咋不对来着
    enflag = [0xC3, 0x82, 0xA3, 0x25, 0xF6, 0x4C, 0x36, 0x3B, 0x59, 0xCC, 0xC4, 0xE9, 0xF1, 0xB5, 0x32, 0x18, 0xB1,
              0x96, 0xAE, 0xBF, 0x08, 0x35]
    flag = decrypt(enc_key, enflag)
    print(flag)

逆向4

本题学习到的新招式是ida中中文字符不能很好的显示出来,可以alt+A选中变量或者汇编区域,使用unicode编码,即可显示中文;同时还发现,如果反编译完的代码读起来很不对(比如scanf里没有赋值变量等),用个低版本的ida(6.8)即可

https://blog.csdn.net/OrientalGlass/article/details/129326915

ida64找到输入字符串的函数,可知v1存储我们的输入,sub_1400010E0核心函数

void __noreturn sub_140001170()
{
  unsigned __int64 v0; // rdx@1
  char *v1; // [sp+20h] [bp-18h]@1

  qword_140004618 = (__int64)malloc(0x10ui64);
  qword_140004620 = qword_140004618;
  *(_QWORD *)(qword_140004618 + 8) = 0i64;
  sub_140001020(&unk_140003260);
  sub_140001080("%lld", &v1, 62i64);
  sub_1400010E0(v1, v0);
}

sub_1400010E0:

void __fastcall __noreturn sub_1400010E0(char *a1, unsigned __int64 a2)
{
  int v2; // er9@1
  unsigned __int64 v3; // r8@1
  char *v4; // r10@2
  char v5; // al@3
  __int64 v6; // rbx@4
  char v7; // cl@5
  char v8; // [sp+1Fh] [bp-3F9h]@5
  char v9; // [sp+20h] [bp-3F8h]@2

  v2 = 0;
  v3 = (unsigned __int64)a1;
  if ( a1 )
  {
    v4 = &v9;
    do
    {
      ++v4;
      ++v2;    // a489057=')(*&^%489$!057@#><:2163qwe'
      a2 = ((unsigned __int64)((unsigned __int128)(5675921253449092805i64 * (signed __int64)v3) >> 64) >> 63)
         + ((signed __int64)((unsigned __int128)(5675921253449092805i64 * (signed __int64)v3) >> 64) >> 3);    // 但实在搞不懂为啥这里表示v3/26
      a1 = &a489057[-26 * a2];    // 下面两行比较绕,可以合并为v5=a489057[v3-26*(v3/26)]
      v5 = a1[v3];
      v3 = ((unsigned __int64)((unsigned __int128)(5675921253449092805i64 * (signed __int64)v3) >> 64) >> 63)
         + ((signed __int64)((unsigned __int128)(5675921253449092805i64 * (signed __int64)v3) >> 64) >> 3);    // v3 = v3 / 26
      *(v4 - 1) = v5;    // 取出的值存入 也搞不太懂,貌似存入了v8
    }
    while ( a2 );    // a2不断除以26直到无限小得很
  }
  v6 = v2;
  while ( v6 )
  {
    v7 = *(&v8 + v6--);    // 取出一个字符
    sub_1400011E0((unsigned __int8)(v7 ^ 7), a2, v3);
  }
  sub_140001220(a1, a2, v3);
}

_QWORD *__fastcall sub_1400011E0(char a1)
{
  char v1; // bl
  _QWORD *result; // rax
  __int64 v3; // rdx

  v1 = a1;
  result = malloc(0x10ui64);
  v3 = qword_140004618;
  qword_140004618 = (__int64)result;
  *(_QWORD *)(v3 + 8) = result;
  *(_BYTE *)v3 = v1;    // 存储到了内存
  result[1] = 0i64;
  return result;
}
// 检查对比字符串的函数
void __noreturn sub_140001220()
{
  __int64 v0; // r9
  int v1; // ecx
  signed __int64 v2; // rdx
  char v3; // al
  int v4; // er8
  __int64 v5; // r9
  char v6; // cl
  int v7; // eax

  v0 = qword_140004620;
  v1 = 0;
  v2 = 0i64;
  while ( 1 )
  {
    v3 = *(_BYTE *)v0;
    v4 = v1 + 1;
    v5 = *(_QWORD *)(v0 + 8);
    if ( v3 != aV4pY59[v2 - 1] )    // aV4pY59='/..v4p$$!>Y59-',大概流程就是cmp比较,长度14
      v4 = v1;
    qword_140004620 = v5;
    if ( !v5 )
      break;
    v6 = *(_BYTE *)v5;
    v7 = v4 + 1;
    v0 = *(_QWORD *)(v5 + 8);
    if ( v6 != aV4pY59[v2] )
      v7 = v4;
    qword_140004620 = v0;
    if ( v0 )
    {
      v2 += 2i64;
      v1 = v7;
      if ( v2 < 14 )
        continue;
    }
    goto LABEL_11;
  }
  v7 = v4;
LABEL_11:
  if ( v7 == 14 )
    sub_1400012E0();
  sub_1400012B0();
}

这题读的稀里糊涂,感觉c语言正向逆向都没学懂的样子,看了wp的脚本

table=")(*&^%489$!057@#><:2163qwe"
ans="/..v4p$$!>Y59-"
ans_s=""
for c in ans:
    ans_s+=chr(ord(c)^7)
print(ans_s)
num=0
for c in ans_s:
    num *= 26
    index=table.find(c)
    num+=index
print(num)
#())q3w##&9^2>*
#2484524302484524302

逆向5

https://ctf.show/challenges#%E9%80%86%E5%90%915-251

首先ida查看核心函数,可以看出来调用了1.dll的函数

size_t sub_401520()
{
  HMODULE hModule; // ST18_4@1
  FARPROC v2; // [sp+14h] [bp-14h]@1
  size_t i; // [sp+1Ch] [bp-Ch]@1

  hModule = LoadLibraryA("1.dll");
  v2 = GetProcAddress(hModule, "H");
  for ( i = 0; i < strlen(Str); ++i )           // Str='dba54edb0?d6>7??3ef0f1caf2ad3102'
  {
    a12345678901111[i] = Str[i];
    a12345678901111[i] = ((int (__cdecl *)(_DWORD))v2)(a12345678901111[i]);
  }
  return sub_40163E();
}

但是运行发现直接报错退出,因此查看调用这个函数的地方(在函数上按下X即可跳转),可以看到要想调用result,必须得过了if判断,但是Str[1]不等于1,所以才会出现

size_t (*__stdcall sub_4015BD(int a1))()
{
  size_t (*result)(); // eax@1
  size_t (*retaddr)(); // [sp+24h] [bp+4h]@2

  result = (size_t (*)())(unsigned __int8)Str[1];
  if ( Str[1] == 1 )
  {
    result = sub_401520;
    retaddr = sub_401520;
  }
  return result;
}

所以用ollydbg动态调试,在if这里判断时打上断点,修改ZF标志位,运行看到输出了结果cef23bce78c190884ba7a6dfa5fc4675,其实这个就是flag

image-20231213175027033.png

这里可以更深入去看上面sub_401520函数的处理

image-20231213175142333.png

上面return返回的eax是一个函数调用,因此F7可以直接进去,可以看到LoadLibraryA系统调用函数

image-20231213175241195.png

运行到进入循环:开始调用

image-20231213175458013.png

F7单步进入查看,可以看到做的操作是和7异或

image-20231213175716529.png

python脚本验证发现和flag一致

image-20231213175838932.png

红包题 武穆遗书

https://ctf.show/challenges#%E7%BA%A2%E5%8C%85%E9%A2%98%20%E6%AD%A6%E7%A9%86%E9%81%97%E4%B9%A6-256

首先upd脱壳,然后ida32查看main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  const char *v4; // [sp+0h] [bp-3Ch]@3
  char v5; // [sp+4h] [bp-38h]@3
  char Buffer; // [sp+8h] [bp-34h]@5

  sub_4011D0();
  sub_401200();
  sub_401280();
  if ( sub_401040() )
    exit(0);
  v4 = (const char *)operator new(0x1Cu);
  sub_401390(v4, &v5, aXKIlpnMlOoJmQm, 28);    // aXKIlpnMlOoJmQm存储有常量,16进制
  if ( sub_4010E0() )
    exit(0);
  while ( 1 )
  {
    printf(Format);    // input the password less than 50 char:
    gets(&Buffer);    // 接收输入
    fflush((FILE *)iob[0]._ptr);
    if ( !strcmp(v4, &Buffer) )    // 和v4比较
      break;
    printf(aPasswordErrorP, &Buffer);    // password error!!! please  try again!
  }
  printf(aWinThePassword, &Buffer);        // win!!!the password and your input are all  %s
  system(Command);
  return 0;
}

简单题直接ollydbg在if处下个断点,可以直接看到flag

image-20231213183457532.png

分析下代码,关键是获得v4值,查看sub_401390函数,哦看不懂果断放弃。。。

读不懂主要原因感觉应该是v4不是flag而是地址,里面涉及很多地址转换啥的,静态调试很麻烦,题目初心应该就是要你用动态调试

红包六

https://ctf.show/challenges#%E7%BA%A2%E5%8C%85%E5%85%AD-1601

拿到一个jar包直接jd-gui反编译,看到如下代码:

public class EzJar {
    //hint: flag not here
    public static void main(String[] args) throws Exception {
        JOptionPane.showMessageDialog(null, "Give me your flag:", "alert", JOptionPane.QUESTION_MESSAGE);
        System.out.print("Give me your flag:");
        Scanner sc = new Scanner(System.in);
        String s = sc.next();
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(1, new SecretKeySpec("easy_key".getBytes(), "DES"));
        String result = new String(Base64.getEncoder().encode(cipher.doFinal(s.getBytes())));
        System.out.println(result);
        if ("UUwnbEk0rzJT9G+ET6MU+Y+6ChoFhCceRnfdDTcuEeJ9+6qwaZFV3w==".equals(result)) {
            JOptionPane.showMessageDialog(null, "Accept!");
            System.out.print("Accept!");
        } else {
            JOptionPane.showMessageDialog(null, "Wrong answer!");
            System.out.print("Wrong answer!");
        }
    }
}

想着就这?在线DES解密网站一输拿到notflag{why_dynamic_flag_are_same?},定睛一看假的

看了wp才知道融合了一点隐写术,网上有用的wp少得可怜,复制粘贴的可真多啊

只找到一个稍微靠谱的wp(https://wiki.compass.college/Writeup/COMPASS%20CTF2021/COMPASS_CTF_2021_ALLwp/),虽然题目不一样,但是思路一样的

image-20231214085432227.png

winhex查看EzJar.jar文件,具体原理没有搜到,找到最后的"EzJar.class/"替换为"EzJar1.class",另存为EzJar1.jar

image-20231214091044194.png

bandzip打开jar可以看到多了个EzJar1.class

image-20231214091148622.png

拖动class文件到idea java工作目录下的out/production/项目名即可反编译(不知道为什么jd-gui打不开这个class文件),可以看出来做了混淆,基本确定这是真实代码,基本和上面的伪装代码格式一致,找关键词即可

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.awt.Component;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.Scanner;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.JOptionPane;

public class EzJar {
    public static void main(String[] var0) throws Exception {
        JOptionPane.showMessageDialog((Component)null, I[l[0]], I[l[1]], l[2]);
        System.out.print(I[l[3]]);
        boolean llllllllllllIIl = new Scanner(System.in);
        long llllllllllllIII = llllllllllllIIl.next();
        double lllllllllllIlll = Cipher.getInstance(I[l[2]]);
        lllllllllllIlll.init(l[1], new SecretKeySpec(I[l[4]].getBytes(), I[l[5]]));    // 求I[l[4]]
        Exception lllllllllllIllI = new String(Base64.getEncoder().encode(lllllllllllIlll.doFinal(llllllllllllIII.getBytes())));
        if (lIl(I[l[6]].equals(lllllllllllIllI))) {    // 求I[l[6]]
            JOptionPane.showMessageDialog((Component)null, I[l[7]]);
            System.out.print(I[l[8]]);
            "".length();
            if (null != null) {
                return;
            }
        } else {
            JOptionPane.showMessageDialog((Component)null, I[l[9]]);
            System.out.print(I[l[10]]);
        }

    }

    private static void lII() {
        l = new int[18];
        l[0] = (156 ^ 136) & ~(127 ^ 107);
        l[1] = " ".length();
        l[2] = "   ".length();
        l[3] = "  ".length();
        l[4] = 185 ^ 197 ^ 32 ^ 88;
        l[5] = " ".length() ^ 28 ^ 24;
        l[6] = 16 ^ 22;
        l[7] = 69 ^ 77 ^ 70 ^ 73;
        l[8] = 106 + 107 - 66 + 32 ^ 33 + 90 - 98 + 162;
        l[9] = 154 + 21 - 48 + 38 ^ 78 + 146 - 163 + 111;
        l[10] = 29 ^ 23;
        l[11] = 64 ^ 75;
        l[12] = 105 ^ 101;
        l[13] = "  ".length() ^ 106 ^ 101;
        l[14] = 72 ^ 70;
        l[15] = 144 ^ 159;
        l[16] = 160 ^ 176;
        l[17] = 177 ^ 160;
    }

    private static String lI(String lllllllllIlIllI, String lllllllllIlIlIl) {
        try {
            SecretKeySpec lllllllllIllIIl = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(lllllllllIlIlIl.getBytes(StandardCharsets.UTF_8)), "Blowfish");
            float lllllllllIlIIIl = Cipher.getInstance("Blowfish");
            lllllllllIlIIIl.init(l[3], lllllllllIllIIl);
            return new String(lllllllllIlIIIl.doFinal(Base64.getDecoder().decode(lllllllllIlIllI.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        } catch (Exception var4) {
            var4.printStackTrace();
            return null;
        }
    }

    public EzJar() {
    }

    private static String I(String lllllllllIIlIIl, String lllllllllIIIllI) {
        try {
            SecretKeySpec lllllllllIIllII = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(lllllllllIIIllI.getBytes(StandardCharsets.UTF_8)), l[8]), "DES");
            double lllllllllIIIlII = Cipher.getInstance("DES");
            lllllllllIIIlII.init(l[3], lllllllllIIllII);
            return new String(lllllllllIIIlII.doFinal(Base64.getDecoder().decode(lllllllllIIlIIl.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        } catch (Exception var5) {
            var5.printStackTrace();
            return null;
        }
    }

    private static void ll() {
        I = new String[l[17]];
        I[l[0]] = I("AQiA0bYffm9HvMlm7RnEMX/tEQAUj4Xb", "FrlAZ");
        I[l[1]] = I("hXzZyx8IUHw=", "Esxsh");
        I[l[3]] = l("ID8PFlEKM1kKHhIkWRUdBjFD", "gVysq");
        I[l[2]] = I("50fO6ARqllg=", "VZbFF");
        I[l[4]] = lI("mvXqH+/XIESPZaSG3ZbZlA==", "TuZSw");    // 密钥
        I[l[5]] = l("JQ0R", "aHBFu");
        I[l[6]] = I("dMKiRQ19iTevvzL7NtVg5+ye5BywL2QaxtVANFLuC5B2/KuC+/5L6BwtCB7zpWK1XBTQr0VWC3Vt/uYEl2xmjskE0dDrCk2C", "dPxYA");    // 密文
        I[l[7]] = lI("B/MVYKSzgq8=", "phiUP");
        I[l[8]] = I("ZtBOhuHeK3Y=", "MfnkQ");
        I[l[9]] = lI("aPhz+GjGynRlU3Alo00QeQ==", "wWtUj");
        I[l[10]] = l("BRQFNiRyBwQrNDcUSw==", "RfjXC");
        I[l[11]] = I("pT10j0lChvyrNwYRFdqBzxqFp1ruTgo9", "hGcuT");
        I[l[12]] = lI("UhBbCDk5yqaWl1uHJyS/OGmtcfVyvOOsk78/1f0MU8U3UfAf1Xf0FWNbpcKes/0HRz9SU/icRJHswW2xWjHrcFzhpsvwzqUl", "eeMoV");
        I[l[13]] = I("s11BihYBzRBpcX9EF43utw==", "RjiXK");
        I[l[14]] = I("wk5jH1cyKoA=", "frsxP");
        I[l[15]] = I("Nfa0rxB8IRArMq2F4iLlLg==", "ulQWJ");
        I[l[16]] = lI("wLcWNd0Xsbw=", "JgPGn");
    }

    static {
        lII();
        ll();
        banner = I[l[11]];
        flag = I[l[12]];
        key = I[l[13]];
        enc = I[l[14]];
        WA = I[l[15]];
        AC = I[l[16]];
    }

    private static boolean llI(int var0, int var1) {
        return var0 < var1;
    }

    private static String l(String llllllllllIlIll, String llllllllllIIlIl) {
        llllllllllIlIll = new String(Base64.getDecoder().decode(llllllllllIlIll.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        int llllllllllIIlII = new StringBuilder();
        byte llllllllllIIIll = llllllllllIIlIl.toCharArray();
        int llllllllllIIIlI = l[0];
        char llllllllllIIIIl = llllllllllIlIll.toCharArray();
        double llllllllllIIIII = llllllllllIIIIl.length;
        double lllllllllIlllll = l[0];

        do {
            if (!llI(lllllllllIlllll, llllllllllIIIII)) {
                return String.valueOf(llllllllllIIlII);
            }

            byte lllllllllIllllI = llllllllllIIIIl[lllllllllIlllll];
            llllllllllIIlII.append((char)(lllllllllIllllI ^ llllllllllIIIll[llllllllllIIIlI % llllllllllIIIll.length]));
            "".length();
            ++llllllllllIIIlI;
            ++lllllllllIlllll;
            "".length();
        } while(-" ".length() <= "   ".length());

        return null;
    }

    private static boolean lIl(int var0) {
        return var0 != 0;
    }
}

在原基础上修改了代码,原来是先DES加密再base64加密,现在是先base64解密再DES解密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;

public class EzJar {
    private static String lI(String lllllllllIlIllI, String lllllllllIlIlIl) {
        try {
            SecretKeySpec lllllllllIllIIl = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(lllllllllIlIlIl.getBytes(StandardCharsets.UTF_8)), "Blowfish");
            Cipher lllllllllIlIIIl = Cipher.getInstance("Blowfish");
            lllllllllIlIIIl.init("  ".length(), lllllllllIllIIl);
            return new String(lllllllllIlIIIl.doFinal(Base64.getDecoder().decode(lllllllllIlIllI.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        } catch (Exception var4) {
            var4.printStackTrace();
            return null;
        }
    }

    private static String I(String lllllllllIIlIIl, String lllllllllIIIllI) {
        try {
            SecretKeySpec lllllllllIIllII = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(lllllllllIIIllI.getBytes(StandardCharsets.UTF_8)), 106 + 107 - 66 + 32 ^ 33 + 90 - 98 + 162), "DES");
            Cipher lllllllllIIIlII = Cipher.getInstance("DES");
            lllllllllIIIlII.init("  ".length(), lllllllllIIllII);
            return new String(lllllllllIIIlII.doFinal(Base64.getDecoder().decode(lllllllllIIlIIl.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
        } catch (Exception var5) {
            var5.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        String key = lI("mvXqH+/XIESPZaSG3ZbZlA==", "TuZSw");
        String c = I("dMKiRQ19iTevvzL7NtVg5+ye5BywL2QaxtVANFLuC5B2/KuC+/5L6BwtCB7zpWK1XBTQr0VWC3Vt/uYEl2xmjskE0dDrCk2C", "dPxYA");
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(2, new SecretKeySpec(key.getBytes(), "DES"));
        String result = new String(cipher.doFinal(Base64.getDecoder().decode(c)));
        System.out.println(result);
    }
}
最后修改:2023 年 12 月 14 日
如果觉得我的文章对你有用,请随意赞赏