zend server试用分析

文件:ZendServer-2021.4.1-multi-php-Windows_x86.exe

安装后可以试用30天,想分析下限制原理, 根据安装日志,发现了2个关键的文件:

ZendServer\gui\module\Configuration\src\Configuration\License\Wrapper.php

ZendServer\gui\module\Configuration\src\Configuration\License\License.php

Wrapper.php里面有一个关键的函数:

php 复制代码
    public function getSerialNumberInfo($serialNumber, $userName)
    {
        $method = 'zem_serial_number_info';

        try {
            $this->validateMethod($method);
            $licenseInfo = $method($serialNumber, $userName);
            if (!is_array($licenseInfo)) {
                throw new ZSException('unexpected response received');
            }
        } catch (\Exception $e) {
            Log::err("method {$method} invocation failed with the following error: ".$e->getMessage());
            throw new ZSException("method {$method} invocation failed with the following error: ".$e->getMessage());
        }

        return new License($licenseInfo);
    }

根据licenseInfo = method(serialNumber, userName);这行代码可以知道,调用了函数:zem_serial_number_info来获取许可证信息,试用期限制应该就在这个函数里面了。

知道了函数名称,我们如何找到这个函数呢?想了下PHP程序的执行流程:

1、客户端请求PHP

2、PHP服务端收到请求 php.exe or php-cgi.exe

3、PHP加载zend server扩展插件,检测是否过期了

思路很清晰了,去PHP扩展目录看下:zendserver\ZendServer\php\7.4\lib\ext

发现了可疑文件:ZendUtils.dll

使用IDA分析ZendUtils.dll的导出函数get_module,找到了函数zem_serial_number_info的地址

get_module IDA代码如下:

php 复制代码
void *get_module()
{
  return get_module_0();
}

void *get_module_0()
{
  return &unk_10061230;
}

函数代码很简单,返回了一个地址10061230,到这里需要了解PHP扩展模块的编写方法和导出函数原理,我们在IDA跳到地址10061230:

php 复制代码
data:10061230 unk_10061230    db  5Ch ; \             ; DATA XREF: sub_10006E20↑o
.data:10061230                                         ; get_module_0↑o
.data:10061231                 db    0
.data:10061232                 db    0
.data:10061233                 db    0
.data:10061234                 db 0B6h
.data:10061235                 db  16h
.data:10061236                 db  34h ; 4
.data:10061237                 db    1
.data:10061238                 db    0
.data:10061239                 db    0
.data:1006123A                 db    0
.data:1006123B                 db    0
.data:1006123C                 db    0
.data:1006123D                 db    0
.data:1006123E                 db    0
.data:1006123F                 db    0
.data:10061240                 db    0
.data:10061241                 db    0
.data:10061242                 db    0
.data:10061243                 db    0
.data:10061244                 dd offset aZendUtils    ; "Zend Utils"
.data:10061248                 dd offset off_100610A0 //这里就是PHP扩展模块的导出函数表了
.data:1006124C                 dd offset sub_10001EF6
.data:10061250                 dd offset sub_1000239C

再跳到地址:off_100610A0

