如何去混淆ctk.c代码2001的IOCCC的赢家?(How to de-obfuscate the

2019-08-18 02:29发布

我见过ctk.c混淆代码,但我怎么可以开始去混淆呢?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#define m(b)a=b;z=*a;while(*++a){y=*a;*a=z;z=y;}
#define h(u)G=u<<3;printf("\e[%uq",l[u])
#define c(n,s)case n:s;continue
char x[]="((((((((((((((((((((((",w[]=
"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";char r[]={92,124,47},l[]={2,3,1
,0};char*T[]={"  |","  |","%\\|/%"," %%%",""};char d=1,p=40,o=40,k=0,*a,y,z,g=
-1,G,X,**P=&T[4],f=0;unsigned int s=0;void u(int i){int n;printf(
"\233;%uH\233L%c\233;%uH%c\233;%uH%s\23322;%uH@\23323;%uH \n",*x-*w,r[d],*x+*w
,r[d],X,*P,p+=k,o);if(abs(p-x[21])>=w[21])exit(0);if(g!=G){struct itimerval t=
{0,0,0,0};g+=((g<G)<<1)-1;t.it_interval.tv_usec=t.it_value.tv_usec=72000/((g>>
3)+1);setitimer(0,&t,0);f&&printf("\e[10;%u]",g+24);}f&&putchar(7);s+=(9-w[21]
)*((g>>3)+1);o=p;m(x);m(w);(n=rand())&255||--*w||++*w;if(!(**P&&P++||n&7936)){
while(abs((X=rand()%76)-*x+2)-*w<6);++X;P=T;}(n=rand()&31)<3&&(d=n);!d&&--*x<=
*w&&(++*x,++d)||d==2&&++*x+*w>79&&(--*x,--d);signal(i,u);}void e(){signal(14,
SIG_IGN);printf("\e[0q\ecScore: %u\n",s);system("stty echo -cbreak");}int main
(int C,char**V){atexit(e);(C<2||*V[1]!=113)&&(f=(C=*(int*)getenv("TERM"))==(
int)0x756E696C||C==(int)0x6C696E75);srand(getpid());system("stty -echo cbreak"
);h(0);u(14);for(;;)switch(getchar()){case 113:return 0;case 91:case 98:c(44,k
=-1);case 32:case 110:c(46,k=0);case 93:case 109:c(47,k=1);c(49,h(0));c(50,h(1
));c(51,h(2));c(52,h(3));}} 

http://www.ioccc.org/2001/ctk.hint :

 This is a game based on an Apple ][ Print Shop Companion easter egg named 'DRIVER', in which the goal is to drive as fast as you can down a long twisty highway without running off the road. Use ',./', '[ ]', or 'bnm' to go left, straight, and right respectively. Use '1234' to switch gears. 'q' quits. The faster you go and the thinner the road is, the more points you get. Most of the obfuscation is in the nonsensical if statements among other things. It works best on the Linux console: you get engine sound (!) and the * Lock keyboard lights tell you what gear you're in (none lit=4th). The 'q' argument (no leading '-') will silence the sound. It won't work on a terminal smaller than 80x24, but it works fine with more (try it in an XTerm with the "Unreadable" font and the window maximized vertically!). 

Answer 1:

第1步

使用:

sed -e'/#include/d' ctk.c | gcc -E - | sed -e's/;/;\n/g' -e's/}/}\n/g' -e '/^#/d' | indent

我能够生成以下输出,而不是完美的似乎已经是可读好了很多:

char x[] = "((((((((((((((((((((((", w[] =
  "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
char r[] = { 92, 124, 47 }

, l[] =
{
2, 3, 1, 0}

;
char *T[] = { "  |", "  |", "%\\|/%", " %%%", "" }

;
char d = 1, p = 40, o = 40, k = 0, *a, y, z, g = -1, G, X, **P = &T[4], f = 0;
unsigned int s = 0;
void
u (int i)
{
  int n;
  printf ("\233;
%uH\233L%c\233;
%uH%c\233;
%uH%s\23322;
%uH@\23323;
%uH \n", *x - *w, r[d], *x + *w, r[d], X, *P, p += k, o);
  if (abs (p - x[21]) >= w[21])
    exit (0);
  if (g != G)
    {
      struct itimerval t = { 0, 0, 0, 0 }
      ;
      g += ((g < G) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((g >> 3) + 1);
      setitimer (0, &t, 0);
      f && printf ("\e[10;
%u]", g + 24);
    }
  f && putchar (7);
  s += (9 - w[21]) * ((g >> 3) + 1);
  o = p;
  a = x;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  a = w;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  (n = rand ()) & 255 || --*w || ++*w;
  if (!(**P && P++ || n & 7936))
    {
      while (abs ((X = rand () % 76) - *x + 2) - *w < 6);
      ++X;
      P = T;
    }
  (n = rand () & 31) < 3 && (d = n);
  !d && --*x <= *w && (++*x, ++d) || d == 2 && ++*x + *w > 79 && (--*x, --d);
  signal (i, u);
}

void
e ()
{
  signal (14, SIG_IGN);
  printf ("\e[0q\ecScore: %u\n", s);
  system ("stty echo -cbreak");
}

int main (int C, char **V)
{
  atexit (e);
  (C < 2 || *V[1] != 113)
    && (f = (C = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || C == (int) 0x6C696E75);
  srand (getpid ());
  system ("stty -echo cbreak");
  G = 0 << 3;
  printf ("\e[%uq", l[0]);
  u (14);
  for (;;)
    switch (getchar ())
      {
      case 113:
    return 0;
      case 91:
      case 98:
      case 44:
    k = -1;
    continue;
      case 32:
      case 110:
      case 46:
    k = 0;
    continue;
      case 93:
      case 109:
      case 47:
    k = 1;
    continue;
      case 49:
    G = 0 << 3;
    printf ("\e[%uq", l[0]);
    continue;
      case 50:
    G = 1 << 3;
    printf ("\e[%uq", l[1]);
    continue;
      case 51:
    G = 2 << 3;
    printf ("\e[%uq", l[2]);
    continue;
      case 52:
    G = 3 << 3;
    printf ("\e[%uq", l[3]);
    continue;
      }
}

... 现在?

我不认为还有更多的自动化过程将能够在这一点上的术语“更”可读或“少”可读从现在起执行可能取决于读者的特定偏好。

一个步骤可能被执行的是消除与琴弦转义序列,并分别放置某处它们。 因为它原来全

char l[] = {2, 3, 1, 0}

有没有别的目的而在主循环的转义序列来被利用:

printf ("\e[%uq", l[0]);

等等。 仰望它们的含义:

ESC [ 0 q: clear all LEDs
ESC [ 1 q: set Scroll Lock LED
ESC [ 2 q: set Num Lock LED
ESC [ 3 q: set Caps Lock LED

依口味你可能要与宏或函数调用更像是对你有意义的交流他们clear_all_LEDs等。

我强烈怀疑一台机器将在此被简化同意。 事实证明整个主循环似乎只是与用户输入键来工作,所以很可能转向数字加入对应的字符可能增加可读性,像更换:

case 113:
  return 0;
case 91:
case 98:
case 44:
  k = -1;
// ...
case 49:
  G = 0 << 3;
  printf ("\e[%uq", l[0]);

喜欢的东西:

case 'q':
  return 0;
case '[':
case 'b':
case ',':
  k = -1;
// ...
case '1':
  G = 0 << 3;
  set_Num_Lock_LED ();

哦-虽然我们是在它已经为什么不是我们想要的名字从这个比较奇怪的改变Ggear 。 再次我强烈怀疑的自动化过程会发现从重新命名Ggear任何比它重命名为更好butterfly 。 嗯,也许它甚至不是。

虽然美化的名字也许这个功能由单一的引用u是另一名候选人:

u (14);

用更有意义的名称update可能。 正如我们已经包含<signal.h>我们为什么不通过更换进一步反混淆码14SIGALRM是这样的:

upadate (SIGALRM);

正如你所看到的“deobfuscating”这里需要的是之前采取完全相反的一步。 更换用这一次宏扩展。 如何将一台机器很难判断哪一个更有益?

另一点,我们可能想用别的东西来代替裸数是这一个在更新功能:

f && putchar (7);

为什么不更换7\a ,因为它会变成是到底是相同的。 或许,我们甚至应该改变裸f更多的东西“有意义”。

我再次投票agains butterfly ,但宁可喜欢叫它play_sound

if (play_sound)
   putchar ('\a');

可能是我们正在寻找更可读的版本。 当然,我们不应该忘记其他所有地点,以取代F。 我们的主要功能beeing这样的罪魁祸首开头的一个正确的:

圣混乱

int main (int C, char **V)
{
  atexit (e);
  (C < 2 || *V[1] != 113)
    && (f = (C = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || C == (int) 0x6C696E75);

一边兴致勃勃地重命名fplay_sounde来-不,仍然没有butterfly ,这一次,我会宁愿把它称为: - end ,我们发现该函数签名似乎看起来有点怪命名约定的条款: argc代替Cargv ,而不是V会显得更传统在这里。 因此给我们:

int main (int argc, char* argv[])
{
  atexit (end);
  (argc < 2 || *argv[1] != 113)
    && (playsound = (argc = *(int *) getenv ("TERM")) == (int) 0x756E696C
    || argc == (int) 0x6C696E75);

由于这仍然不是一个美女,我们要求我们的标准的家伙,他告诉我们,这是非常正常更换

(A || B) && (C)

if (A || B) { C }

E = (x=F)==H || x==I

x = F; 
if (x==H || x==I) 
  A=1; 
else 
  A=0;` 

因此,也许这应该是整个代码的可读性更强的版本:

if (argc < 2 || *argv[1] != 'q') {
   argc = *(int*) getenv ("TERM");
   if (argc == (int) 0x756E69 || argc == (int) 0x6C696E75))
     play_sound = 1;
   /* skip the else brach here as play_sound is alredy initialized to 0 */
}

现在,另一个家伙轮番上涨,并开始通知我们,根据一些所谓的字节序东星奇怪的看数字0x6C696E75和0x756E69如果存储在内存中会(时解释原始字节VALES为ASCII码)只是看起来像"linu""unil" 。 一个是在一个木构建筑类型“UNIL”和“丽努”另一个而就倒过来的其他架构的不同字节。

所以,左看右看什么本质上发生在这里的是:

  • 我们得到一个指向从getenv的一个字符串(“期限”),我们解除引用它因此导致存储在所述串的位置作为int位模式之前typcast的指针为int。
  • 接下来我们比较,我们会得到,如果曾与任一“UNIL”或存储在该特定位置“丽努”执行相同的一个此值。

也许我们只是想检查TERM环境变量设置为“LINUX”所以我们的版本反混淆可能想在这里执行字符串比较。

由于在另一方面,我们也不能肯定是否也允许与终端名称开头“UNIL”播放声音,所以我决定可能会更好,不要动它可能是这个软件的特殊功能。

现在怎么办 ?

虽然重命名和重新编码的变量名和值的那些奇怪的字符数组可能是我们的下一个受害者。 下面乱看起来并不太好:

char x[] = "((((((((((((((((((((((", w[] =
  "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
char r[] = { 92, 124, 47 };

所以也许他们可以改为:

char x_offset[] = {
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 0 };

char width[] = {
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 0 };

const char border[] = "\\|/";

正如你所看到的我只是选择之间切换所描述的值的方式x为字符串常量写下来作为一个数组作为这种方式存储在这里的值的目的似乎有点更清晰的对我X。

而在另一方面,我改变了方式r作为再次写下来只是在完全相反的方向,这似乎更清晰的对我很重要。

虽然追捕所有这些裁判对xwr的时间可以用来命名po至-再次抱歉没有butterfly - posold_pos而重命名sscore

更改,例如:

  s += (9 - w[21]) * ((g >> 3) + 1);
  o = p;
  a = x;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;
  a = w;
  z = *a;
  while (*++a)
    {
      y = *a;
      *a = z;
      z = y;
    }
  ;

至:

  /* update score */
  score += (9 - width[NEXT_LINE]) * ((g >> 3) + 1);
  old_pos = pos;

  /* shift x_offset */
  a = x_offset;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* shift width */
  a = width;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

除了可能把它变成一些其他类型的循环的存在,你可以做的最大的不多美化可能两个换挡功能,因此可能加入适当的注释。 卸下幻数21可能是另一种想法NEXT_LINE似乎并没有在这里是最坏的选择。

单字符标记变量g仍然看起来并不太好。 但它重命名为类似update_interval有也消除另一个奇怪的终端转义序列的机会:

 if (g != G)
    {
      struct itimerval t = { 0, 0, 0, 0 }
      ;
      g += ((g < G) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((g >> 3) + 1);
      setitimer (0, &t, 0);
      f && printf ("\e[10;
%u]", g + 24);
    }

也许看起来比更混乱一点:

  /* update simulation speed */
  if (update_interval != gear) {
    struct itimerval t = { 0, 0, 0, 0 }  ;
      update_interval += ((update_interval < gear) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((update_interval >> 3) + 1);
      setitimer (0, &t, 0);
      if (play_sound)
        change_bell_frequency (update_interval + 24);
  }

最后修复

虽然代码看起来应该很多现在更具可读性仍然有一些讨厌的部分左起:

!d && --*x <= *w && (++*x, ++d) || d == 2 && ++*x + *w > 79 && (--*x, --d);

如何选择其他(希望)更有意义的名称d和断裂运算符优先级下来,你可能最终的东西,如:

  if (curve == CURVE_LEFT) {
    --*x_offset;
    if (*x_offset < *width) {
       ++*x_offset;
       curve = CURVE_NONE;
    }
  }
  else if (curve == CURVE_RIGHT) {
    ++*x_offset;
    if (*x_offset + *width > 79) {
      --*x_offsett;
      curve = CURVE_NONE;
    }
  } 

不是将所有这些适当的宏CURVE_...秒。

现在,仍然有那些XPT的名字挂在那也可能会改变。 因为这使得它的目的也更好一点可见的代码,我决定翻转线序T ,我改名为tree这肯定意味着计算还必须固定。 总而言之,这是来自:

char *T[] = { "  |", "  |", "%\\|/%", " %%%", "" };
char X, **P = &T[4];

// ...

  if (!(**P && P++ || n & 7936))
    {
      while (abs ((X = rand () % 76) - *x + 2) - *w < 6);
      ++X;
      P = T;
    }

喜欢的东西:

char *tree[] = {
  "",
  " %%%",
  "%\\|/%",
  "  |",
  "  |",
};

char **tree_line = tree;
char tree_position;

// ...

  /* update tree line pointer */
  if (!(**tree_line && tree_line-- || n & 7936)) {
    /* find the right spot to grow */
    while (abs ((tree_position = rand () % 76) - *x_offset + 2) - *width < 6)
      ;
    ++tree_position;
    tree_line = &tree[4];
  }

保持最好的部分,直到结束

虽然代码似乎已经看上去更漂亮了很多对我现在有还有一部分失踪。 这是在做所有的输出的一个。 这是此行我说的:

 printf ("\233;%uH\233L%c\233;%uH%c\233;%uH%s\23322;%uH@\23323;%uH \n",
      *x - *w, r[d], *x + *w, r[d], X, *P, p += k, o); 

从看起来相当难读相距甚至是模糊的计算机产生任何有用的结果。

我尝试了很多其他的终端模拟器运行,更改终端设置和切换语言环境来回不sucess不同的东西。

因此,除了在事实上这种困惑的似乎是更完美的,因为它甚至似乎混淆我的电脑我现在还不能告诉笔者这里打算什么把戏。

八进制代码\233具有相同的位模式作为转义字符( \033与另外设置的8个比特这种作用可能是与于在这里意效果某种方式)。 不幸的是,我已经告诉它没有为我工作。

幸运的是转义序列仍然似乎很容易猜到,所以我想出了以下替换:

POS + = move_x,

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[22;%uH" "@"
          "\e[23;%uH" " " "\n",
          pos,
          old_pos);  

以绘画分解成独立的(希望)让他们多一点点的可读性。 实际行和前行仍拼命此编码为原始版本。 也许从那里,如下图所示,甚至会提高可读性提取它们:

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[%u;%uH" "@"
          "\e[%u;%uH" " " "\n",
          NEXT_LINE +1, pos,
          NEXT_LINE +2, old_pos);

这终于给我带来了我再“测试”了很多第一个可用的版本。 虽然艺术可能不是100%状态时,它似乎仍然是很容易上瘾。

最后的话

在这里,我带着最后的简易版本。 正如你会看到我没有实现的LED设置功能和清晰的屏幕功能,但它不应该是很难找到散布在模糊版本所需的转义序列。 其实我已经提到在这个职位的LED序列。 清除屏幕上的人是“\ E [0Q”。 快乐的黑客攻击。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>

#define NEXT_LINE 21

#define CURVE_LEFT 0
#define CURVE_NONE 1
#define CURVE_RIGHT 2

char x_offset[] = {
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
  40, 40, 0 };

char width[] = {
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
  8, 8, 0 };

const char border[] = "\\|/";

void change_bell_frequency () {}
void clear_screen () {}
void clear_all_LEDs () {}
void set_Num_Lock_LED () {}
void set_Scroll_lock_LED () {}
void set_Caps_Lock_LED () {}



char *tree[] = {
  "",
  " %%%",
  "%\\|/%",
  "  |",
  "  |",
};


char **tree_line = tree;
char tree_position;

char curve = CURVE_NONE;
char *a, y, z;

char move_x = 0;
char update_interval = -1;

char pos = 40;
char old_pos = 40;

char play_sound = 0;
char gear;

unsigned int score = 0;

void move (char x, char y) {
  printf ("\e[%u;%uH", x, y);
}

void insert () {
  printf ("\e[L");
}

void update (int i) {
  int n;

  pos += move_x,

  /* draw street */
  printf ("\e[1;%uH" "\e[L" "%c"
          "\e[1;%uH" "%c",
          *x_offset - *width, border[curve],
          *x_offset + *width, border[curve]);
  /* draw tree */
  printf ("\e[1;%uH" "%s",
          tree_position, *tree_line);

  /* redraw car */
  printf ("\e[%u;%uH" "@"
          "\e[%u;%uH" " " "\n",
          NEXT_LINE + 1, pos,
          NEXT_LINE +2, old_pos);

  /* did we leave the road ? */
  if (abs (pos - x_offset[NEXT_LINE]) >= width[NEXT_LINE])
    exit (0);

  /* update simulation speed */
  if (update_interval != gear) {
    struct itimerval t = { 0, 0, 0, 0 }  ;
      update_interval += ((update_interval < gear) << 1) - 1;
      t.it_interval.tv_usec = t.it_value.tv_usec = 72000 / ((update_interval >> 3) + 1);
      setitimer (0, &t, 0);
      if (play_sound)
        change_bell_frequency (update_interval + 24);
  }

  /* play sound */
  if (play_sound)
    putchar ('\a');

  /* update score */
  score += (9 - width[NEXT_LINE]) * ((update_interval >> 3) + 1);
  old_pos = pos;

  /* shift x_offset */
  a = x_offset;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* shift width */
  a = width;
  z = *a;
  while (*++a) {
    y = *a;
    *a = z;
    z = y;
  };

  /* generate new road */
  n = rand ();

  if (!(n & 255) && *width > 1)
    --*width;

  /* set tree line pointer */
  if (!(**tree_line && tree_line-- || n & 7936)) {
    /* find the right spot to grow */
    while (abs ((tree_position = rand () % 76) - *x_offset + 2) - *width < 6)
      ;
    ++tree_position;
    tree_line = &tree[4];
  }

  /* new offset */
  n = rand () & 31;
  if (n < 3)
    curve = n;

  if (curve == CURVE_LEFT) {
    --*x_offset;
    if (*x_offset <= *width) {
      ++*x_offset;
      curve = CURVE_NONE;
    }
  }
  else if (curve == CURVE_RIGHT) {
    ++*x_offset;
    if (*x_offset + *width > 79) {
      --*x_offset;
      curve = CURVE_NONE;
    }
  }

  signal (SIGALRM, update);
}


void end () {
  signal (SIGALRM, SIG_IGN);
  clear_all_LEDs ();
  clear_screen ();
  printf ("Score: %u\n", score);
  system ("stty echo -cbreak");
}


int main (int argc, char **argv) {
  atexit (end);

  if (argc < 2 || *argv[1] != 'q') {
    argc = *(int*) getenv ("TERM");
    if (argc == (int) 0x6C696E75 || argc == (int) 0x756E696C)
      play_sound = 1;
  }

  srand (getpid ());
  system ("stty -echo cbreak");
  gear = 0 << 3;

  clear_all_LEDs ();
  update (14);
  for (;;)
    switch (getchar ())
      {
        case 'q':
          return 0;
        case '[':
        case 'b':
        case ',':
          move_x = -1;
          continue;
        case ' ':
        case 'n':
        case '.':
          move_x = 0;
          continue;
        case ']':
        case 'm':
        case '/':
          move_x = 1;
          continue;
        case '1':
          gear = 0 << 3;
          set_Num_Lock_LED ();
          continue;
        case '2':
          gear = 1 << 3;
          set_Caps_Lock_LED ();
          continue;
        case '3':
          gear = 2 << 3;
          set_Scroll_lock_LED ();
          continue;
        case '4':
          gear = 3 << 3;
          clear_all_LEDs ();
          continue;
      }
}


文章来源: How to de-obfuscate the ctk.c code the winner of 2001's IOCCC?