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");
}
路线: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来跳转
根据伪代码,第二个push eax然后call,且eax值为我们输入的24个1,此处call的函数即为字符串加密函数
F8单步跳过看发生什么变化,发现直接看看不出来规律只能认真分析
重新执行到此处,F7单步进入,好吧。。。看了实在读不懂,wp们也都没讲解怎么分析代码分析出的按位异或,都是案例分析出来的
同时要注意Str后面的存放v6,相当于Str[16]=v6,则v6^1即Str[16]^1,相当于Str溢出把v6覆盖了?所以逆向的时候第十六位还要再异或个1
python脚本还原下得到==07154=518?9i<5=6!&!v$#%.==
输入程序:程序同一目录下生成二维码png,扫描说flag=input+Docupa
代码阅读量真不小,汇编代码调试也花了很长时间,但是定位所需要找的函数越来越熟练了
杰瑞的奶酪
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;
}
逆向:可以看到正是程序打印的字符串
继续深入分析并还原:可知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去掉冒号即可
杰瑞的下午茶
https://ctf.bugku.com/challenges/detail/id/233.html
难点主要是在动态调试分析,还没有熟练掌握,关键在于追踪我们的输入看传入哪个函数(先push再call),然后看返回(但这道题并没有在原字符串上进行加密,而是加密后存到内存里,后面判断?貌似是这样,因为看到了新的值mov到了ds段寄存器)
首先ida32查看伪代码读不懂,但是可以先大致了解下函数调用,可以看到好多sub_401A31函数,传入参数为97、98
再使用ollydbg,断点下在scanf后,输入的是1234
单步调试,发现调用了我们的输入作为参数的函数
单步步入函数,直到发现开始一个个字符遍历
循环过程中每次都和3异或,并存到内存,原字符串并没有改动;or eax eax来判断是否到输入字符串的结尾(\0)
跳出函数后接着向下走,来到一个常量字符串作为参数的函数
单步进入,直到看到异或后的字符串开始作循环遍历比较处理
被比较的字符串是==eobdxwlgbzjpdllg~==,回到函数调用完后,可以看到if做判断
综上,整个代码逻辑很简单,输入字符串按位与3异或,得到的新字符串和eobdxwlgbzjpdllg~比较
python脚本解密
3-babyre
https://ctf.bugku.com/challenges/detail/id/252.html
exeinfo查看可知是一个C#net程序,需要用dnspy来反编译,进去就能找到flag,关键是知道要用这个反编译软件
其他wp说ce(作弊机器)也可以改点击次数
pyre
https://buuoj.cn/challenges#[GWCTF%202019]pyre
pyc文件反编译为py文件:使用uncompyle6库
代码如下:
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辅助阅读代码逻辑
首先运行下看特定字符串:
接着ollydbg智能搜索找字符串下断点
F9运行并输入1,直到发现读入flag.txt,但文件不存在,如果不改标志位就会退出,因此手动改下
接着继续运行会写enflag.txt,此时会清空文件原本内容,因此记得备份;再继续程序会要求输入密钥
先随便输入1234,发现会调用一个函数并传入我们的输入,结合ida可以知道这是个密钥加密函数,里面存储有加密后的密钥字符串
python逆向下可以看到密钥是正常英文单词组成
再往后就借助ida分析,因为函数较多;比较完密钥后,一个关键函数传入了密钥、flag、enflag等,推测为加密算法函数
如上发现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
这里可以更深入去看上面sub_401520函数的处理
上面return返回的eax是一个函数调用,因此F7可以直接进去,可以看到LoadLibraryA系统调用函数
运行到进入循环:开始调用
F7单步进入查看,可以看到做的操作是和7异或
python脚本验证发现和flag一致
红包题 武穆遗书
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
分析下代码,关键是获得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/),虽然题目不一样,但是思路一样的
winhex查看EzJar.jar文件,具体原理没有搜到,找到最后的"EzJar.class/"替换为"EzJar1.class",另存为EzJar1.jar
bandzip打开jar可以看到多了个EzJar1.class
拖动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);
}
}