{"id":1042,"date":"2015-09-21T02:03:58","date_gmt":"2015-09-21T00:03:58","guid":{"rendered":"https:\/\/0x90r00t.com\/fr\/?p=1042"},"modified":"2015-09-21T02:59:06","modified_gmt":"2015-09-21T00:59:06","slug":"ekoparty-pre-ctf-2015-reverse-200-reversing-the-apc-cache-write-up","status":"publish","type":"post","link":"https:\/\/0x90r00t.com\/fr\/2015\/09\/21\/ekoparty-pre-ctf-2015-reverse-200-reversing-the-apc-cache-write-up\/","title":{"rendered":"[EKOPARTY PRE-CTF 2015] [Reverse 200 &#8211; Reversing the APC cache] Write Up"},"content":{"rendered":"<h2>Description<\/h2>\n<blockquote><p>Description: Strings are not always an alternative.<\/p>\n<p>Hints: APC 3.1.13 with PHP 5.4 was used.<\/p>\n<p>Attachment: <a href=\"https:\/\/0x90r00t.com\/wp-content\/uploads\/2015\/09\/reversing200.zip\">reversing200.zip<\/a><\/p><\/blockquote>\n<p><!--more--><\/p>\n<h2>Resolution<\/h2>\n<p>Nous nous retrouvons devant un myst\u00e9rieux fichier cache.data. Celui-ci n&rsquo;\u00e9tant pas identifi\u00e9 avec notre habituelle commande \u00ab\u00a0file\u00a0\u00bb, on essaye alors avec la commande strings.<\/p>\n<p>Ici quelques choses int\u00e9ressantes :<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n\/var\/www\/html\/index.php\r\n&lt;html&gt;\r\n    &lt;head&gt;&lt;\/head&gt;\r\n    &lt;body&gt;\r\n        &lt;form method=&quot;POST&quot; action=&quot;login.php&quot;&gt;\r\n            &lt;input type=&quot;text&quot; name=&quot;token&quot;&gt;\r\n            &lt;input type=&quot;submit&quot; value=&quot;Login&quot;&gt;\r\n        &lt;\/form&gt;\r\n    &lt;\/body&gt;\r\n&lt;\/html&gt;\r\n\/var\/www\/html\/login.php\r\n&#x5B;..]\r\nAzDGCrypt\r\nazdgcrypt\r\nEKO{this_is_not_the_flag}\r\n&#x5B;..]\r\ne88ef51d4112b999380444ce48488762\r\nsha1\r\nsha1\r\nWelcome master, your key is EKO{\r\n&#x5B;...]\r\n<\/pre>\n<p>Evidemment, le flag n&rsquo;\u00e9tait pas \u00ab\u00a0EKO{this_is_not_the_flag}\u00a0\u00bb.<\/p>\n<p>L&rsquo;\u00e9nonc\u00e9 nous parle de cache APC, nous allons donc poursuivre cette voie. D&rsquo;ailleurs il y a une fonction qui a l&rsquo;air assez sympa dans le doc php, <a href=\"http:\/\/php.net\/manual\/fr\/function.apc-bin-loadfile.php\">apc_bin_loadfile()<\/a>.<\/p>\n<p>Nous allons donc compiler php 5.4 et la derni\u00e8re version d&rsquo;APC afin de pouvoir charger le fichier de cache et pouvoir l&rsquo;utiliser. Ce qui nous montre quelque choses int\u00e9ressantes :<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">.\/bin\/php -d extension=\/tmp\/php\/php-5.4.45\/compiled\/lib\/php\/extensions\/no-debug-non-zts-20100525\/apc.so -d apc.enable_cli=1 -d apc.stat=0 -r &quot;apc_bin_loadfile('cache.data'); print_r(apc_cache_info());&quot;<\/pre>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\nArray\r\n(\r\n    &#x5B;num_slots] =&gt; 1031\r\n    &#x5B;ttl] =&gt; 0\r\n    &#x5B;num_hits] =&gt; 0\r\n    &#x5B;num_misses] =&gt; 0\r\n    &#x5B;num_inserts] =&gt; 2\r\n    &#x5B;expunges] =&gt; 0\r\n    &#x5B;start_time] =&gt; 1442496130\r\n    &#x5B;mem_size] =&gt; 21568\r\n    &#x5B;num_entries] =&gt; 2\r\n    &#x5B;file_upload_progress] =&gt; 1\r\n    &#x5B;memory_type] =&gt; mmap\r\n    &#x5B;locking_type] =&gt; pthread mutex Locks\r\n    &#x5B;cache_list] =&gt; Array\r\n        (\r\n            &#x5B;0] =&gt; Array\r\n                (\r\n                    &#x5B;type] =&gt; file\r\n                    &#x5B;device] =&gt; 0\r\n                    &#x5B;inode] =&gt; 0\r\n                    &#x5B;filename] =&gt; \/var\/www\/html\/index.php\r\n                    &#x5B;num_hits] =&gt; 0\r\n                    &#x5B;mtime] =&gt; 1442496130\r\n                    &#x5B;creation_time] =&gt; 1442496130\r\n                    &#x5B;deletion_time] =&gt; 0\r\n                    &#x5B;access_time] =&gt; 1442496130\r\n                    &#x5B;ref_count] =&gt; 0\r\n                    &#x5B;mem_size] =&gt; 1200\r\n                )\r\n\r\n            &#x5B;1] =&gt; Array\r\n                (\r\n                    &#x5B;type] =&gt; file\r\n                    &#x5B;device] =&gt; 0\r\n                    &#x5B;inode] =&gt; 0\r\n                    &#x5B;filename] =&gt; \/var\/www\/html\/login.php\r\n                    &#x5B;num_hits] =&gt; 0\r\n                    &#x5B;mtime] =&gt; 1442496130\r\n                    &#x5B;creation_time] =&gt; 1442496130\r\n                    &#x5B;deletion_time] =&gt; 0\r\n                    &#x5B;access_time] =&gt; 1442496130\r\n                    &#x5B;ref_count] =&gt; 0\r\n                    &#x5B;mem_size] =&gt; 20368\r\n                )\r\n\r\n        )\r\n\r\n    &#x5B;deleted_list] =&gt; Array\r\n        (\r\n        )\r\n\r\n    &#x5B;slot_distribution] =&gt; Array\r\n        (\r\n            &#x5B;231] =&gt; 1\r\n            &#x5B;850] =&gt; 1\r\n        )\r\n\r\n)\r\n<\/pre>\n<p>Nous apprenons donc que le fichier de cache contient deux pages php. Bien entendu un file_get_contents pour obtenir le code source ne fonctionne pas (comme APC ne met pas en cache les file_get_contents()), on va alors devoir inclure les diff\u00e9rents fichiers et d\u00e9sassembler le r\u00e9sultat.<\/p>\n<p>Pour ceci nous avons utilis\u00e9 l&rsquo;extension php <a href=\"http:\/\/derickrethans.nl\/projects.html#vld\">vld<\/a> qui nous a bien simplifi\u00e9 la compr\u00e9hension des opcodes php.<\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">.\/bin\/php -d extension=\/tmp\/php\/php-5.4.45\/compiled\/lib\/php\/extensions\/no-debug-non-zts-20100525\/apc.so -d apc.enable_cli=1 -d apc.stat=0 -d extension=\/tmp\/php\/php-5.4.45\/compiled\/lib\/php\/extensions\/no-debug-non-zts-20100525\/vld.so -d vld.active=0 -d vld.execute=1 -r &quot;apc_bin_loadfile('cache.data'); include '\/var\/www\/html\/login.php';&quot;<\/pre>\n<p>Ce qui nous a donn\u00e9 :<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">Finding entry points\r\nBranch analysis from position: 0\r\nJump found. Position 1 = -2\r\nfunction name:  (null)\r\nnumber of ops:  4\r\ncompiled vars:  none\r\nline     #* E I O op                           fetch          ext  return  operands\r\n-------------------------------------------------------------------------------------\r\n   2     0  E &gt;   SEND_VAL                                                 '%2Ftmp%2Fcache.data'\r\n         1        DO_FCALL                                      1          'apc_bin_loadfile'\r\n   3     2        INCLUDE_OR_EVAL                                          '%2Fvar%2Fwww%2Fhtml%2Flogin.php', REQUIRE\r\n   8     3      &gt; RETURN                                                   1\r\n\r\nbranch: #  0; line:     2-    8; sop:     0; eop:     3; out1:  -2\r\npath #1: 0, \r\nFinding entry points\r\nBranch analysis from position: 0\r\nJump found. Position 1 = 5, Position 2 = 55\r\nBranch analysis from position: 5\r\nJump found. Position 1 = 15, Position 2 = 22\r\nBranch analysis from position: 15\r\nJump found. Position 1 = 23, Position 2 = 29\r\nBranch analysis from position: 23\r\nJump found. Position 1 = 30, Position 2 = 54\r\nBranch analysis from position: 30\r\nJump found. Position 1 = 41, Position 2 = 50\r\nBranch analysis from position: 41\r\nJump found. Position 1 = 53\r\nBranch analysis from position: 53\r\nJump found. Position 1 = 54\r\nBranch analysis from position: 54\r\nJump found. Position 1 = 58\r\nBranch analysis from position: 58\r\nJump found. Position 1 = -2\r\nBranch analysis from position: 50\r\nJump found. Position 1 = 54\r\nBranch analysis from position: 54\r\nBranch analysis from position: 54\r\nBranch analysis from position: 29\r\nBranch analysis from position: 22\r\nBranch analysis from position: 55\r\nJump found. Position 1 = -2\r\nfilename:       \/var\/www\/html\/login.php\r\nfunction name:  (null)\r\nnumber of ops:  59\r\ncompiled vars:  !0 = $token, !1 = $crypt, !2 = $hash, !3 = $flag\r\nline     #* E I O op                           fetch          ext  return  operands\r\n-------------------------------------------------------------------------------------\r\n   3     0  E &gt;   NOP                                                      \r\n  44     1        FETCH_IS                                         $1      '_POST'\r\n         2        ISSET_ISEMPTY_DIM_OBJ                       16777216  ~2      $1, 'token'\r\n         3        BOOL_NOT                                         ~3      ~2\r\n         4      &gt; JMPZ                                                     ~3, -&gt;55\r\n  45     5    &gt;   FETCH_R                      global              $4      '_POST'\r\n         6        FETCH_DIM_R                                      $5      $4, 'token'\r\n         7        ASSIGN                                                   !0, $5\r\n  47     8        INIT_FCALL_BY_NAME                                       'substr'\r\n         9        SEND_VAR                                                 !0\r\n        10        SEND_VAL                                                 0\r\n        11        SEND_VAL                                                 4\r\n        12        DO_FCALL_BY_NAME                              3  $7      \r\n        13        IS_IDENTICAL                                     ~8      $7, 'CmxQ'                                   ; substr($token, 0, 4) == &quot;CmxQ&quot;\r\n        14      &gt; JMPZ_EX                                          ~8      ~8, -&gt;22\r\n        15    &gt;   INIT_FCALL_BY_NAME                                       'substr'\r\n        16        SEND_VAR                                                 !0\r\n        17        SEND_VAL                                                 44\r\n        18        SEND_VAL                                                 4\r\n        19        DO_FCALL_BY_NAME                              3  $9      \r\n        20        IS_IDENTICAL                                     ~10     $9, 'MgY%2F'                                 ; substr($token, 44, 4) == &quot;MgY%2F&quot;\r\n        21        BOOL                                             ~8      ~10\r\n        22    &gt; &gt; JMPZ_EX                                          ~8      ~8, -&gt;29\r\n        23    &gt;   INIT_FCALL_BY_NAME                                       'substr'\r\n        24        SEND_VAR                                                 !0\r\n        25        SEND_VAL                                                 -4\r\n        26        DO_FCALL_BY_NAME                              2  $11     \r\n        27        IS_IDENTICAL                                     ~12     $11, 'Mg%3D%3D'                              ; substr($token, -4) == &quot;Mg%3D%3D&quot;\r\n        28        BOOL                                             ~8      ~12\r\n        29    &gt; &gt; JMPZ                                                     ~8, -&gt;54\r\n  48    30    &gt;   FETCH_CLASS                                   4  :13     'AzDGCrypt'\r\n        31        NEW                                              $14     :13                                          ; $14 = new AzDGCrypt('EKO{this_is_not_the_flag}');\r\n        32        SEND_VAL                                                 'EKO%7Bthis_is_not_the_flag%7D'\r\n        33        DO_FCALL_BY_NAME                              1          \r\n        34        ASSIGN                                                   !1, $14\r\n  49    35        INIT_METHOD_CALL                                         !1, 'decrypt'                                ; \r\n        36        SEND_VAR                                                 !0\r\n        37        DO_FCALL_BY_NAME                              1  $18                                                  ; $18 = $14.decrypt($token);\r\n        38        ASSIGN                                                   !2, $18\r\n  51    39        IS_IDENTICAL                                     ~20     !2, 'e88ef51d4112b999380444ce48488762'       ; $14.decrypt($token) == md5(&quot;EKO{}&quot;)    &lt;-- This is what we want ! 40 &gt; JMPZ                                                     ~20, -&gt;50\r\n  52    41    &gt;   INIT_FCALL_BY_NAME                                       'sha1'                                       \r\n        42        SEND_VAR                                                 !0\r\n        43        DO_FCALL_BY_NAME                              1  $21     \r\n        44        ASSIGN                                                   !3, $21                                      ; $21 c'est le flag, c'est sha1($token)\r\n  53    45        ADD_STRING                                       ~23     'Welcome+master%2C+your+key+is+EKO%7B'       ; %7B &lt;=&gt; &quot;{&quot;\r\n        46        ADD_VAR                                          ~23     ~23, !3                                      ; Le flag c'est le &quot;!3&quot;\r\n        47        ADD_CHAR                                         ~23     ~23, 125                                     ; 125 == %7D == &quot;}&quot;\r\n        48        ECHO                                                     ~23\r\n  54    49      &gt; JMP                                                      -&gt;53\r\n  55    50    &gt;   INIT_FCALL_BY_NAME                                       'header'\r\n        51        SEND_VAL                                                 'Location%3A+index.php'\r\n        52        DO_FCALL_BY_NAME                              1          \r\n  57    53    &gt; &gt; JMP                                                      -&gt;54\r\n  58    54    &gt; &gt; JMP                                                      -&gt;58\r\n  59    55    &gt;   INIT_FCALL_BY_NAME                                       'header'\r\n        56        SEND_VAL                                                 'Location%3A+index.php'\r\n        57        DO_FCALL_BY_NAME                              1          \r\n  61    58    &gt; &gt; RETURN                                                   1<\/pre>\n<p>On comprend ici que le param\u00e8tre POST \u00ab\u00a0token\u00a0\u00bb doit avoir un certain format. Il est v\u00e9rifi\u00e9 par 3 substr : substr($token, 0, 4) doit valoir \u00ab\u00a0CmxQ\u00a0\u00bb, substr($token, 44, 4) : \u00ab\u00a0MgY\/\u00a0\u00bb et substr($token, -4) : \u00ab\u00a0Mg==\u00a0\u00bb.<\/p>\n<p>Si ces conditions sont remplies, il est d\u00e9crypt\u00e9 \u00e0 l&rsquo;aide de la m\u00e9thode \u00ab\u00a0decrypt\u00a0\u00bb d&rsquo;une classe s&rsquo;appelant \u00ab\u00a0AzDGCrypt\u00a0\u00bb et instanci\u00e9e avec comme param\u00e8tre \u00ab\u00a0EKO{this_is_not_the_flag}\u00a0\u00bb (qui sert de cl\u00e9 de cryptage), et le texte d\u00e9crypt\u00e9 doit avoir comme valeur \u00ab\u00a0e88ef51d4112b999380444ce48488762\u00a0\u00bb .<\/p>\n<p>Au lieu de nous emb\u00eater \u00e0 comprendre le code d\u00e9sassembl\u00e9 de cette classe, nous avons fait une recherche sur google \u00ab\u00a0au cas o\u00f9\u00a0\u00bb, et cela a port\u00e9 ses fruits, le code source \u00e9tait disponible sur <a href=\"https:\/\/github.com\/IlchCMS\/Ilch-1.1\/blob\/master\/include\/includes\/class\/AzDGCrypt.class.inc.php\">github<\/a>.<\/p>\n<p>Arriv\u00e9 \u00e0 ce point, on aurait pu se dire que comme nous avions le code pour crypter\/d\u00e9cryter, la cl\u00e9 de cryptage ainsi que le texte \u00e0 retrouver en clair, nous avions tout, mais en fait pas tout \u00e0 fait.<\/p>\n<p>La fonction de cryptage a une partie al\u00e9atoire : $r = md5(rand(0,32000));, ce qui fait que le token base64 n&rsquo;a que tr\u00e8s peu de chances de correspondre aux verifications faites par les trois substr.<\/p>\n<p>Nous avons alors modifi\u00e9 la fonction crypt afin qu&rsquo;elle prenne un nouvel argument, celui qui aurait d\u00fb \u00eatre al\u00e9atoire, puis nous avons cr\u00e9\u00e9 un script de bruteforce. 32000 possibilit\u00e9s \u00e7a va assez vite \u00e0 tester.<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\nclass AzDGCrypt{\r\n   var $k;\r\n   function AzDGCrypt($m){\r\n      $this-&gt;k = $m;\r\n   }\r\n   function ed($t) {\r\n      $r = md5($this-&gt;k);\r\n      $c=0;\r\n      $v = &quot;&quot;;\r\n      for ($i=0;$i&lt;strlen($t);$i++) {\r\n         if ($c==strlen($r)) $c=0;\r\n         $v.= substr($t,$i,1) ^ substr($r,$c,1);\r\n         $c++;\r\n      }\r\n      return $v;\r\n   }\r\n   function crypt($t, $rand){\r\n      $r = md5($rand);\r\n      $c=0;\r\n      $v = &quot;&quot;;\r\n      for ($i=0;$i&lt;strlen($t);$i++){ if ($c==strlen($r)) $c=0; $v.= substr($r,$c,1) . (substr($t,$i,1) ^ substr($r,$c,1)); $c++; } return base64_encode($this-&gt;ed($v));\r\n   }\r\n   function decrypt($t) {\r\n      $t = $this-&gt;ed(base64_decode($t));\r\n      $v = &quot;&quot;;\r\n      for ($i=0;$i&lt;strlen($t);$i++){\r\n         $md5 = substr($t,$i,1);\r\n         $i++;\r\n         $v.= (substr($t,$i,1) ^ $md5);\r\n      }\r\n      return $v;\r\n   }\r\n}\r\n\r\n \/\/ bf\r\n $crypt = new AzDGCrypt('EKO{this_is_not_the_flag}');\r\n for ($i = 0; $i &lt;= 32000; ++$i) {\r\n  $result = $crypt-&gt;crypt('e88ef51d4112b999380444ce48488762', $i);\r\n  if (substr($result, 0, 4) == 'CmxQ' &amp;&amp; substr($result, 44, 4) == 'MgY\/' &amp;&amp; substr($result, -4) == 'Mg==') {\r\n   echo &quot;b64: $result\\nRand: $i\\n&quot;;\r\n   break;\r\n  }\r\n }\r\n<\/pre>\n<p>Au bout de quelques instants, le r\u00e9sultat s&rsquo;affiche :<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nb64: CmxQaQAzBTYKZQYzAWAFZAY1V2NVZAZgUGQCbAU9Vz4CMgY\/AzgLaQwxWm5SYVY2UGNUaVFlAm5VO1MzBDNQMg==\r\nRand: 17291\r\n<\/pre>\n<p>On teste alors ce token avec la page cach\u00e9e :<\/p>\n<pre class=\"brush: php; title: ; notranslate\" title=\"\">\r\n&lt;?php\r\n $_POST&#x5B;'token'] = 'CmxQaQAzBTYKZQYzAWAFZAY1V2NVZAZgUGQCbAU9Vz4CMgY\/AzgLaQwxWm5SYVY2UGNUaVFlAm5VO1MzBDNQMg==';\r\n apc_bin_loadfile('cache.data');\r\n include('\/var\/www\/html\/login.php');\r\n<\/pre>\n<p>Tout est OK ! \ud83d\ude42<\/p>\n<p><code>Welcome master, your key is EKO{59a59936b318e8ef20fd923a3e7b05a1e44e9e91}<\/code><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Description Description: Strings are not always an alternative. Hints: APC 3.1.13 with PHP 5.4 was used. Attachment: reversing200.zip<\/p>\n","protected":false},"author":9,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[34,35,70],"tags":[71,72,10,54],"class_list":["post-1042","post","type-post","status-publish","format-standard","hentry","category-2015-fr","category-ctf-fr","category-ekoparty","tag-apc","tag-cache","tag-reverse","tag-php"],"_links":{"self":[{"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/posts\/1042","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/comments?post=1042"}],"version-history":[{"count":5,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/posts\/1042\/revisions"}],"predecessor-version":[{"id":1118,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/posts\/1042\/revisions\/1118"}],"wp:attachment":[{"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/media?parent=1042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/categories?post=1042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/0x90r00t.com\/fr\/wp-json\/wp\/v2\/tags?post=1042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}