2018nctf几道逆向题
本帖最后由 yechen123 于 2019-2-9 15:55 编辑这两天比赛挺多了
利用空余时间看了下题目
发个帖子记录一下
上传题目别的两道题目没有存有。。
南邮杯的 题目大部分都不怎么难
0.后门后门后门
没啥技术含量
hereisyourflag();里边就是flag
1.基本操作
看代码
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 result; // rax
puts("Input flag:");
__isoc99_scanf("%64s", &byte_601100);
dword_601064 = 0;
sub_400666(0LL);
if ( !strcmp(&s1, "bcec8d7dcda25d91ed3e0b720cbb6cf202b09fedbc3e017774273ef5d5581794") )
{
memset(&s1, 0, 0x80uLL);
dword_601064 = 0;
sub_4006BE(0LL, 0LL);
if ( !strcmp(&s1, "7d8dcdcaed592e1dcb07e02c36bcb2f0bf9e0bdcb0e13777237e25fd48515974") )
printf("TQL! TQL! flag: nctf{%s}\n", &byte_601100);
else
puts("Emmmm.....");
result = 0LL;
}
else
{
puts("GG!");
result = 0LL;
}
return result;
}
输入字符串后经过变换后
再跟字符串比较得到就是flag
看变换函数
__int64 __fastcall sub_400666(signed int a1)
{
int v1; // eax
__int64 result; // rax
if ( a1 <= 63 )
{
v1 = dword_601064++;
*(&s1 + v1) = byte_601100;
sub_400666((unsigned int)(2 * a1 + 1));
result = sub_400666((unsigned int)(2 * (a1 + 1)));
}
return result;
}
懒得写脚本
直接按照ascii码生成一串有序字符串
然后再看如何变换
0123456789:;<=>?
@ABCDEFGHIJKLMNO
PQRSTUVWXYZ[\\]^
_`abcdefghijklmn
变换之后
0137?OnP@QR8ASTB
UV49CWXDYZ:E[\F\
]25;G^_H`a<IbcJd
e6=KfgLhi>MjkNlm
看变换之后的字符串
bcec8d7dcda25d91
ed3e0b720cbb6cf2
02b09fedbc3e0177
74273ef5d5581794
逆推得到flag
bc2e3b4c2eb03258c5102bf9de77f57dddad9edb70c6c20febc01773e5d81947
2.Some Boxes
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed int v3; // eax
int i; //
int len; //
sub_4007D9();
read(0, buf, 1000uLL);
len = strlen(buf);
for ( i = 0; i < len; ++i )
{
v3 = buf;
if ( v3 == 52 ) // 4
{
sub_400C62(); //
goto LABEL_14;
}
if ( v3 > 52 )
{
if ( v3 == 53 ) // 5
{
sub_400D5D(); // 下
goto LABEL_14;
}
if ( v3 == 87 ) // W
{
sub_400B67(); // 上
goto LABEL_14;
}
}
else if ( v3 == 48 ) // 0
{
sub_400A6C(); // 右
goto LABEL_14;
}
puts("error!");
LABEL_14:
sub_400E58();
}
return 0LL;
}
题目显示play a game
题目会允许输入1000个字符
但是只能是4 5 W 0 是控制方向的否则报错
先看看判断成功的函数
__int64 sub_400E58()
{
__int64 result; // rax
result = (unsigned __int8)byte_6020A0;
if ( (_BYTE)result == 20 )
{
result = (unsigned __int8)byte_6020A0;
if ( (_BYTE)result == 20 )
{
puts(&byte_4018EE);
system("cat flag");
exit(0);
}
}
return result;
}
最终要这两个值返回20
看一下迷宫
08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08
08 08 00 00 08 08 08 08 08 08 08 08 00 00 08 08
08 08 00 00 00 00 00 08 00 00 00 00 00 00 08 08
08 08 00 08 08 00 00 00 00 00 08 08 08 00 00 08
08 08 00 08 08 00 08 08 00 00 08 14 00 00 00 08
08 08 00 00 08 00 00 00 00 00 08 08 08 08 08 08
08 08 00 00 08 08 08 08 00 08 08 08 08 08 08 08
08 08 00 08 08 08 08 08 00 08 08 00 00 00 08 08
08 00 00 08 14 08 00 08 00 00 00 00 08 00 08 08
08 00 00 00 00 00 00 00 00 08 08 08 08 00 08 08
08 00 00 00 00 08 08 08 00 08 00 00 00 00 08 08
08 08 08 08 08 08 08 08 08 08 08 08 08 08 08 08
在看一下方向控制函数 看看是一个什么游戏
__int64 sub_400C62()
{
__int64 result; // rax
result = (unsigned __int8)byte_6020A0;
if ( (_BYTE)result != 8 ) // 如果是通路
{
if ( lie - 1 != flag_one_lie || hang != flag_one_hang )//
{
if ( lie - 1 == flag_two_lie && hang == flag_two_hang )// 需要和2冲突
{
if ( byte_6020A0 == 8 )// 如果二左移撞墙
++lie; // 那主键就不移动
else
sub_400A1C(&flag_two_lie); // 否则二左移
}
}
else if ( byte_6020A0 == 8 )// 如果此时一左移撞墙
{
++lie; // 主键不移动
}
else // 如果冲突了
{
sub_400A1C(&flag_one_lie); // one_lie左移
}
result = (unsigned int)(lie-- - 1);
}
return result;
}
说明 控制自己是主要的另外还有两个值也在迷宫中
当主键移动时候 如果主键碰到强 也就是08那就不移动 如果碰到另外两个值
如果另外两个值向着相同方向移动会碰墙 那就不移动
最终要使两个值推到0x14的方格中
那就是推箱子了
看看初始化位置
void sub_400796()
{
lie = 8;
hang = 5;
flag_one_lie = 5;
flag_one_hang = 2;
flag_two_lie = 8;
flag_two_hang = 7;
}
ok
4代表左 5是下 0是右 W是上
得到
WW44W444W45555555450050W0000WWWWW5444WW00050W4W0000W0550544
4.VM VM题目基本没怎么做过 只是了解一点加密过程可能会有错误
一个vm程序
看主要代码__int64 sub_400DAB()
{
int v1; //
void *liuchengbiao; //
char *v3; //
int *v4; //
int *v4_one; //
int *v4_two; //
int *v4_three; //
char *v8; //
char *v9; //
_DWORD *v10; //
unsigned __int64 v11; //
v11 = __readfsqword(0x28u);
v1 = 0; //
liuchengbiao = malloc(0x200uLL); // 1653010
v3 = (char *)malloc(0x1400uLL); // 1653220
memset(&v4, 0, 48uLL);
memcpy(liuchengbiao, &unk_6021C0, 0x190uLL);
v4 = (int *)malloc(0x38uLL); // 1654630
v4_one = v4 + 1; // 可能是寄存器
v4_two = v4 + 2;
v4_three = v4 + 3;
v8 = v3 + 5120;
v9 = v3 + 5120;
v10 = liuchengbiao;
LABEL_26:
while ( *v10 )
{
switch ( (char)*v10 )
{
case 8:
sub_40082B((&v4) - 1], v10, &v10);// 把v10的值放到 第v10-1块寄存器
goto LABEL_26;
case 9:
sub_40096D((&v4) - 1], (_DWORD **)&v8, &v10);// v8值赋值给寄存器
goto LABEL_26;
case 0xA:
sub_400927((&v4) - 1], &v8, &v10);// 寄存器值复制给v8 两个相差不远
goto LABEL_26;
case 0xB:
input_flag(v4, &v10); // flag存到寄存器中 每次就存一个字符
goto LABEL_26;
case 0xC:
sub_4009E5(v4, &v10);
goto LABEL_26;
case 0xD:
sub_400B5D(&v1, (&v4) - 1], (&v4) - 1], &v10);// 比较两个操作寄存器大小 并操作v1的值 下一个函数(F)会根据v1的值跳转
goto LABEL_26;
case 0xE:
sub_400A34(&v10, v10, (__int64)liuchengbiao);// 根据v10决定跳转到liucjhengbiao哪个个值
goto LABEL_26;
case 0xF:
sub_400AAF(v1, (signed __int64 *)&v10, v10, (__int64)liuchengbiao);// 跳转
goto LABEL_26;
case 0x10:
sub_400A61(v1, (signed __int64 *)&v10, v10, (__int64)liuchengbiao);
goto LABEL_26;
case 0x11:
sub_400AFD((&v4) - 1], &v10); // 寄存器自增
goto LABEL_26;
case 0x12:
sub_400B2D((&v4) - 1], &v10);
goto LABEL_26;
case 0x13:
sub_400C0E((&v4) - 1], v10, &v10);// 让寄存器一加上流程表的值
goto LABEL_26;
case 0x14:
sub_400C43((&v4) - 1], (&v4) - 1], &v10);// 寄存器1减去寄存器2
goto LABEL_26;
case 0x15:
sub_400C7C((&v4) - 1], v10, &v10);// 让寄存器的值^上流程表的值
goto LABEL_26;
case 0x16:
sub_400CB1((&v4) - 1], (&v4) - 1], &v10);
goto LABEL_26;
case 0x17:
sub_400CEA((&v4) - 1], (&v4) - 1], &v10);
goto LABEL_26;
case 0x19:
sub_400858((&v4) - 1], (&v4) - 1], &v10);// 寄存器赋值
goto LABEL_26;
case 0x1A:
sub_400889((&v4) - 1], (unsigned __int64)(&v4) - 1], &v10);
goto LABEL_26;
case 0x1B:
sub_4008BA((&v4) - 1], (unsigned int *)(&v4) - 1], &v10);// 把寄存器二的值作为地址指向的值给寄存器1
goto LABEL_26;
case 0x1C:
sub_4008EF((unsigned int *)(&v4) - 1], (&v4) - 1], &v10);// 把寄存器一的值赋值给第二个寄存器所指向的地址
goto LABEL_26;
case 0x1D:
sub_400D23((&v4) - 1], v10, &v10);// 让第一个寄存器乘以v10也就是流程表的值
goto LABEL_26;
case 0x64:
return sub_400D59((__int64)v9);
default:
sub_400A17(&v10);
break;
}
}
return 1LL;
}
memcpy(liuchengbiao, &unk_6021C0, 0x190uLL);申请流程表 程序会根据里边的值加密程序
v4_one = v4 + 1; // 可能是寄存器
v4_two = v4 + 2;
v4_three = v4 + 3;
v8 = v3 + 5120;
v4_one 这几块内存 模拟 寄存器 其中v4_three会储存一个常数0x46也就是输入的字符串长度
v8指的是一块内存 存储输入的字符串 一个字符一个字符的存 每次存完就会自动减四
看流程表
08 00 00 00 01 00 00 0000 00 00 00 08 00 00 00
03 00 00 00 46 00 00 000E 00 00 00 15 00 00 00
0A 00 00 00 01 00 00 0009 00 00 00 02 00 00 00
0B 00 00 00 0A 00 00 0001 00 00 00 0A 00 00 00
02 00 00 00 09 00 00 0001 00 00 00 11 00 00 00
01 00 00 00 0D 00 00 0001 00 00 00 03 00 00 00
0F 00 00 00 08 00 00 0008 00 00 00 01 00 00 00
00 00 00 00 08 00 00 0003 00 00 00 47 00 00 00
0E 00 00 00 46 00 00 000A 00 00 00 01 00 00 00
1A 00 00 00 02 00 00 0006 00 00 00 1D 00 00 00
01 00 00 00 04 00 00 0014 00 00 00 02 00 00 00
01 00 00 00 19 00 00 0001 00 00 00 02 00 00 00
1B 00 00 00 01 00 00 0001 00 00 00 1D 00 00 00
01 00 00 00 6E 00 00 0013 00 00 00 01 00 00 00
63 00 00 00 15 00 00 0001 00 00 00 74 00 00 00
13 00 00 00 01 00 00 0066 00 00 00 1C 00 00 00
02 00 00 00 01 00 00 0009 00 00 00 01 00 00 00
11 00 00 00 01 00 00 000D 00 00 00 01 00 00 00
03 00 00 00 0F 00 00 0022 00 00 00 64 00 00 00
其中比较重要的是
0D 00 00 0001 00 00 00 03 00 00 00
这样的流程
0d表示比较寄存器
当字符计数寄存器大于另一个寄存器时候 跳转也就是判断读取长度 要读够0x46个字符
然后后边的
0F 00 00 00 08 00 00 00
跳转
读取完了跳转到后边加密
当到了后边加密时候
会从内存中一个字符地读数据 放到最左边的寄存器中加密
加密完了在放回去最右边的是一个计数寄存器 判断循环次数
而上边的内存就是存储输入的数据
再看看最后的判断代码
signed __int64 __fastcall sub_400D59(__int64 a1)
{
signed int i; //
for ( i = 0; i <= 69; ++i )
{
if ( dword_6020A0 != *(_DWORD *)(4LL * i - 280 + a1) )
return 0LL;
}
return 1LL;
}
最后就是比较了读出最后加密过的代码
i =
刚上面的加密算法是
((输入的值*6e)+0x63)^0x74+0x66
不建议逆推 涉及小数点问题
直接爆破
i =
flags = ""
for q in range(len(i)):
for u in range(32, 126):
if (((((u*0x6e)+0x63)^0x74)+0x66)==i):
flags += chr(u)
print flags
flag = ""
u = len(flags)
for u in range(len(flags)):
flag += flags
print flag
得到最终flag
nctf{3e1ce77b70e4cb9941d6800aec022c813d03e70a274ba96c722fed72783dddac}
那个基本操作中的,比较字符串,那块变换没看懂,能详细解答一下吗 虽然不能一次性看明白,正在学习逆向破解中。。。 1感谢分享 学习学习 厉害了,学习学习! 谢谢分享。 看不懂了,哈哈哈 厉害,不明觉厉!! 感谢分享,学习学习{:1_893:}