Skip to main content

ml-model-extraction

Extract a functional clone of a black-box ML model via prediction API queries, and infer whether specific records were in the training set (membership inference).

소스 정보

저장소
BitterSecurity/Decepticon
최근 소스 활동
2026년 5월 30일 01:29
감지된 SKILL.md 언어
영어
스타
5,666
포크
1,067

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
ml-model-extraction
description
Extract a functional clone of a black-box ML model via prediction API queries, and infer whether specific records were in the training set (membership inference).
allowed-tools
Bash Read Write
metadata
{"subdomain":"ai-security","when_to_use":"model extraction model stealing prediction API clone membership inference training data privacy ML model black-box oracle query budget","tags":"adversarial-ml, model-extraction, model-stealing, membership-inference, privacy, ml-api","mitre_attack":"T1213, T1119, T1590"}
# ML Model Extraction and Membership Inference Two closely related attacks against deployed ML prediction APIs: - **Model extraction (stealing):** reconstruct a functionally equivalent clone of the target model using only its input/output pairs, without access to weights, architecture, or training data. (Tramer et al. 2016, "Stealing Machine Learning Models via Prediction APIs") - **Membership inference:** determine whether a specific record was used to train the target model, breaching training-data confidentiality. (Shokri et al. 2017, "Membership Inference Attacks Against Machine Learning Models") These are distinct from LLM prompt-extraction attacks: the target is a classical ML model (logistic regression, decision tree, DNN) or an ML-as-a-service endpoint (AWS SageMaker, GCP AutoML, Azure ML, custom REST API). > **Authorized use only.** Query-volume attacks against commercial ML APIs may violate terms > of service, the CFAA, and GDPR Article 22. Confirm scope includes data-privacy testing of > the prediction endpoint before proceeding. --- ## ATT&CK Mapping | Technique | Use | |---|---| | T1213 — Data from Information Repositories | Reconstructing training data via repeated model queries | | T1119 — Automated Collection | Systematic API querying to build a clone dataset | | T1590 — Gather Victim Network Information | Fingerprinting the ML service to determine model family | --- ## 1. Reconnaissance — profile the target API ```bash # 1a. Determine output type curl -s -X POST "$API/predict" -d '{"features": [0,0,0]}' | jq . # Does it return: hard label only? Confidence scores? Probability distributions? # Probabilities = highest information; enables equation-solving extraction # 1b. Infer model type from decision-boundary shape # Binary classifier: probe boundary by linear interpolation between two known-class samples # Multi-class: vary one feature at a time, record where predicted class changes # Decision-tree vs smooth boundary: tree-type models have axis-aligned boundaries # 1c. Count features and valid ranges from API docs / error messages curl -s -X POST "$API/predict" -d '{}' | jq .error # Validation errors often expose expected feature names and types # 1d. Estimate query budget (cost / rate limit) # Most commercial APIs: ~$0.0001–0.001 per query; 10k–1M queries typical for extraction ``` --- ## 2. Model extraction ### 2a. Equation-solving attack (logistic/linear models) For models returning exact probability scores, d features → d+1 queries suffice (Tramer): ```python import numpy as np from scipy.optimize import fsolve def extract_logreg(query_fn, n_features, n_classes): """ Solve for weight matrix W using the system of equations: softmax(W @ x_i) = query_fn(x_i) n_features * n_classes unknowns, solved via n_features * n_classes + 1 queries. """ queries = [np.random.randn(n_features) for _ in range(n_features * n_classes + 1)] scores = [query_fn(q) for q in queries] # shape: (n_queries, n_classes) # Fit a local logistic regression to (queries, scores) using soft targets from sklearn.linear_model import LogisticRegression surrogate = LogisticRegression(multi_class='multinomial', max_iter=1000) labels = [np.argmax(s) for s in scores] surrogate.fit(queries, labels) return surrogate # clone with equivalent decision boundary # Verify: clone accuracy vs target on held-out natural inputs should be >95% ``` ### 2b. Path-finding attack (decision trees) Decision trees are fully recoverable with O(n_leaves * depth) queries via adaptive path traversal: ```python def extract_decision_tree(query_fn, feature_bounds, max_depth=10): """ Recursively find split thresholds by binary search. For each feature, probe the boundary where the predicted class changes. """ from sklearn.tree import DecisionTreeClassifier X_queries, y_labels = [], [] # Adaptive sampling: grid + boundary refinement for _ in range(5000): x = np.random.uniform(feature_bounds[:, 0], feature_bounds[:, 1]) y = np.argmax(query_fn(x)) X_queries.append(x) y_labels.append(y) surrogate = DecisionTreeClassifier(max_depth=max_depth) surrogate.fit(X_queries, y_labels) return surrogate ``` ### 2c. Neural network surrogate (DNN, general) For deep models, train a surrogate network on (query, prediction) pairs using soft labels: ```python import torch, torch.nn as nn def extract_dnn_surrogate(query_fn, input_dim, n_classes, query_budget=50000): """ Knockoff Nets approach: sample diverse inputs, label with target API, train clone. """ # Step 1: collect a representative query dataset # Use natural images / domain data if available; random noise also works for coarse clones X_steal = collect_diverse_inputs(query_budget) # shape: (N, input_dim) y_soft = np.array([query_fn(x) for x in X_steal]) # soft labels from API # Step 2: train surrogate with knowledge distillation on soft labels surrogate = build_surrogate_nn(input_dim, n_classes) optimizer = torch.optim.Adam(surrogate.parameters(), lr=1e-3) kl_loss = nn.KLDivLoss(reduction='batchmean') for epoch in range(50): for xb, yb in batches(X_steal, y_soft): pred = torch.log_softmax(surrogate(xb), dim=1) loss = kl_loss(pred, torch.tensor(yb, dtype=torch.float32)) loss.backward(); optimizer.step(); optimizer.zero_grad() return surrogate # Reference implementation: https://github.com/tribhuvanesh/knockoffnets ``` ### 2d. Using Steal-ML (Tramer reference implementation) ```bash git clone https://github.com/ftramer/Steal-ML cd Steal-ML pip install -r requirements.txt # Configure target: edit config.py with your API endpoint # Modes: logreg, mlp, dtree, svm python steal.py --model logreg --n_queries 5000 --target $API_URL # Outputs: cloned_model.pkl + fidelity score (agreement with target) ``` --- ## 3. Membership inference Determine whether a specific record (x, y) was used to train the target model. ### 3a. Shadow model attack (Shokri et al.) ```python # Principle: models generalize less to non-training data. # Train "shadow models" on datasets similar to the target's. # Train an "attack classifier" on (shadow_model_outputs, member/non-member). # Apply attack classifier to target model outputs for test records. from sklearn.ensemble import RandomForestClassifier def membership_inference_attack(target_query_fn, shadow_models, shadow_train_sets, shadow_test_sets, candidate_records): """ shadow_models: list of models trained on shadow_train_sets shadow_train_sets: data used to train each shadow model (members) shadow_test_sets: data NOT used (non-members) """ # Build meta-training set for attack model X_meta, y_meta = [], [] for model, train_set, test_set in zip(shadow_models, shadow_train_sets, shadow_test_sets): for x in train_set: X_meta.append(model.predict_proba([x])[0]) y_meta.append(1) # member for x in test_set: X_meta.append(model.predict_proba([x])[0]) y_meta.append(0) # non-member attack_clf = RandomForestClassifier(n_estimators=100) attack_clf.fit(X_meta, y_meta) # Query target and classify results = {} for record in candidate_records: conf_vector = target_query_fn(record) results[record] = attack_clf.predict([conf_vector])[0] # 1=member, 0=non-member return results ``` ### 3b. Confidence-threshold attack (simple, no shadow models) When a model overfits, training examples get very high confidence scores: ```python def threshold_membership_inference(query_fn, candidate_records, threshold=0.95): """ Heuristic: if predicted confidence for the correct class exceeds threshold, the record was likely in the training set. Works best against overfitted models (small datasets, many epochs). """ for record, true_label in candidate_records: scores = query_fn(record) # confidence vector if scores[true_label] >= threshold: print(f"MEMBER (confidence {scores[true_label]:.3f}): {record[:5]}...") else: print(f"Non-member (confidence {scores[true_label]:.3f}): {record[:5]}...") ``` ### 3c. Using ML Privacy Meter ```bash pip install ml-privacy-meter python - <<'EOF' from privacymeter.audit import Audit from privacymeter.information_source import InformationSource # target_model: your model or API wrapper # target_train: records suspected to be in training set # target_test: records known NOT to be in training set (baseline) audit = Audit( target=InformationSource(model=target_model, dataset=target_train), reference=InformationSource(model=target_model, dataset=target_test), ) report = audit.prepare_privacy_risk_report() report.save("membership_inference_report.pdf") EOF ``` --- ## 4. Query budget and operational considerations | Model type | Extraction queries | Clone fidelity | Key reference | |---|---|---|---| | Logistic regression (d features) | d+2 (exact) | ~100% | Tramer 2016 | | Decision tree (depth k, d features) | O(d * 2^k) | ~100% | Tramer 2016 | | DNN, surrogate+KD | 10k–100k | 80–95% | Knockoff Nets 2018 | | DNN, hard-label only | 100k–1M | 70–90% | PRADA 2019 | **Rate limit evasion:** distribute queries across IPs/accounts; randomize timing; use domain-valid inputs to avoid anomaly detection on the API side. --- ## 5. Validation ```bash # Measure clone fidelity: agreement rate on held-out inputs python - <<'EOF' import numpy as np X_test = load_held_out_test_set() target_labels = [query_target_api(x) for x in X_test] surrogate_labels = [surrogate_model.predict([x])[0] for x in X_test] fidelity = np.mean(np.array(target_labels) == np.array(surrogate_labels)) print(f"Clone fidelity: {fidelity:.3%}") # >80% = functional clone suitable for transfer attacks # >95% = near-exact extraction, sufficient for IP theft demonstration EOF ``` --- ## 6. Detection signals (defender perspective) - Unusually high query volume from a single account/IP, especially systematic feature sweeps - Queries that span the full input space uniformly (non-natural distribution) - Confidence scores accessed at rate inconsistent with normal application usage - PRADA defence: detect surrogate-training patterns via query-sequence analysis - Watermark model outputs: embed a statistical fingerprint in confidence scores that survives cloning and allows ownership proof - Differential privacy in training reduces membership inference signal at the cost of accuracy
GitHub에서 보기