[suctf_2018] Enigma
0x00 背景
这次比赛因为撞上了运动会, 加上周日还有一些别的事情, 所以其实也就打了半天, 就做出来了这个题目(虽然可能打两天也只能做出来这一个题目QAQ), 算是xctf中第一个独立做出来的题目, 竟然又是逆向......pwn是真的太难了..........多看师傅们的wp吧.... 因为刚好选了门课, 讲的就是图灵奖得主的生平, 当然首先要讲的就是图灵了, 讲图灵自然就绕不过他二战期间帮助英国军方破解德国的密码机--Enigma 的故事了.....这也是我没有做pwn, 而是选择先做这个题的原因.
贴上官方wp
0x01 程序分析
看到这个题目我首先想到两种解法: 爆破或者逆算法. 因为enigma是每个字母单独加密的, 所以完全可以每个字母单独爆破. 并不会很慢. 应该要比逆算法快不少(对于一名逆向新手而言)
所以我就决定先自己用c重新实现一下程序中的算法, 然后爆破之. 这个过程就是体力活.......没有太多技术含量..........(可能有骚操作但是我不会吧......), 个人感觉了解enigma加密原理对理解程序有很大的帮助. 总之最后自己用c复现出来代码如下(对比官方wp中的官方实现, 我意识到了巨大的差距...........):
#include <stdio.h>
#include <stdlib.h>
char target[36] = {'\xa8', '\x1c', '\xaf', '\xd9', '\x00', '\x6c', '\xac', '\x02',
'\x9b', '\x05', '\xe3', '\x68', '\x2f', '\xc7', '\x78', '\x3a',
'\x02', '\xbc', '\xbf', '\xb9', '\x4d', '\x1c', '\x7d', '\x6e',
'\x31', '\x1b', '\x9b', '\x84', '\xd4', '\x84', '\x00', '\x76',
'\x5a', '\x4d', '\x06', '\x75'};
char key0[4] = {'\x31', '\x62', '\x93', '\xc4'};
char key1[4] = {'\x21', '\x42', '\x63', '\x84'};
char key2[4] = {'\x3d', '\x7a', '\xb7', '\xf4'};
int key0_idx = 0;
int key1_idx = 0;
int key2_idx = 0;
char v17=0;
char v18=0;
int encode3[9] = {0x2f9bacef, 0x97CDD677, 0x4BE6EB3B, 0x0A5F3759D,
0x0D2F9BACE, 0x697CDD67, 0x0B4BE6EB3, 0x5A5F3759, 0x2D2F9BAC};
char input[36] = {'c'};
char input2[36];
char encoded[36];
int g_val = 0x5f3759df;
char encode00(char key, int idx){
void * v1[2];
char input_i = input[idx];
for(int i=0; i<8; ++i){
char v3 = v17;
int v4 = ((key & (1 << (i&0x3f))) != 0);
int v5 = ((input_i & (1 << (i&0x3f))) != 0);
v18 = v3 ^ v4 ^ v5;
v17 = v4 & v5 | v3 & (v4 | v5);
if(v18 == 0){
input[idx] = input[idx] & ~(1 << (i&0x3f));
}else{
input[idx] = input[idx] | (1 << (i&0x3f));
}
}
return input[idx];
}
int keyset[3] = {0, 0, 0};
char encode0(int idx){
v17 = 0;
v18 = 0;
char key = key0[key0_idx];
char ret = encode00(key, idx);
key = key1[key1_idx];
ret = encode00(key, idx);
key = key2[key2_idx];
ret = encode00(key, idx);
encoded[idx] = ret;
if ( ++key0_idx == 4 )
{
key0_idx = 0;
++key1_idx;
}
if ( key1_idx == 4 )
{
key1_idx = 0;
++key2_idx;
}
if ( key2_idx == 4 )
key2_idx = 0;
return ret;
}
int func(int a1, int a2){
return (((a1&0xff) & (1 << (a2 & 0x3f))) != 0);
}
char encode11(int idx, int j){
char encoded_i = encoded[idx];
int v0 = func(encoded_i, j);
int v1 = (v0 != func(encoded_i, 7-j));
if(v1){
encoded_i |= (1 << (j & 0x3f));
}else{
encoded_i &= ~(1 << (j & 0x3f));
}
encoded[idx] = encoded_i;
return encoded_i;
}
char encode12(int idx, int j){
char encoded_i = encoded[idx];
int v0 = func(encoded_i, 7-j);
int v1 = (v0 != func(encoded_i, j));
if(v1){
encoded_i |= (1 << ((7-j)&0x3f));
}else{
encoded_i &= ~(1 << ((7-j)&0x3f));
}
encoded[idx] = encoded_i;
return encoded_i;
}
int encode1(int i){
for(int j=0; j<=2; ++j){
encode11(i, j);
encode12(i, j);
encode11(i, j);
}
}
int encode22(){
int v0 = func(g_val, 31);
int v1 = (func(g_val, 7) ^ v0);
int v2 = (func(g_val, 5) ^ v1);
int v3 = (func(g_val, 3) ^ v2);
int v4 = (func(g_val, 2) ^ v3);
int v16 = (v4 ^ func(g_val, 0));
g_val = g_val>>1;
int v6 = (v16!=0);
if(v6){
g_val |= (1 << (31 & 0x3f));
}else{
g_val &= ~(1 << (31 & 0x3f));
}
return g_val;
}
char encode2(int i){
char *e3_ptr = (char *) encode3;
char ret = encoded[i] ^ e3_ptr[i];
encoded[i] = ret;
return ret;
}
void show(){
printf("output:\n" );
for(int i=0; i<36; ++i){
printf("%d, %2x, %c\n", i, encoded[i], encoded[i]);
}
printf("input:\n" );
for(int i=0; i<36; ++i){
printf("%d: %2x, %c\n", i, input2[i], input2[i]);
}
for(int i=0; i<36; ++i){
printf("%c", input2[i]);
}
}
void idx2key(int idx){
key0_idx = idx % 4;
key1_idx = (idx >> 2) % 4;
key2_idx = (idx >> 4) % 4;
}
int decode(){
char *e3_ptr = (char *)encode3;
for(int i=0; i<36; ++i){
for(char possible = 0; ; ++possible){
idx2key(i);
input[i] = possible;
input2[i] = possible;
encode0(i);
encode1(i);
char ret = encode2(i);
if(ret == target[i]){
int a = i;
break;
}
}
}
show();
return 0;
}
int test(){
for(int i=0; i<36; ++i){
input[i] = 'a';
encode0(i);
encode1(i);
encode2(i);
}
show();
}
int main(){
decode();
// test();
// printf("hello" );
return 0;
}
然后其实也就没啥了, 爆破的算法很无脑.......不过期间遇到了一个坑, 就是enigma是有种东西叫转子, 每加密一个字母转子就会转一个单位, 不同状态的转子对应的加密映射是不同的. 所以爆破的时候一定需要将每一位对应的转子的状态设置好, 否则就会出现奇怪的东西. 还卡在一个地方是因为我把用来存贮输入的flag的数组input
在爆破的过程中修改了.......进而导致最后输出input
的时候得到的是一串包含一堆不可见字符的怎么看都不像flag的乱码........所以我们要牢记软件构造的教导!养成良好的编程习惯!不要修改参数!!!
0x04 exp
参考上面代码的decode()函数
0x05 收获
逆向真好玩.
pwn是不可能pwn的, 这辈子都不可能pwn的, 漏洞又找不到, 找到了漏洞又不会利用, 只有靠看wp勉强明白题目什么意思这样子
[suctf_2018] Enigma