[关闭]
@romangol 2015-10-07T13:08:55.000000Z 字数 3152 阅读 1836

乌云峰会puzzle 第四题命题报告

writeup


这个题目的最初思路来自于2015年台湾逢甲大學張真誠教授在上海交通大学做的一个学术报告,張教授介绍了关于使用数独方阵进行信息隐藏的主题报告。从这个非常有意思的方案出发,我们设计了一个类似的信息隐藏小程序,并命题制作了本次峰会Puzzle系列的第四题。

首先,我们给出的题目包含了一张图片(图片本身是一个数独,也就是解题需要的数独方阵)以及三个二进制可执行文件,三个可执行文件分别对应的是Windows,Linux和Raspberry Pi ARM平台,出题的时候有意对最后一个树莓派上可执行文件的符号进行了保留(从后来看到的解题报告来看,这个信息对解题起到了一定的帮助)。

我们设计的基于数独方阵的信息隐藏算法很简单,首先介绍一下这个算法:隐藏信息的方案依赖于我们选择的数独方阵,熟悉数独的同学都知道数独可以按每行,每列或者每个9x9的小方格;对于每一行、列或者小方格,可以将其视为[1,2,3,4,5,6,7,8,9]的一个随机置换。这个随机置换是之后信息隐藏的关键:我们将一张bmp图片中的每个像素点(RGB值)取出得到一个三元组,三元组中每一个像素点都可以用来隐藏信息。根据人眼的视觉特性,稍微改变一下像素点的RGB值,并不会让视觉产生很大的差异,因此我们对每个像素点的RGB值三元组进行一个“偏移”,假设一个像素点的RGB值原先为[x,y,z],我们通过对每个值加上或者减去一个小于9的数,人为将其修改为[x',y',z'],使得x',y',z'每个值 mod 9之后都变为我们指定的值a, b, c;这里的a, b, c再通过数独方阵中特定的行、列或者小方格指定的置换所对应的逆变换,就恢复得到了原始的信息。

为了编码特定的明文,我们先将字符串转换成hex值,然后对每4bit信息m转成两个值(m & 0xF) / 9和(m & 0xF) % 9,再把这一系列的值通过数独方阵指定的行、列和小方格进行置换。最后把置换写回到bmp里面去。在操作bmp时,我们使用了http://partow.net/programming/bitmap/提供的一个简单方便的bmp C++ library,使得程序可以很简洁地完成这个编码变换。

  1. #include "bitmap_image.hpp"
  2. /* solution
  3. unsigned char soduku[] = "126730458075684132483215607204563781358471026617028345531807264742356810860142573";
  4. unsigned char text[] = "1aed56787db88808434ed857e200507c4de68ef3";
  5. */
  6. unsigned char soduku[] = "251764983683921475947835126824576391395182764176493852539648217418257639762319548";
  7. unsigned char text[] = "50105f052d4681d28c642188cabfff5b7492817d";
  8. unsigned char msg[sizeof(text) * 4];
  9. unsigned int transform( unsigned int input, size_t counter )
  10. {
  11. unsigned char table[9];
  12. if ( counter % 3 == 0 )
  13. {
  14. unsigned int row = counter / 3 % 9;
  15. for ( size_t i = 0; i < 9; ++i )
  16. {
  17. table[i] = soduku[row * 9 + i];
  18. }
  19. }
  20. if ( counter % 3 == 1 )
  21. {
  22. unsigned int col = counter / 3 % 9;
  23. for ( size_t i = 0; i < 9; ++i )
  24. {
  25. table[i] = soduku[i * 9 + col];
  26. }
  27. }
  28. if ( counter % 3 == 2 )
  29. {
  30. unsigned int cell = counter / 3 % 9;
  31. for ( size_t i = 0; i < 3; ++i )
  32. {
  33. unsigned int row = i + cell / 3 * 3;
  34. for ( size_t j = 0; j < 3; ++j )
  35. {
  36. unsigned int col = cell % 3 * 3 + j;
  37. table[i * 3 + j] = soduku[row * 9 + col];
  38. }
  39. }
  40. }
  41. unsigned int x = input / 9 * 9 + table[ msg[counter % sizeof(msg)] ] - 0x30;
  42. /*
  43. if ( counter < sizeof(msg) )
  44. {
  45. printf("%d", x % 9 );
  46. }
  47. */
  48. return x > 255 ? x - 9 : x;
  49. }
  50. int main()
  51. {
  52. for ( size_t i = 0; i < sizeof(text); ++i )
  53. {
  54. msg[i * 4 + 0] = (text[i] & 0xF) / 9;
  55. msg[i * 4 + 1] = (text[i] & 0xF) % 9;
  56. msg[i * 4 + 2] = (text[i] >> 4 & 0xF) / 9;
  57. msg[i * 4 + 3] = (text[i] >> 4 & 0xF) % 9;
  58. }
  59. /*
  60. for ( size_t i = 0; i < sizeof(msg); ++i )
  61. printf("%d", msg[i]);
  62. puts("");
  63. */
  64. bitmap_image image("input.bmp");
  65. if (!image)
  66. {
  67. printf("Error - Failed to open: input.bmp\n");
  68. return 1;
  69. }
  70. unsigned char red;
  71. unsigned char green;
  72. unsigned char blue;
  73. unsigned int total_number_of_pixels = 0;
  74. const unsigned int height = image.height();
  75. const unsigned int width = image.width();
  76. size_t counter = 0;
  77. for (size_t y = 0; y < height; ++y)
  78. {
  79. for (size_t x = 0; x < width; ++x)
  80. {
  81. image.get_pixel( x, y, red, green, blue);
  82. if ( (y * width + x) % 3 == 0 )
  83. {
  84. red = transform( red, counter++ );
  85. }
  86. if ( (y * width + x) % 3 == 1 )
  87. {
  88. green = transform( green, counter++ );
  89. }
  90. if ( (y * width + x) % 3 == 2 )
  91. {
  92. blue = transform( blue, counter++ );
  93. }
  94. image.set_pixel( x, y, red, green, blue);
  95. }
  96. }
  97. image.save_image("soduku.bmp");
  98. return 0;
  99. }

我们发布的binary executables中所编码的unsigned char soduku[]数组并不是对应发布的包含隐写信息bmp所采用的数组,实际使用的数组可以通过解这个数独来得到。我们选择的这个数独很有名,它来自于Gary McGuire在其著名的论文《There is no 16-Clue Sudoku: Solving the Sudoku Minimum Number of Clues Problem via Hitting Set Enumeration》中所给出的一个17个数字具有唯一解的数独puzzle。
实际上,在我们自己对几个发布发binary executables进行分析的时候发现如果对raspberry pi版本的elf进行分析(例如使用Hex-rays分析插件),得到的结果基本上和源代码是差不太多的,因此解答此题的主要难点在于理解这个思路,对每个像素点取到其隐藏的信息并拼接起来就可以得到正确的答案:字符串"http://www.securitygossip.com"的SHA-1值。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注