php 复制代码
.data:100610A0 off_100610A0    dd offset aZemGetExtensio
.data:100610A0                                         ; DATA XREF: .data:10061248↓o
.data:100610A0                                         ; "zem_get_extension_info_by_id"
.data:100610A4                 dd offset sub_10002973
.data:100610A8                 db    0
.data:100610A9                 db    0
.data:100610AA                 db    0
.data:100610AB                 db    0
.data:100610AC                 db 0FFh
.data:100610AD                 db 0FFh
.data:100610AE                 db 0FFh
.data:100610AF                 db 0FFh
.data:100610B0                 db    0
.data:100610B1                 db    0
.data:100610B2                 db    0
.data:100610B3                 db    0
.data:100610B4                 dd offset aZemGetExtensio_0 ; "zem_get_extension_info_by_name"
.data:100610B8                 dd offset sub_100013D9
.data:100610BC                 align 10h
.data:100610C0                 db 0FFh
.data:100610C1                 db 0FFh
.data:100610C2                 db 0FFh
.data:100610C3                 db 0FFh
.data:100610C4                 db    0
.data:100610C5                 db    0
.data:100610C6                 db    0
.data:100610C7                 db    0
.data:100610C8                 dd offset aZemGetExtensio_1 ; "zem_get_extensions_info"
.data:100610CC                 dd offset sub_10002432
.data:100610D0                 db    0
.data:100610D1                 db    0
.data:100610D2                 db    0
.data:100610D3                 db    0
.data:100610D4                 db 0FFh
.data:100610D5                 db 0FFh
.data:100610D6                 db 0FFh
.data:100610D7                 db 0FFh
.data:100610D8                 db    0
.data:100610D9                 db    0
.data:100610DA                 db    0
.data:100610DB                 db    0
.data:100610DC                 dd offset aZemGetLicenseI ; "zem_get_license_info"
.data:100610E0                 dd offset zem_get_license_info_sub_10002455
.data:100610E4                 align 8
.data:100610E8                 db 0FFh
.data:100610E9                 db 0FFh
.data:100610EA                 db 0FFh
.data:100610EB                 db 0FFh
.data:100610EC                 db    0
.data:100610ED                 db    0
.data:100610EE                 db    0
.data:100610EF                 db    0
.data:100610F0                 dd offset aZendIsConfigur ; "zend_is_configuration_changed"
.data:100610F4                 dd offset sub_10001A78
.data:100610F8                 db    0
.data:100610F9                 db    0
.data:100610FA                 db    0
.data:100610FB                 db    0
.data:100610FC                 db 0FFh
.data:100610FD                 db 0FFh
.data:100610FE                 db 0FFh
.data:100610FF                 db 0FFh
.data:10061100                 db    0
.data:10061101                 db    0
.data:10061102                 db    0
.data:10061103                 db    0
.data:10061104                 dd offset aZendSetConfigu ; "zend_set_configuration_changed"
.data:10061108                 dd offset sub_10001262
.data:1006110C                 align 10h
.data:10061110                 db 0FFh
.data:10061111                 db 0FFh
.data:10061112                 db 0FFh
.data:10061113                 db 0FFh
.data:10061114                 db    0
.data:10061115                 db    0
.data:10061116                 db    0
.data:10061117                 db    0
.data:10061118                 dd offset aZendGetCfgVar ; "zend_get_cfg_var"
.data:1006111C                 dd offset sub_10001604
.data:10061120                 db    0
.data:10061121                 db    0
.data:10061122                 db    0
.data:10061123                 db    0
.data:10061124                 db 0FFh
.data:10061125                 db 0FFh
.data:10061126                 db 0FFh
.data:10061127                 db 0FFh
.data:10061128                 db    0
.data:10061129                 db    0
.data:1006112A                 db    0
.data:1006112B                 db    0
.data:1006112C                 dd offset aZendRestartPhp ; "zend_restart_php"
.data:10061130                 dd offset sub_100026A3
.data:10061134                 align 8
.data:10061138                 db 0FFh
.data:10061139                 db 0FFh
.data:1006113A                 db 0FFh
.data:1006113B                 db 0FFh
.data:1006113C                 db    0
.data:1006113D                 db    0
.data:1006113E                 db    0
.data:1006113F                 db    0
.data:10061140                 dd offset aZendGetFileSiz ; "zend_get_file_size"
.data:10061144                 dd offset sub_1000146F
.data:10061148                 db    0
.data:10061149                 db    0
.data:1006114A                 db    0
.data:1006114B                 db    0
.data:1006114C                 db 0FFh
.data:1006114D                 db 0FFh
.data:1006114E                 db 0FFh
.data:1006114F                 db 0FFh
.data:10061150                 db    0
.data:10061151                 db    0
.data:10061152                 db    0
.data:10061153                 db    0
.data:10061154                 dd offset aZendGetCpuArch ; "zend_get_cpu_arch"
.data:10061158                 dd offset sub_10002D6F
.data:1006115C                 align 10h
.data:10061160                 db 0FFh
.data:10061161                 db 0FFh
.data:10061162                 db 0FFh
.data:10061163                 db 0FFh
.data:10061164                 db    0
.data:10061165                 db    0
.data:10061166                 db    0
.data:10061167                 db    0
.data:10061168                 dd offset aZemSerialNumbe ; "zem_serial_number_info"
.data:1006116C                 dd offset sub_100015B9
.data:10061170                 db    0
.data:10061171                 db    0
.data:10061172                 db    0
.data:10061173                 db    0
.data:10061174                 db 0FFh
.data:10061175                 db 0FFh
.data:10061176                 db 0FFh
.data:10061177                 db 0FFh
.data:10061178                 db    0
.data:10061179                 db    0
.data:1006117A                 db    0
.data:1006117B                 db    0

