源码级逆向 · 未加壳

Waratah PMS 8.1
酒店管理系统 逆向分析报告

目标:Waratah-PMS_8.1.202607021300.exe · 130.5 MB · Nullsoft NSIS 安装包
分析日期:2026-09-17 · 方法:安装包解包 → Electron 源码提取 → 静态分析

一、核心结论

这个应用没有做任何源码保护 —— 逆向难度接近为零

安装包用 7-Zip 即可完整解包,内部是一个 Electron 应用(Chromium + Node.js)。没有 asar 打包、没有代码混淆、没有加密,17,030 个文件全部以明文形式躺在磁盘上,其中 9,352 个 JavaScript 源文件可读

这不是"反汇编级别"的逆向,而是"直接读源码"级别。整个系统的业务逻辑、数据库结构、接口协议、加密密钥,全部一览无余。

17,030
解包文件总数
9,352
明文 JS 文件
1,509
HTML 页面
364
数据表(提取)
5,630
字段总数
74
后端 API 端点

二、技术栈与架构

2.1 应用形态

项目内容
应用名称FO-ST(Front Office – Standard)
版本8.1.202607021300
开发者hi-thinking <developer@hi-thinking.com>
技术形态Electron 桌面应用(Chromium + Node.js)
安装包Nullsoft NSIS v3.04,需管理员权限(requireAdministrator
入口main.js(主进程,74 KB)
前端页面1,509 个 HTML,jQuery + jqGrid 风格的传统多页应用

2.2 关键依赖库

版本用途
@grapecity/activereports^5.1.3报表引擎(商业授权组件)
nedb / nedb-promises^1.8.0 / ^6.2.3本地嵌入式数据库
lokijs^1.5.11本地内存数据库
crypto-js^4.1.1加解密(AES/MD5)
electron-store^6.0.1本地配置持久化
electron-updater4.3.5自动更新
pdfkit / xlsx^0.15.1 / ^0.18.5PDF / Excel 导出
streamsaver^2.0.6大文件流式下载

2.3 服务端架构

后端是 Java Web 应用,部署包名为 huasheng(华胜,厂商内部代号),前端通过 HTTP 与之通信:

服务地址配置:$.HSAPI_SERVER = {客户端设置}[默认模板值 192.168.1.156:8080]
                    ↓  可在登录页齿轮⚙️中修改
接口基址:    $.HSAPI_AJAXURL = HSAPI_SERVER + "/" + HSAPI_PACKAGENAME
             例:http://v8.argylehotels.com:8089/huasheng

注:文档中提到雅阁酒店实际使用 v8.argylehotels.com:8089(酒店编号 1001–1054)和 crsv8.argylehotels.com:8089(1055 以上)。

2.4 通信协议核心

系统几乎所有数据操作都走 同一个"万能接口",这是整个架构最关键的设计:

POST {服务器}/huasheng/qms/mobile/procedure2
Headers: token / hotelId

请求体(AES 加密):
  selectname     → 存储过程名
  sqlwhere       → 查询条件(AES-ECB 加密)
  ...            → 分页/排序参数

响应:JSON

四种调用变体:

本质是一个"通用 SQL 执行网关":前端传存储过程名和条件,后端执行后返回结果集。这种设计让后端无需为每个功能写接口,但把所有业务逻辑压在了数据库存储过程里。

三、后端 API 端点清单

共提取 74 个端点,以下按调用频次排序(已过滤静态资源):

端点路径调用次数
/qms/mobile/procedure2968
/qms/mobile/procedure336
/qms/mobile/procedure423
/qms/mobile/procedure2withoutToken10
/frontoffice/front/imageUtil/saveIdcard9
/qms/mobile/logout7
/hobill/bill-pdf7
/attach/5
/frontoffice/sys/sysuser/editPassword5
/qms/mobile/getToken4
/frontoffice/sys/sysuser/home#page/reservation_new?&showmenu=yes&returnOrClose=close4
/qms/mobile/login_check4
/qms/mobile/download-word-file4
/file/4
/excel/convert3
/qms/mobile/procedure3
/weChat/payment_temp3
/efd/bill3
/efd/devices3
/frontoffice/dead-lock3
/account/export2
/byyy/export2
/h2/getH2DataSet/h2_roomsview2
/qms/mobile/login2
/qr-code-base642
/login/housekeeping/customLogin.jsp?&hotelId=2
/al/payment_temp2
/weChat/payment_refund2
/al/payment_refund2
/weChat/payment_checkout2
/al/payment_checkout2
/weChat/payment_credit2
/al/payment_credit2
/qms/mobile/upload-word-file2
/static/hotel/report/js/plug-ins/business/ajaxfileupload.js2
/frontoffice/front/imageUtil/saveMarcust_img2
/account/export?1
/excel/export641
/mfa-qrcode1
/qms/mobile/proceduregrid1
/qms/mobile/procedure2Post1
/qms/mobile/procedure2bypass1
/upload/file1
/arCustDebitPosting/payment_temp1
/arCustDebitPosting/payment_checkout1
/frontoffice/sys/sysuser/home#page/rsv_group?&returnOrClose=close&1
/frontoffice/front/imageUtil/couponVoucher1
/frontoffice/sys/sysuser/home#page/reservation_new?&showmenu=yes&returnOrClose=return1
/frontoffice/sys/sysuser/home#page/reservation_new?&gno=1
/frontoffice/front/imageUtil/getFacePhotoDb1
/frontoffice/front/imageUtil/saveProfilePhotoDb1
/frontoffice/front/imageUtil/getProfilePhotoDb1
/frontoffice/sys/sysuser/decryptPassword1
/weChat/payment_encode_wechar1
/weChat/payment_encode_ali1
/frontoffice/front/wordModalOper/downloadWord1
/frontoffice/front/wordModalOper/uploadWordFile1
/hobill/bill-srd-download1
/hobill/srd-save1
/efd/send-data1

四、功能模块盘点

按源文件数量排序,反映各模块的复杂度:

模块文件数
systemSettings476
reservation384
js307
assets162
memberManagement150
feast138
roomsManagement126
stStoreIn2122
auditQuery116
ar108
cashiering104
housekeeper88
coupons85
sales81
miscellaneous76
report71
group53
quickKeys50
maintenance48
components42
oms39
busReservation36
upgradeDeploy36
pmscs_ota34
sc20
frontDesk20
initdata20
otaTicket16
homepage14
document14
anewSummarize12
custom12
(根)10
html10
easyFrontDesk8
programmer8
test6
测试4
repastReports2
salesCatering2

五、数据模型(逆向提取)

这是本次逆向最有价值的成果。系统用 1,341 个 .db 文件来描述每个页面组件,每个文件是 JSON 文本,里面直接写着:

{{"cpname":"grid", "tablename":"hotel_cost_set",
 "procedurename":"ALINCustomSelect_Plus30",
 "fields":"id,rcode,item,sn,code,formula,..."}

等于把整个数据库的表名、字段名、存储过程名、页面映射关系全部交了出来。汇总后得到 364 张表、5,630 个字段、60 个存储过程

5.1 核心业务表(按字段数排序 TOP 40)

表名字段存储过程字段示例
viewJqGrid_rsv_profile_list117ALINCustomSelect_Plus30Bl_mess, Blacklist, Mailing, Potential_night, Potential_revenue, Vip, acc_contact, addr…
viewJqGrid_ho_guest_listAll83ALINCustomSelect, ALINCustomSelect_Normal, ALINCustomSelect_Plus30acc_name1, acc_name2, acc_name3, acc_name4, adults, agent, agent_id, arr_c…
View_pms_invoice76ALINCustomSelect_Plus30bill_invoice_amount, bmb_bbh, byzd1, byzd2, byzd3, byzd4, byzd5, bz…
pms_invoice74ALINCustomSelect_Plus30bmb_bbh, byzd1, byzd2, byzd3, byzd4, byzd5, bz, chbz…
viewJqGrid_ho_marcust_arcust73ALINCustomSelect_Plus30address, allsearch, amount, ar_aname, ar_balance, ar_gno, ardate, balance_c…
viewOMS_oms_order_head68ALINCustomSelect_Plus30approved_at, approver_id, approver_name, approver_note, assigned_at, assignee_dept, assignee_id, assignee_name…
viewGridPMS_Reservation68ALINCustomSelect_Plus30Status, accname, adults, allsearch, arr_d, arr_d_c, arr_t, balance_c…
viewJqGridtcoupon_cpcode65ALINCustomSelect_Normal, ALINCustomSelect_Plus30act_code, act_expirydays, act_rate, act_rule, act_startdays, amount, apptype, ava_bmh…
viewGridPMS_ReservationCheck63ALINCustomSelect_Plus30Contact_company, Status, adults, arr_d, arr_d_c, arr_d_end, arr_t, arr_t_end…
viewGridPMS_ReservationAll62ALINCustomSelect_Plus30Status, accname, adults, arr_d, arr_d_c, arr_t, building, child…
viewJqGrid_ho_ArApFr_ledger62ALINCustomSelect_Plus30Ftype, adjust, amount, arcrtype, balance, balance1, balancedate, co_text4…
viewJqGridtCoupon_ledger57ALINCustomSelect_Normal, ALINCustomSelect_Plus30act_code, act_rate, act_rule, actived, actived_by, actived_status, available, cancel…
view_ct_yygk_fb_ct_lszd55ALINCustomSelect_Normal, ALINCustomSelect_Plus30BMMC, DISPRE, JDH, JZRQ, JZSJ, KTRQ, KTSJ, MC…
viewGridPMS_WHSK_ServiceCenter50ALINCustomSelect_Plus30beginning_time, crdate, duration, finished, finished_time, finisheddate, floor_c, getinfo…
viewJqGrid_ct_invoice49ALINCustomSelect_Plus30account, address, ar_coid, ar_no, bank, bmh, bmh_c, checker…
VIEWALL_ho_ftp_setup48ALINCustomSelect_Plus30block, editdefault, ftptype, ico_arrival, ico_ass, ico_assz, ico_checkedin, ico_com…
viewJqGrid_rsv_profile_listall46ALINCustomSelect_Plus30Bl_mess, Blacklist, addr, agent, birthday, car_no, city, cname…
viewGridPMSHo_gledgerFolioHistory42ALINCustomSelect_Plus30ar, arr_d_c, audit_status, ca, cc, closedate, co_name, co_room…
viewlistpms_reservationhis40ALINCustomSelect_Plus30Status, adults, arr_d, charge, child, co_mess, comment, comment1…
viewGridPMS_ho_arledger37ALINCustomSelect_Plus30aname, codate_c, coid, credit, crtype, date_c, dcode, dcode_c…
viewGridPMS_ho_group36ALINCustomSelect_Plus30allsearch, arr_d, balance, c_status, cancel, catering_only, checkin, checkin_pm…
viewJqGrid_ho_rate36ALINCustomSelect_Plus30addrate, bar_code, child, crib, enddate, extra, holidaychild, holidaycrib…
view_ho_guest_credit_card36ALINCustomSelect_Plus30acquirer, amount, authcode, bankname, batchno, cardnumber, cardtype, datetime…
viewJqGrid_ho_gledger_coid_all35ALINCustomSelect_Plus30al, amount, ar, audit_status, bar, ca, cc, closedate…
viewlistpms_ct_yygk_ct_lszd35ALINCustomSelect_Plus30DISPRE, JDH, JZRQ, JZSJ, KTRQ, KTSJ, MC, NO…
viewGridPMS_ho_gledger_all35ALINCustomSelect_Plus30codate, coid, company, cotime, credit, date, dcode, debit…
viewGridPMS_ho_arcust35ALINCustomSelect_Plus30allsearch, aname, ardate, ardate_c, balance_c, barcode, begindate, count_balance…
viewJqGrid_ho_guest_listAll_andhis34ALINCustomSelect_Normaladults, arr_d, arr_d_c, arr_t, exp_dep_d, exp_dep_d_c, exp_dep_t, fname…
viewJqGrid_temp_BILLho_gledger_new34ALINCustomSelect_Plus30arbalance, as_remark, coid, couserid, cousername, credit, date, datetime_c…
viewJqGrid_ho_gledger_coid34ALINCustomSelect_Plus30al, amount, ar, arr_d_c, audit_status, audit_status_c, audit_status_color, bar…
viewGridPMS_ho_arledgerAll34ALINCustomSelect_Plus30codate_c, coid, credit, crtype, custom_keyword, date, date_c, dcode…
ho_ratehd33ALINCustomSelect_Plus30OTA, OtaCancelRule, OtaCompanyRule, OtaCreateRule, OtaMarketRule, OtaRemark, addition, addition_tt…
viewGridPMShotel_mainrpt_setup33ALINCustomSelect_Plus30bug1, bug10, bug11, bug12, bug2, bug3, bug4, bug5…
viewGridPMS_ho_guest32ALINCustomSelect_Normal, ALINCustomSelect_Plus30, PMSReservationSearchStatus, adults, arr_d, child, comp_id, company, exp_dep_d, fname…
viewGridPMS_ReservationOperator32ALINCustomSelect_Plus30DND, L, M, Status, T, agent, arr_d, balance…
view_items_inventory_guest32ALINCustomSelect_Plus30Status, amount, arr_d, attribute, begindate, begindate_c, begintime, code…
viewJqGrid_ho_AR_FR_AP_ledger_all32ALINCustomSelect_Plus30adjust, aname, co_text4, codate, coid, cotime, couserid, credit…
ho_dcode32ALINCustomSelect_Plus30bmh, code, code_b, comm, crtype, d_grp, dc, dcode…
viewGridPMS_message_list32ALINCustomSelect_Plus30arr_d, checkroomuser, chk_Cancel, chk_CheckOut, chk_DueIn, chk_DueOut, chk_InHouse, chk_NoShow…
viewGridPMS_ho_guest_waitroom32ALINCustomSelect_Plus30adults, arr, arr_d, block, child, comp_features, crtime, cruserid…

5.2 存储过程(60 个)

全部以 ALIN/Ho/List 前缀命名,按业务域划分:

通用 CRUD(框架级): ALINCustomSelectALINCustomSelect_NormalALINCustomSelect_Plus30ALINCustomUpdateALINCustomDefaultALINProcedure2Post

预订域: viewGridPMS_ReservationBUSReservationSearchPlusHoReservationRateDetailListRetreveGuestSelect

账务域: HoGledgerPackageListHoARledgerSearchHoBILLS_BillDefault3HoMArledgerAndBootbymno

客户域: HoProfileFromIdentityCardListRetrieveHoProfilePersonAndBootViewJqGrid_ho_marcust_arcust

审计/报表: AUDIT_120SearchHoGroupStatisticsListRetrieveOutputStatistics

六、安全审计发现

高危 硬编码 AES 加密密钥

系统中所有查询条件都用 AES 加密传输,但密钥硬编码在客户端源码里

// js/huasheng.js:127
var key = CryptoJs.enc.Utf8.parse("abcdefgabcdefg12");
var encrypted = CryptoJs.AES.encrypt(srcs, key,
    {mode: CryptoJs.mode.ECB, padding: CryptoJs.pad.Pkcs7});

影响:密钥、模式(ECB)、填充全部已知且全客户端共用。任何人都能构造合法的加密请求体。

更严重:ECB 模式是确定性加密 —— 相同的明文产生相同的密文,攻击者无需解密即可识别重复模式。且密钥 abcdefgabcdefg12 是键盘顺序的弱密钥。

高危 通用 SQL 执行网关 + 条件拼接

架构上 /qms/mobile/procedure2 是一个"传存储过程名 + 传条件"的通用执行器。前端 sqlwhere 参数在客户端拼装后加密发送,后端解密后直接使用。

代码中可见 292 处 SQL 条件字符串拼接sqlwhere + ...)。加密只是"掩码"而非"防护" —— 因为密钥公开,攻击者可自行加密任意恶意条件。

风险:若后端未做参数化或存储过程内存在动态 SQL,将形成注入面。

中危 免鉴权接口

/qms/mobile/procedure2withoutToken 出现 10 次 —— 从命名看是不校验 token 的接口变体。需确认后端是否真的只允许白名单操作,否则是未授权访问入口。

中危 身份信息明文落盘

登录成功后,用户身份写入 localStorage(未加密):

localStorage.setItem("hotelId", hotelId);
localStorage.setItem("userid", data.userid);
localStorage.setItem("token", ...);
localStorage.setItem("kn", data.kn);   // 内部标识

配合硬编码密钥,本机任意进程可读取并冒用会话。

中危 本地硬件接口无鉴权

本地 127.0.0.1:60050 服务暴露 6 类硬件能力,均无任何认证:

端点功能
/readIdentityCard读取身份证
/readIdentityCardHttp读取身份证(HTTP)
/FacePhoto人脸照片采集
/AvisionA6证件扫描仪
/DoorCard门卡读写
/text2audio语音合成

本机任意程序均可调用,可直接读取身份证信息、写入门卡。

低危 默认服务器地址硬编码

源码中残留开发环境地址:192.168.1.156:8080(模板默认),另有报表服务器 192.168.1.12:9090192.168.33.1 等内网地址。

七、认证流程还原

js/login/login.js 还原的完整登录链路:

① 用户输入 酒店编号 / 用户号 / 密码
        ↓
② POST {服务器}/huasheng/qms/mobile/getToken
   → 返回 token、userid、username、hotelname、kn
        ↓
③ 检查在线用户(onlineUser):若已登录 → 弹出占用提示,
   可选择"强制登录"(login 操作)
        ↓
④ POST {服务器}/huasheng/qms/mobile/login_check
   Headers: token, hotelId
   → RETURN=="1" 则登录成功
        ↓
⑤ 身份写入 localStorage:
   hotelId / hotelname / loginTime / bmh / userid / kn / username

登录页支持"第三方登录"(/third/login-url),用于单点登录集成。

八、其他技术观察

报表体系:自建 URL 协议做报表渲染,把 token/hotelId 拼在查询串里:

HSAPI_RPTURL  = hsrpt(s)://{server}/huasheng/rpt?token=..&hotelId=..
HSAPI_RPTPBLURL / HSAPI_RPTEXCELURL / HSAPI_BILLURL(账单 PDF)

WebSocket:两个端点 /ws-sql-log/(SQL 日志)和 /ws-timer/(定时推送),后者用于房态实时刷新、预订数据同步。

支付集成:同时对接 /weChat/*/al/*(支付宝)两套支付流程,各有 payment_temp(预授权)、payment_checkout(结账)、payment_refund(退款)、payment_credit(挂账)。

电子发票:/efd/bill/efd/devices(Electronic Fiscal Device,税务开票设备对接)。

数据导出:/excel/convert/excel/export64/account/export/hobill/bill-pdf

更新机制:electron-updater 使用 generic provider,但 url 字段为空(未配置在线更新);残留 POS_setup_1.0.1.exe 的更新元数据(2020 年)。

九、逆向方法论(可复用)

① 判定应用类型 —— 先 file 看安装包格式:Nullsoft Installer → 用 7-Zip 解包。

② 解包安装器 —— 7z x installer.exe 得到 $PLUGINSDIR/app-64.7z(程序主体)。

③ 提取应用 —— 再解 app-64.7z。若见到 resources/app/ + *.dll + locales/,即为 Electron。

④ 源码定位 —— 有 resources/app.asarasar extract;没有 asar 则源码直接裸露(本例)。

⑤ 找通信层 —— grep -rn "HSAPI_AJAXURL\s*=" 找基址,再 grep "HSAPI_[A-Z_]*\s*:" 拿端点常量表。

⑥ 挖数据模型 —— 找 *.db/*.json 配置,解析 tablename+fields+procedurename 字段,重建 schema。

⑦ 找密钥 —— grep -rnoE "Utf8\.parse\("[^"](8, 40)"\)" 一把捞出硬编码密钥。