Original supplied text, unchanged. Read alongside the revision record above and the separately pinned 2.12.1 evidence. Its line references refer to the author’s webcrack output.
# C2 / Distributed Task Grid — Evidence Report
## Steam Inventory Helper (SIH) v2.11.12 (`cmeakgjggjdlcpncigglobpjbkabhmjl`)
**Method.** Static analysis of the unpacked MV3 build. The webpack bundles were unpacked module-by-module with **webcrack** (`bundle/js/background.js` → 120 modules; `bundleAngular/backgroundAngular.js`; `js/siteExt/global.bundle.js` → 8 modules). Line references below point to the webcrack output (`922.js` = the main 4.1 MB module). No dynamic execution was performed; every claim is backed by decompiled code.
---
# 1. VERDICT
| Claim | Status | Basis |
|---|---|---|
| The extension operates a persistent **command-and-control (C2) channel** to developer servers | **PROVEN** | §3: WSS agent, task protocol, restart/ping logic |
| The server can **push tasks to clients** that execute **Steam market scraping** through the user's authenticated session | **PROVEN** | §4: `task.new` → `trader_tool.steam.get_price_overview(s)` etc. |
| The server can **remotely operate the user's account**: send / accept / decline / cancel trades, sell items, remove listings | **PROVEN** | §4: `tradesend`, `tradeaccept`, `tradedecline`, `tradecancel`, `sell_item(s)` |
| The server can **enable/disable and restart the agent fleet per user-bucket** (canary rollout by steamId digit) | **PROVEN** | §5: `/sih/ping` → `controllerIds`, `controller`, `timer` |
| Requests run from **residential IPs with the user's cookies** (invisible to rate-limit / anti-bot systems) | **PROVEN** | §6: cookie assembly + DNR header injection |
| The install base functions as a **distributed scraping grid** for the developer's commercial price database | **PROVEN by architecture** | §4 + §7: results uploaded to `items.steaminventoryhelper.com` and resold as "SIH Steam Median" |
| The fleet could be repurposed as a **Layer-7 DDoS instrument** | **CAPABILITY PRESENT, ABUSE NOT EVIDENCED** | §8: nothing in code commands abusive mass requests today; the transport, auth and dispatch layer to do so fully exists |
**Bottom line:** this is not a classical malware botnet (no keylogging, no arbitrary code execution from C2), but it **is a remotely-tasked execution grid over users' authenticated Steam sessions**, and its scraping tasks are distributed across residential IPs by design. Users' network reputation and session credentials power a commercial data-aggregation service.
---
# 2. FULL ENDPOINT INVENTORY (IPs / banners / logs / C2)
## 2.1 Hardcoded IP addresses
Scanned every `.js`, `.json`, `.html` in the package. **One real IP literal exists** (all other numeric patterns are library version strings):
| IP | Where | Purpose |
|---|---|---|
| `23.105.226.164` | `js/siteExt/addfunds.bundle.js` | Paid banner injected into Steam's "add funds" page: `http://23.105.226.164/Phx3RR?placement=refillpage` |
Facts that make this endpoint a finding on its own:
- **Plain HTTP** (no TLS) → any on-path attacker can substitute the image and the `target="_blank"` link target (malware interstitials, phishing).
- **Hardcoded IP, no domain** → no certificate, no revocation path, no owner accountability; typical of gray-market ad placements.
- **Locale targeting**: the banner renders only when Steam language is `ru` (`"ru"!==r` guard) and has a local "close" persistence flag — regionally-targeted undisclosed advertising inside Steam UI.
## 2.2 Banner / ad-serving endpoints
| Endpoint | Evidence |
|---|---|
| `ads.steaminventoryhelper.com/api/v1/adapi/<token>/find-all` | POST; body = targeting object `{f: {...}}` (server-side per-user ad selection); adSpaceTokens: `marketSponsor` (`mllkor7du1wiepgo`), `tradeoffer` (`2d243388513b44459fdcf78f270362c4`), `marketLeftSideBanner` (`rk1lknvys9fjx357`), `externalIntegrationBanner` (`qizy368ile7ewbuv`), `marketSponsorBanner` (`ujj9var1a8ow50lx`) |
| `download.steaminventoryhelper.com/banners` | banner + ad-token download |
| `gainskins.steaminventoryhelper.com/api/v2/sih/steam/banner(-list)` | gambling-sector banners |
| `t.sih-db.com/promo?subid1=trade_window&subid2=csgofast` | CSGOFAST referral injected in the trade window |
| `http://23.105.226.164/Phx3RR?placement=refillpage` | hardcoded-IP banner (see 2.1) |
Ad telemetry: `AD_HIT_SEND` alarm reports impressions/clicks back to the ad API.
## 2.3 Logging / telemetry endpoints
| Endpoint / channel | Payload |
|---|---|
| `wss://wss-new.steaminventoryhelper.com` (C2) | handshake: `{client:"sih-extension", clientVersion, capabilities}`; heartbeat 25 s; reconnect backoff `[10s, 10s, 20s, 30s, 50s, 80s, 130s]` |
| `sentry.steaminventoryhelper.com` DSN `3f2c5883e4126dccaf4c6e9645bf4230@.../12` | exceptions + **`userInfo` (full Steam profile) and `sihAppUserProfile` attached as event extras** (`setExtraSihUser`, 922.js:24063–24079). **Correction of earlier notes:** the `Breadcrumbs` integration is explicitly filtered out (922.js:24098–24105) — DOM/XHR breadcrumbs are NOT sent. The user-data-in-extras finding stands. |
| `items.steaminventoryhelper.com/prices/v2/update` | POST: user's observed prices `{steamid, hash_name, app_id, prices, buy_price, sold, orders, updatedAt}` (batch ≤ 1000 items) (922.js:135699–135703) |
| `core.steaminventoryhelper.com/historystatsoverprices` | POST: purchase/market history, base64, `x-sih-token`, `withCredentials` |
| `core.steaminventoryhelper.com/sih/ping` | GET with `x-sih-token`: **server→client control response** (§5) |
| `core.sih.app/sih/backups/sync` | POST: **base64 maFiles + manifest** (Steam Guard `shared_secret`/`identity_secret`), header `X-Sih-Token` (922.js:99652–99664) |
| `core.steaminventoryhelper.com/steam/check` | POST `steamUsername` |
| `skinpay.sih.app/steam-inventory/<steamId>/<appId>` | third-party inventory fetch by steamId (server-side proxy) |
| `gamestats.steaminventoryhelper.com/api/v1|v2` | key-orders, tournaments, online stats |
| Event streams | `sendEventData`, `sihEventReg`, `SEND_EVENT_REGISTRATION_V2`, `sendMircoStats`, `sendUserHistoryInfo` (background alarms) |
## 2.4 C2 event names (complete list)
Declared at 922.js:30887–30902 — this is the server→client command vocabulary:
```
connect handshake (client id, version, capabilities)
jsonsend generic command packages: refreshTradeList, balanceUpdated,
wishList, followgame, refreshWithdrawOrder, steamRefill,
updateUser, sihrep, trader_tool.steam.{get_my_listings,
sell_item, remove_listing} (922.js:58006)
tradesend server orders the client to SEND a trade offer (922.js:80728–80778 "Trade send: called")
tradeaccept server orders the client to ACCEPT a trade offer
tradedecline server orders the client to DECLINE a trade offer
tradecancel server orders the client to CANCEL a trade offer
task.new server assigns a task to the client (922.js:74793–74803)
task.result client reports the task outcome back
syncuserinfo server orders an on-demand user-data upload
permissionsget / permissionsrequest / permissionsedit
vYPRqkhY88H9iuTxrcm / vYPRgkhY88H91uTxrcm / vYPRqkhY88H91uTxrcm / vYPRqjhY88H91uTxrcm
← 4 deliberately OBFUSCATED event names (unnamed handlers)
```
`task.new` actions (922.js:30871–30879, dispatcher 74793–74803):
```
trader_tool.steam.get_my_listings
trader_tool.steam.get_market_history
trader_tool.steam.get_price_overview ← distributed price scraping
trader_tool.steam.get_price_overviews ← batch price scraping
trader_tool.steam.sell_item ← sell on the user's account
trader_tool.steam.sell_items ← mass sell
trader_tool.steam.remove_listing ← 922.js:71398–71425
trader_tool.steam.remove_listings
trader_tool.steam.get_wallet_currency
```
---
# 3. THE C2 CHANNEL (PROOF)
`service-worker.js` → `bundle/js/background.js` → agent bootstrap (922.js:80905–80951):
```js
var i = new zy.WSProvider("wss://wss-new.steaminventoryhelper.com", o, e);
n.wrap(i); // retry wrapper, delays [10s,10s,20s,30s,50s,80s,130s]
var s = new Vy(XA, i, r, a); // Vy = agent; XA = event->handler map
var c = new Yy(s, "sih_app_market_toggle", ...); // runner, 2 s poll
```
- The agent (`Vy`, 922.js:30219–30260) subscribes every handler to `provider.onmessage(type, handler)` and maintains a persistent socket with a 25-second ping and exponential reconnect backoff — standard resilient C2 transport.
- The handshake advertises `{client: "sih-extension", clientVersion, capabilities}` (922.js:30880–30886) — the server knows each node's version and feature set.
- The agent can be **stopped / restarted / re-enabled remotely** (`restartAgent`, `updateState`, `ok.run(true)` / `ok.stop(true)`, 922.js:81646–81759).
- Four event names are obfuscated (`vYPR...`), i.e. the developers deliberately hid part of the command vocabulary from casual review.
# 4. TASK EXECUTION THROUGH THE USER'S SESSION (PROOF)
Handler `task.new` → action dispatch → concrete example, remove-listing task (922.js:71398–71425):
```js
n = e.data.listingId; // task payload from the server
o = e.taskId;
zS(n, r.storage.get("steamId"), r.storage.get("sessionId")); // executed AS THE USER
r.provider.send({ event: "task.result", payload: { taskId: o, data: { ok: true, listingId: n } } });
```
The agent's storage holds **`steamId` and `sessionId` of the logged-in user**; every task executes Steam calls with those credentials, and the outcome is reported back over the C2 socket. The identical pattern applies to price-scraping tasks (`get_price_overview(s)`), which feed the commercial price database at `items.steaminventoryhelper.com` (crowd-sourced via `prices/v2/update`, resold as "SIH Steam Median" and as a paid price provider).
Additionally, the `sih_app_market_toggle` agent runs an **autonomous buy loop** (922.js:127030–127218): it takes the top item from a local buy-stack (`sihAppBuyStack`), checks `user.balance / 1000 >= price`, calls `POST /sih/buy` (with `steam_id`, `custom_trade_link`, 922.js:27465–27517), retries up to 10 iterations per item with a 10-minute deadline (`finishOrderAt = Date.now() + 600000`), and logs outcomes to the developer's storage log.
# 5. FLEET GATING — SERVER-SIDE ROLLOUT CONTROL (PROOF)
`pingUser` → `GET core.steaminventoryhelper.com/sih/ping` (922.js:25960–26029) returns:
| Server field | Client effect |
|---|---|
| `controllerIds` | stored to `AVAILABLE_CONTROLLER_LAST_NUMBER_IDS`; agent enabled only if **the last digit of the user's steamId is in the list** — 10% bucket canary control of the fleet (922.js:161250–161264) |
| `controller` | stored to `server_switch_controller` — global kill/enable switch |
| `timer` | stored to `INVENTORY_PARSING_INFO.timer` — remote tuning of inventory parsing cadence |
| `itemPayIds` | stored to `AVAILABLE_ITEM_PAY_IDS` — server-chosen item sets |
The same `/sih/ping` request uploads the user's state (`disabled` reasons, `userGemsCount`).
# 6. WHY THE TRAFFIC LOOKS LEGITIMATE (RESIDENTIAL-IP SCRAPING)
- All Steam requests originate in the **user's browser** — real residential IP, real browser TLS fingerprint, real `sessionid`/`steamLoginSecure` cookies.
- Cookies are read via `chrome.cookies` (e.g. 922.js:33450) and, where the fetch layer needs them, the extension **creates dynamic `declarativeNetRequest` rules that set the `Cookie` header** on its own requests (rule builder 922.js:124863–124912; `setRule`/`removeRule` wrapper 922.js:13161–13324). The `rules.json` shipped in the package is `[]` — all header-manipulation rules are created **dynamically at runtime**, invisible to store review.
- The code self-throttles to stay below Steam's abuse radar: `TRADE_REQUEST_LIMIT_COUNT` (> 20 → pause), `CLEAR_TRADEOFFER_REQUEST_LIMIT` alarm, `STEAM_RATE_LIMITED` handling, and a **server-controlled delay** for price uploads (`a.data.delay` from the `prices/v2/update` response, 922.js:135711–135719).
Net effect: scraping that would be blocked from datacenter IPs is laundered through hundreds of thousands of residential users — each individual client stays under the limit, while the aggregate fleet harvests continuously.
# 7. DATA LOOP (WHAT THE GRID PRODUCES)
```
fleet users ──(task.new: get_price_overview / own browsing)──► Steam endpoints
│ │
│ (residential IP + user session) ▼
│ items.steaminventoryhelper.com
│ prices/v2/update (per-user batches)
▼ ▼
C2: task.result / historystatsoverprices / Sentry userInfo aggregated price DB
│ ("SIH Steam Median",
▼ premium subscriptions)
developer-controlled commercial dataset
```
# 8. RISK ASSESSMENT
## 8.1 Proven risks
| # | Risk | Severity | Evidence |
|---|---|---|---|
| R1 | **C2 over user accounts** — the server can send/accept/decline/cancel trades and sell/remove items on every connected client | CRITICAL | §2.4, §4 |
| R2 | **Distributed scraping grid** — price-history harvesting at scale through residential IPs | CRITICAL | §4, §6 |
| R3 | **Steam Guard secrets uploaded to developer cloud** (`backups/sync`, plaintext when SDA unlocked) — server compromise = mass account takeover with 2FA bypass | CRITICAL | §2.3 |
| R4 | **Obfuscated command vocabulary** (4 unnamed `vYPR*` events) — part of the remote command surface is deliberately hidden from review | HIGH | §2.4 |
| R5 | **Fleet canary gating by steamId digit** — silent enable/disable of node capabilities per bucket | HIGH | §5 |
| R6 | **HTTP banner on hardcoded IP** (MITM content injection into Steam pages, RU-locale targeting) | HIGH | §2.1 |
| R7 | **Full economic profile exfiltration** (prices, orders, history, balances) + `userInfo` into Sentry | HIGH | §2.3 |
| R8 | Auto-confirm + auto-buy loops can move items/money with a single server-side or logic error | HIGH | §4 |
## 8.2 Capability present, abuse not evidenced (must be stated honestly)
- **Layer-7 DDoS / mass-targeting**: the transport (persistent WSS + 25 s heartbeat), the dispatch layer (`task.new`, `jsonsend`) and the authenticated request machinery exist, so the fleet **could** be pointed at any Steam or third-party endpoint at any rate the server chooses. In the audited version, task types are limited to Steam market endpoints and no command floods targets. Verdict: **capability present; no evidence of current abuse in code.**
- **Arbitrary URL fetching**: the universal HTTP client (`Gs.Ay.apiRequest`) + `<all_urls>` + dynamic DNR rules could technically fetch arbitrary hosts with arbitrary headers. The observed call graph targets Steam, SIH's own domains and 5 partner-market APIs. Verdict: same — **capability present, not exercised.**
## 8.3 Vulnerabilities (concrete, demonstrable)
1. **MITM on the ad chain**: `http://23.105.226.164/...` banner (no TLS) inside authenticated Steam pages → on-path attacker controls link/image content.
2. **Secret upload channel**: `backups/sync` base64-encodes maFiles; base64 is encoding, not encryption — server-side storage security is unknown and unauditable; combined with auto-confirm this is a full account-takeover path.
3. **Cookie-header injection machinery**: dynamic DNR `modifyHeaders` rules (runtime-created, `rules.json` empty at review time) can set any cookie value on any request the worker makes — a general-purpose session-impersonation primitive.
4. **Un-auditable remote behavior**: 4 obfuscated event names + server-pushed toggles mean the reviewed code does not equal the deployed behavior; Google's review cannot statically enumerate the command surface.
---
# 9. GOOGLE POLICY MAPPING (Chrome Web Store)
| Policy | Violated? | Mapping |
|---|---|---|
| **User Data Privacy** — limited use, disclosure, secure handling | **YES** | R3, R7: authentication secrets and full economic profiles leave the browser without prominent disclosure; "description" claims only "prices, item details, trade status" |
| **Permissions — minimum scope** | **YES** | `<all_urls>` + `cookies` + `webRequest` + `declarativeNetRequestFeedback` + `management` for a "price helper"; DNR header-injection machinery at runtime |
| **Single Purpose** | **YES** | price helper + authenticator + trade bot + C2 agent + gambling + ads in one package |
| **Deceptive / Misleading functionality** | **YES** | hidden C2 agent with obfuscated commands; undisclosed remote task execution on the user's account |
| **Ads policy** (clear labeling) | **YES** | injected sponsored banners/links in Steam UI incl. an HTTP hardcoded-IP banner targeted at RU users |
| **Malware-like behavior / remote code control** | **RISK — strongest case** | an always-on C2 socket that can operate the user's account and whose command vocabulary is partially obfuscated is, functionally, a remotely-controlled execution agent on every client |
---
# 10. APPENDIX — REPRODUCIBILITY
```bash
npx webcrack bundle/js/background.js -o bg # 120 webpack modules
npx webcrack bundleAngular/backgroundAngular.js -o ang
npx webcrack js/siteExt/global.bundle.js -o global
```
Key line references (webcrack output, main module `922.js`):
- C2 bootstrap 80905–80951; agent class 30219–30260; event names 30871–30902
- task dispatcher 74793–74803; remove-listing task 71398–71425; jsonsend 57996–58085
- tradesend handler 80728–80778; ping/control 25960–26029; steamId-bucket gate 161250–161264
- buy loop 127030–127218; /sih/buy 27465–27517; maFiles sync 99620–99679
- DNR rule engine 13161–13324; cookie-header injection 124863–124912
- Sentry extras 24063–24108; price upload 135670–135719
*All findings are static-analysis facts of the audited build; the "abuse not evidenced" qualifiers are kept deliberately to keep the report defensible.*