看到了函数zem_serial_number_info:

.data:10061168 dd offset aZemSerialNumbe ; "zem_serial_number_info"

.data:1006116C dd offset sub_100015B9

跟进sub_100015B9看下:

php 复制代码
int __fastcall sub_100015B9(int a1, int *a2)
{
  return sub_1000A730(a1, a2);
}
php 复制代码
int __fastcall sub_1000A730(int a1, int *a2)
{
  int *v2; // edi
  int result; // eax
  unsigned int v4; // ecx
  const struct QString *v5; // esi
  int v6; // eax
  int v7; // eax
  volatile signed __int32 *v8; // [esp+Ch] [ebp-30h]
  volatile signed __int32 *v9; // [esp+10h] [ebp-2Ch]
  char v10; // [esp+14h] [ebp-28h]
  char v11; // [esp+18h] [ebp-24h]
  char v12; // [esp+1Ch] [ebp-20h]
  char v13; // [esp+20h] [ebp-1Ch]
  int v14; // [esp+24h] [ebp-18h]
  const char *v15; // [esp+28h] [ebp-14h]
  int v16; // [esp+2Ch] [ebp-10h]
  int v17; // [esp+38h] [ebp-4h]

  v2 = a2;
  result = zend_parse_parameters(*(_DWORD *)(a1 + 28), "ss", &v15, &v12, &v14, &v13);
  if ( result == -1 )
  {
    *v2 = -1;
    v2[2] = 4;
  }
  else
  {
    if ( v15 )
      v4 = strlen(v15);
    else
      v4 = -1;
    v16 = QString::fromAscii_helper(v15, v4);
    sub_10001267((int)&v8);
    sub_10002306(&v8, (int)&v16);
    v5 = (const struct QString *)sub_1000272F((int)&v16);// allocate_license_db
    sub_10011270(v2, v5);                       // 许可证各个字段解析
    j_free_license_db(v5);
    QString::~QString((QString *)&v11);
    QString::~QString((QString *)&v10);
    v17 = 0;
    v6 = *((_DWORD *)v9 + 2);
    if ( !v6 || v6 != -1 && _InterlockedExchangeAdd(v9 + 2, 0xFFFFFFFF) == 1 )
      QHashData::free_helper((QHashData *)v9, (void (__cdecl *)(struct Node *))sub_100026B2);
    v17 = 1;
    v7 = *((_DWORD *)v8 + 2);
    if ( !v7 || v7 != -1 && _InterlockedExchangeAdd(v8 + 2, 0xFFFFFFFF) == 1 )
      QHashData::free_helper((QHashData *)v8, (void (__cdecl *)(struct Node *))sub_10001109);
    result = QString::~QString((QString *)&v16);
  }
  return result;
}

发现了,两个关键的函数:

v5 = (const struct QString *)sub_1000272F((int)&v16);// allocate_license_db

sub_10011270(v2, v5); // 许可证各个字段解析

最关键的是:sub_1000272F调用了allocate_license_db,看函数名字就知道了

经分析allocate_license_db在ZendExtensionManager.dll文件里面,我们转去分析ZendExtensionManager.dll的导出函数, 看到了get_license_db ,应该就是它了

php 复制代码
void *get_license_db()
{
  return get_license_db_0();
}

void *get_license_db_0()
{
  return Memory;
}

IDA分析很简单,只是返回了一个内存地址:Memory,那就是计算好的许可证信息了。

