Loading... # Reverse(八) ## Take the maze https://ctf.bugku.com/challenges/detail/id/126.html ida32定位主函数 ~~~c 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 ~~~c // 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函数 ~~~c // 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 ~~~c // 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](http://xherlock.top/usr/uploads/2023/12/701795298.png) 路线:DRDDDRDRRRRRRDDDRRRRDDRRRRRRRRRDRRRR,接着还要转为小写和步长数字,因此结果为d1r1d3r1d1r6d3r4d2r9d1r4,又因为switch做了映射,d对应0,r对应3,其他数字对应自身+5的16进制; 所以加密后的key是06360836063b0839073e0639 最后分析加密key的函数sub_101C748 ~~~c // 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](http://xherlock.top/usr/uploads/2023/12/3160274900.png) 根据伪代码,第二个push eax然后call,且eax值为我们输入的24个1,此处call的函数即为字符串加密函数 ![image-20231206174616591.png](http://xherlock.top/usr/uploads/2023/12/355751605.png) F8单步跳过看发生什么变化,发现直接看看不出来规律只能认真分析 ![image-20231206174652326.png](http://xherlock.top/usr/uploads/2023/12/1200702893.png) 重新执行到此处,F7单步进入,好吧。。。看了实在读不懂,wp们也都没讲解怎么分析代码分析出的按位异或,都是案例分析出来的 同时要注意Str后面的存放v6,相当于Str[16]=v6,则v6^1即Str[16]^1,相当于Str溢出把v6覆盖了?所以逆向的时候第十六位还要再异或个1 ![image-20231206203130371.png](http://xherlock.top/usr/uploads/2023/12/1946501248.png) python脚本还原下得到==07154=518?9i<5=6!&!v$#%.== ![image-20231206204059769.png](http://xherlock.top/usr/uploads/2023/12/2896843312.png) 输入程序:程序同一目录下生成二维码png,扫描说flag=input+Docupa ![image-20231206204117838.png](http://xherlock.top/usr/uploads/2023/12/3935298717.png) 代码阅读量真不小,汇编代码调试也花了很长时间,但是定位所需要找的函数越来越熟练了 ## 杰瑞的奶酪 https://ctf.bugku.com/challenges/detail/id/231.html 先运行了下,发现要打印了input value,上来ida32查字符串无果,搜main也没有什么信息,但是幸亏仔细看了下里面的函数,发现对代码做了混淆; 如下的sub_4011A0出现了多次,且传入了好多常量,点击去看第一个 ~~~c 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函数:可以看到做了按位异或的操作 ~~~c 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](http://xherlock.top/usr/uploads/2023/12/2395167964.png) 继续深入分析并还原:可知Str是输入的字符串,sub_401000做了加密,sub_401060做了判断 ~~~c 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](http://xherlock.top/usr/uploads/2023/12/1044868628.png) ## 杰瑞的下午茶 https://ctf.bugku.com/challenges/detail/id/233.html 难点主要是在动态调试分析,还没有熟练掌握,关键在于追踪我们的输入看传入哪个函数(先push再call),然后看返回(但这道题并没有在原字符串上进行加密,而是加密后存到内存里,后面判断?貌似是这样,因为看到了新的值mov到了ds段寄存器) 首先ida32查看伪代码读不懂,但是可以先大致了解下函数调用,可以看到好多sub_401A31函数,传入参数为97、98 ![image-20231207152656654.png](http://xherlock.top/usr/uploads/2023/12/4089500082.png) 再使用ollydbg,断点下在scanf后,输入的是1234 ![image-20231207151926827.png](http://xherlock.top/usr/uploads/2023/12/736850948.png) 单步调试,发现调用了我们的输入作为参数的函数 ![image-20231207152028948.png](http://xherlock.top/usr/uploads/2023/12/765212563.png) 单步步入函数,直到发现开始一个个字符遍历 ![image-20231207152303612.png](http://xherlock.top/usr/uploads/2023/12/3166840793.png) 循环过程中每次都和3异或,并存到内存,原字符串并没有改动;or eax eax来判断是否到输入字符串的结尾(\\0) 跳出函数后接着向下走,来到一个常量字符串作为参数的函数 ![image-20231207152840197.png](http://xherlock.top/usr/uploads/2023/12/192620862.png) 单步进入,直到看到异或后的字符串开始作循环遍历比较处理 ![image-20231207153359006.png](http://xherlock.top/usr/uploads/2023/12/3150007519.png) 被比较的字符串是==eobdxwlgbz\jp\dllg~==,回到函数调用完后,可以看到if做判断 ![image-20231207153700020.png](http://xherlock.top/usr/uploads/2023/12/4177445184.png) 综上,整个代码逻辑很简单,输入字符串按位与3异或,得到的新字符串和eobdxwlgbz\jp\dllg~比较 python脚本解密 ![image-20231207153916161.png](http://xherlock.top/usr/uploads/2023/12/2113439830.png) ## 3-babyre https://ctf.bugku.com/challenges/detail/id/252.html exeinfo查看可知是一个C#net程序,需要用**dnspy**来反编译,进去就能找到flag,关键是知道要用这个反编译软件 ![image-20231209200343247.png](http://xherlock.top/usr/uploads/2023/12/3984762962.png) 其他wp说ce(作弊机器)也可以改点击次数 ## pyre https://buuoj.cn/challenges#[GWCTF%202019]pyre pyc文件反编译为py文件:使用uncompyle6库 ![image-20231210184105146.png](http://xherlock.top/usr/uploads/2023/12/2370123907.png) 代码如下: ~~~python 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'] ~~~ 逆向解密即可 ~~~python 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 ~~~java 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 ~~~java 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](http://xherlock.top/usr/uploads/2023/12/3190533728.png) 接着ollydbg智能搜索找字符串下断点 ![image-20231211164302279.png](http://xherlock.top/usr/uploads/2023/12/2697765406.png) ![image-20231211164352567.png](http://xherlock.top/usr/uploads/2023/12/3429800501.png) F9运行并输入1,直到发现读入flag.txt,但文件不存在,如果不改标志位就会退出,因此手动改下 ![image-20231211164513932.png](http://xherlock.top/usr/uploads/2023/12/1949574142.png) 接着继续运行会写enflag.txt,此时会清空文件原本内容,因此记得备份;再继续程序会要求输入密钥 ![image-20231211164750409.png](http://xherlock.top/usr/uploads/2023/12/2433757551.png) 先随便输入1234,发现会调用一个函数并传入我们的输入,结合ida可以知道这是个密钥加密函数,里面存储有加密后的密钥字符串 ![image-20231211164938638.png](http://xherlock.top/usr/uploads/2023/12/1428334083.png) python逆向下可以看到密钥是正常英文单词组成 ![image-20231211165126117.png](http://xherlock.top/usr/uploads/2023/12/1385686484.png) 再往后就借助ida分析,因为函数较多;比较完密钥后,一个关键函数传入了密钥、flag、enflag等,推测为加密算法函数 ![image-20231211165337970.png](http://xherlock.top/usr/uploads/2023/12/3930225205.png) 如上发现sub_4010C8函数把输入的密钥复制了到256个字符存到a3,接着sub_40116D函数a3又做了处理,最后sub_4010EB函数加密 sub_4010F0函数分析: ~~~c // 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函数分析: ~~~c // 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函数分析: ~~~c // 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函数分析:前面的函数相当于初始化,这个函数是加密 ~~~c // 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即可查看 ~~~python 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核心函数 ~~~c 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: ~~~c 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的脚本 ~~~python 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的函数 ~~~c 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,所以才会出现 ~~~c 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](http://xherlock.top/usr/uploads/2023/12/2898279684.png) 这里可以更深入去看上面sub_401520函数的处理 ![image-20231213175142333.png](http://xherlock.top/usr/uploads/2023/12/3474209386.png) 上面return返回的eax是一个函数调用,因此F7可以直接进去,可以看到LoadLibraryA系统调用函数 ![image-20231213175241195.png](http://xherlock.top/usr/uploads/2023/12/1937283744.png) 运行到进入循环:开始调用 ![image-20231213175458013.png](http://xherlock.top/usr/uploads/2023/12/3685251601.png) F7单步进入查看,可以看到做的操作是和7异或 ![image-20231213175716529.png](http://xherlock.top/usr/uploads/2023/12/1337915490.png) python脚本验证发现和flag一致 ![image-20231213175838932.png](http://xherlock.top/usr/uploads/2023/12/1990037503.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函数 ~~~c 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](http://xherlock.top/usr/uploads/2023/12/302085084.png) 分析下代码,关键是获得v4值,查看sub_401390函数,哦看不懂果断放弃。。。 读不懂主要原因感觉应该是v4不是flag而是地址,里面涉及很多地址转换啥的,静态调试很麻烦,题目初心应该就是要你用动态调试 ## 红包六 https://ctf.show/challenges#%E7%BA%A2%E5%8C%85%E5%85%AD-1601 拿到一个jar包直接jd-gui反编译,看到如下代码: ~~~java 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](http://xherlock.top/usr/uploads/2023/12/417056109.png) winhex查看EzJar.jar文件,具体原理没有搜到,找到最后的"EzJar.class/"替换为"EzJar1.class",另存为EzJar1.jar ![image-20231214091044194.png](http://xherlock.top/usr/uploads/2023/12/1563612657.png) bandzip打开jar可以看到多了个EzJar1.class ![image-20231214091148622.png](http://xherlock.top/usr/uploads/2023/12/1097559123.png) 拖动class文件到idea java工作目录下的out/production/项目名即可反编译(不知道为什么jd-gui打不开这个class文件),可以看出来做了混淆,基本确定这是真实代码,基本和上面的伪装代码格式一致,找关键词即可 ```java // // 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解密 ~~~java 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 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