交叉引用Memory看看哪些地方写了Memory,找到了关键函数sub_10014FC0

php 复制代码
QString *__thiscall sub_10014FC0(QString *this, const struct QString *Memory, unsigned int a3, int pUserName)
{
  QString *pThis; // ebx
  unsigned int v5; // ecx
  const struct QString *v6; // eax
  QHashData **v7; // eax
  bool v8; // al
  int v9; // ecx
  char v10; // al
  int v11; // ecx
  int v12; // ecx
  QDate *v13; // eax
  bool v14; // al
  int v15; // ecx
  int v16; // eax
  int v17; // ecx
  const struct QString *v18; // edi
  int v19; // ecx
  char v20; // bl
  int v21; // esi
  int *v22; // edi
  int v23; // esi
  int v24; // ebx
  int *v25; // eax
  int v26; // eax
  _DWORD *v27; // edi
  int v28; // ecx
  int v29; // ecx
  int v30; // esi
  struct QListData::Data *v31; // ebx
  int v32; // edi
  void **i; // esi
  struct QListData::Data *v34; // ebx
  int v35; // edi
  void **j; // esi
  int v38; // [esp-8h] [ebp-54h]
  const struct ZPrintable *v39; // [esp-4h] [ebp-50h]
  void **v40; // [esp+10h] [ebp-3Ch]
  const struct QString *v41; // [esp+14h] [ebp-38h]
  int v42; // [esp+18h] [ebp-34h]
  char v43; // [esp+1Ch] [ebp-30h]
  char v44; // [esp+20h] [ebp-2Ch]
  char v45; // [esp+28h] [ebp-24h]
  const struct QString *v46; // [esp+30h] [ebp-1Ch]
  int *v47; // [esp+34h] [ebp-18h]
  QString *v48; // [esp+38h] [ebp-14h]
  int v49; // [esp+3Ch] [ebp-10h]
  int v50; // [esp+48h] [ebp-4h]

  pThis = this;
  v48 = this;
  QString::QString(this, Memory);
  v50 = 0;
  if ( pUserName )
    v5 = strlen((const char *)pUserName);
  else
    v5 = -1;
  *((_DWORD *)pThis + 1) = QString::fromAscii_helper(pUserName, v5);// 用户名
  v6 = (const struct QString *)operator new(0x10u);// 分配16个字节
  Memory = v6;
  LOBYTE(v50) = 2;
  if ( v6 )
    v7 = sub_100010FA((int)v6);
  else
    v7 = 0;
  *((_DWORD *)pThis + 2) = v7;
  *((_WORD *)pThis + 6) = 0;
  *((_BYTE *)pThis + 14) = 0;
  *((_DWORD *)pThis + 4) = 4;
  *((_DWORD *)pThis + 5) = 1;
  QDate::QDate((QString *)((char *)pThis + 24));// 时间?
  v47 = (int *)((char *)pThis + 32);
  *v47 = QHashData::shared_null;
  v49 = QListData::shared_null;
  v39 = (QString *)((char *)pThis + 4);
  v38 = (int)pThis + 32;
  LOBYTE(v50) = 4;
  sub_10001974((QListData *)&v38, (int)&v49);
  v8 = sub_10001479(*((char **)pThis + 2), (int)pThis, a3, v38, (int)v39);
  *((_BYTE *)pThis + 12) = v8;
  if ( v8 )
  {
    *((_BYTE *)pThis + 14) = 1;
  }
  else
  {
    v9 = *((_DWORD *)pThis + 2);
    v10 = sub_1000105A(pThis);
    *((_BYTE *)pThis + 14) = v10;
    if ( v10 && (v11 = *((_DWORD *)pThis + 2), (unsigned __int8)sub_100015D7(pThis, 6)) )
    {
      v12 = *((_DWORD *)pThis + 2);
      *((_BYTE *)pThis + 13) = sub_100010DC(pThis);
    }
    else
    {
      *((_BYTE *)pThis + 13) = 0;
    }
  }
  v13 = sub_1000169F(*((_DWORD **)pThis + 2), (int)&v45, (int)pThis);// 计算过期时间
  *((_DWORD *)pThis + 6) = *(_DWORD *)v13;      // date_lock 0是永不过期
  *((_DWORD *)pThis + 7) = *((_DWORD *)v13 + 1);// version_lock
  v14 = *((_QWORD *)pThis + 3) < *(_QWORD *)QDate::currentDate(&v44);// 判断许可证是否过期
  *((_BYTE *)pThis + 13) = v14;
  if ( v14 )
    *((_BYTE *)pThis + 12) = 0;                 // license_ok赋值false了
  v15 = *((_DWORD *)pThis + 2);
  v16 = sub_1000185C(pThis);
  v17 = *((_DWORD *)pThis + 2);
  v39 = pThis;
  *((_DWORD *)pThis + 5) = v16;
  v18 = 0;
  *((_DWORD *)pThis + 4) = sub_1000182A(v39);
  Memory = 0;
  a3 = 0;
  do
  {
    pUserName = QListData::shared_null;
    LOBYTE(v50) = 5;
    v46 = v18;
    sub_1000160E(&v46);
    v19 = *((_DWORD *)pThis + 2);
    v20 = sub_100015D7(pThis, v18);
    v21 = sub_1000150F(v18);
    ZPrintable::ZPrintable((ZPrintable *)&v40);
    v40 = &FeatureDescriptor::`vftable';
    v41 = v18;
    v42 = v21;
    v43 = v20;
    v22 = v47;
    LOBYTE(v50) = 6;
    sub_100017A3(v47);
    v23 = *v22;
    v24 = a3 ^ *(_DWORD *)(*v22 + 28);
    v39 = (const struct ZPrintable *)(a3 ^ *(_DWORD *)(*v22 + 28));
    v25 = sub_10001032(v22, (int)&Memory, (int)v39);
    a3 = (unsigned int)v25;
    v26 = *v25;
    if ( v26 == v23 )
    {
      if ( *(_DWORD *)(v23 + 12) >= *(_DWORD *)(v23 + 24) )
      {
        QHashData::rehash((QHashData *)v23, *(signed __int16 *)(v23 + 22) + 1);
        v23 = *v22;
        a3 = (unsigned int)sub_10001032(v22, (int)&Memory, v24);
      }
      v27 = QHashData::allocateNode((QHashData *)v23, 4);
      v28 = *(_DWORD *)a3;
      v27[2] = Memory;
      *v27 = v28;
      v39 = (const struct ZPrintable *)&v40;
      v27[1] = v24;
      ZPrintable::ZPrintable((ZPrintable *)(v27 + 3), v39);
      v29 = (int)v47;
      v27[3] = &FeatureDescriptor::`vftable';
      v27[4] = v41;
      v27[5] = v42;
      *((_BYTE *)v27 + 24) = v43;
      *(_DWORD *)a3 = v27;
      ++*(_DWORD *)(*(_DWORD *)v29 + 12);
    }
    else
    {
      v30 = v26 + 12;
      ZPrintable::ZPrintable((ZPrintable *)(v26 + 12), (const struct ZPrintable *)&v40);
      *(_DWORD *)v30 = &FeatureDescriptor::`vftable';
      *(_DWORD *)(v30 + 4) = v41;
      *(_DWORD *)(v30 + 8) = v42;
      *(_BYTE *)(v30 + 12) = v43;
    }
    ZPrintable::~ZPrintable((ZPrintable *)&v40);
    LOBYTE(v50) = 7;
    if ( !*(_DWORD *)pUserName
      || *(_DWORD *)pUserName != -1 && _InterlockedExchangeAdd((volatile signed __int32 *)pUserName, 0xFFFFFFFF) == 1 )
    {
      v31 = (struct QListData::Data *)pUserName;
      v32 = pUserName + 4 * (*(_DWORD *)(pUserName + 8) + 4);
      for ( i = (void **)(pUserName + 4 * (*(_DWORD *)(pUserName + 12) + 4)); i != (void **)v32; sub_10001807(*i) )
      {
        --i;
        v39 = (const struct ZPrintable *)4;
      }
      QListData::dispose(v31);
    }
    pThis = v48;
    v18 = (const struct QString *)((char *)Memory + 1);
    Memory = v18;
    a3 = (unsigned int)v18;
  }
  while ( (signed int)v18 < 15 );
  LOBYTE(v50) = 8;
  if ( !*(_DWORD *)v49
    || *(_DWORD *)v49 != -1 && _InterlockedExchangeAdd((volatile signed __int32 *)v49, 0xFFFFFFFF) == 1 )
  {
    v34 = (struct QListData::Data *)v49;
    v35 = v49 + 4 * (*(_DWORD *)(v49 + 8) + 4);
    for ( j = (void **)(v49 + 4 * (*(_DWORD *)(v49 + 12) + 4)); j != (void **)v35; sub_10001807(*j) )
    {
      --j;
      v39 = (const struct ZPrintable *)4;
    }
    QListData::dispose(v34);
  }
  return v48;
}
php 复制代码
QDate *__thiscall sub_1000169F(_DWORD *this, int a1, int a2)
{
  return sub_1001D510(this, (QDate *)a1, a2);
}

QDate *__thiscall sub_1001D510(_DWORD *this, QDate *a2, int a3)
{
  _DWORD *v3; // edi
  int v4; // esi
  QString *v5; // eax
  int v6; // eax
  int v7; // eax
  int expiredDay; // ebx
  int v9; // eax
  int v10; // eax
  int expiredMonth; // edi
  int v12; // eax
  int v13; // eax
  int expiredYear; // esi
  char v16; // [esp+10h] [ebp-1Ch]
  _DWORD *v17; // [esp+14h] [ebp-18h]
  char v18; // [esp+18h] [ebp-14h]
  char v19; // [esp+1Ch] [ebp-10h]
  int v20; // [esp+28h] [ebp-4h]

  v3 = this;
  v17 = this;
  v4 = a3;
  QString::operator=(this + 2, a3);
  v5 = sub_10001C2B(v3, (int)&v18, v4);
  QString::operator=(v3 + 3, v5);
  QString::~QString((QString *)&v18);
  sub_10001C2B(v3, (int)&v19, v4);
  v20 = 0;
  QString::mid(&v19, &a3, 10, 16);
  LOBYTE(v20) = 1;
  v6 = QString::mid(&a3, &v18, 0, 5);
  LOBYTE(v20) = 2;
  v7 = sub_10001604(v6);                        // 计算过期时间 日
  LOBYTE(v20) = 1;
  expiredDay = v7;
  QString::~QString((QString *)&v18);
  v9 = QString::mid(&a3, &v18, 5, 4);
  LOBYTE(v20) = 3;
  v10 = sub_10001604(v9);                       // 计算过期时间 月
  LOBYTE(v20) = 1;
  expiredMonth = v10;
  QString::~QString((QString *)&v18);
  v12 = QString::mid(&a3, &v16, 9, 7);
  LOBYTE(v20) = 4;
  v13 = sub_10001604(v12);                      // 计算过期时间 年
  LOBYTE(v20) = 1;
  expiredYear = v13 + 2000;
  QString::~QString((QString *)&v16);
  QDate::QDate(a2, expiredYear, expiredMonth, expiredDay);// 过期时间
  QString::~QString((QString *)&a3);
  QString::~QString((QString *)&v19);
  return a2;
}

到此真相大白了,我本想改成9999年,但是32位程序最大日期是:2038年01月19日

因此我改成了2038年01月01日,还有种修改方法,在判断是否过期的地方,我们改成永不过期就行。

注:OD分析时,分析zendserver\ZendServer\php\7.4\bin\php-cgi.exe就好

为了关闭zend server替换文件,可以关闭服务:ZendApache

相关推荐
JaguarJack1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo1 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack2 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe3 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
longxiangam3 天前
Composer 私有仓库搭建
php·composer
上海云盾-高防顾问3 天前
DNS异常怎么办?快速排查+解决指南
开发语言·php
ShoreKiten3 天前
关于解决本地部署sqli-labs无法安装低版本php环境问题
开发语言·php
liliangcsdn3 天前
深入探索TD3算法的推理过程
开发语言·php