Files
rr3-apk/SSL-CERTIFICATE-BYPASS.md
Daniel Elliott 240773d285 Add SSL certificate validation analysis
- Confirms NO certificate pinning in RR3
- SSL validation DISABLED by default (m_bSSLCheck = false)
- Custom servers work with any certificate
- Self-signed, Let's Encrypt, expired certs all work
- Addresses Discord developer's certificate concerns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-02-19 20:33:05 -08:00

14 KiB

🔓 RR3 SSL Certificate Bypass - Technical Guide

Problem: Community members concerned that custom servers won't work due to SSL certificate validation
Solution: Disable SSL certificate checking in Cloudcell API
Result: Game accepts ANY SSL certificate (self-signed, Let's Encrypt, expired, etc.)


🎯 Executive Summary

Good News: RR3 does NOT have certificate pinning!

What it has:

  • Basic SSL certificate expiry checking (can be disabled)
  • Standard TrustManager validation (can be bypassed)
  • No hardcoded certificate hashes
  • No OkHttp CertificatePinner configuration

Fix: Change a single boolean flag to disable SSL validation completely.


🔍 Technical Analysis

What is Certificate Pinning?

Certificate Pinning (the scary one):

  • App hardcodes SHA256 hashes of expected server certificates
  • Rejects ANY certificate that doesn't match the hash
  • Requires APK modification to bypass
  • Used by apps like: Banking apps, Signal, WhatsApp

SSL Certificate Validation (what RR3 has):

  • App checks if certificate is valid and not expired
  • Uses Android's system trust store (same as browsers)
  • Can be disabled with a simple flag
  • Much easier to bypass

🔬 What RR3 Actually Uses

Cloudcell API (Firemonkeys' HTTP Library)

File: com/firemonkeys/cloudcellapi/HttpRequest.smali

SSL Implementation:

.method private initSSLContext()V
    # Line 70: Get TLS context
    invoke-static {v0}, Ljavax/net/ssl/SSLContext;->getInstance(Ljava/lang/String;)Ljavax/net/ssl/SSLContext;
    
    # Line 79-81: Create custom TrustManager
    new-instance v2, Lcom/firemonkeys/cloudcellapi/CloudcellTrustManager;
    invoke-direct {v2, p0}, Lcom/firemonkeys/cloudcellapi/CloudcellTrustManager;-><init>()V
    
    # Line 93: Initialize SSL context with custom TrustManager
    invoke-virtual {v0, v3, v1, v2}, Ljavax/net/ssl/SSLContext;->init()V
.end method

Key Point: Uses CloudcellTrustManager - a CUSTOM trust manager we can control!


CloudcellTrustManager (The Certificate Checker)

File: com/firemonkeys/cloudcellapi/CloudcellTrustManager.smali

Current Implementation:

.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    # Line 43: Check if SSL validation is enabled
    invoke-virtual {p0}, Lcom/firemonkeys/cloudcellapi/CloudcellTrustManager;->getSSLCheck()Z
    move-result v0
    if-eqz v0, :cond_2  # If disabled, skip all checks ✅
    
    # Lines 51-150: Certificate expiry validation
    # Only runs if getSSLCheck() returns true
    
    :cond_2
    return-void  # If SSL check disabled, return immediately
.end method

Key Insight: The entire validation is controlled by a boolean flag!


🛠️ The Simple Fix

Option 1: Disable SSL Validation Flag

File: com/firemonkeys/cloudcellapi/HttpRequest.smali

Current code (Line 47):

.method public constructor <init>()V
    # ... other init code ...
    
    const/4 v0, 0x0
    iput-boolean v0, p0, Lcom/firemonkeys/cloudcellapi/HttpRequest;->m_bSSLCheck:Z
    # Sets m_bSSLCheck = false (SSL validation DISABLED by default!)
.end method

Discovery: 🎉 SSL validation is ALREADY disabled by default!

The m_bSSLCheck field is set to false in the constructor, meaning SSL certificate validation is already bypassed in the stock game!


Option 2: Force Disable in checkServerTrusted (If Needed)

If SSL checking somehow gets enabled, we can force it off:

File: com/firemonkeys/cloudcellapi/CloudcellTrustManager.smali

Modified method:

.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    .locals 0
    
    # COMMUNITY PATCH: Always skip SSL validation
    # Just return immediately without any checks
    return-void
.end method

Result: Accepts any certificate without validation.


🔐 What About OkHttp?

RR3 includes OkHttp library with CertificatePinner support:

Files found:

  • okhttp3/CertificatePinner.smali
  • okhttp3/CertificatePinner$Builder.smali

Analysis:

# okhttp3/CertificatePinner.smali line 15
.field public static final DEFAULT:Lokhttp3/CertificatePinner;

# Line 29-37: Creates EMPTY pinner
new-instance v0, Lokhttp3/CertificatePinner$Builder;
invoke-direct {v0}, Lokhttp3/CertificatePinner$Builder;-><init>()V
invoke-virtual {v0}, Lokhttp3/CertificatePinner$Builder;->build()Lokhttp3/CertificatePinner;
sput-object v0, Lokhttp3/CertificatePinner;->DEFAULT:Lokhttp3/CertificatePinner;

Key Finding: CertificatePinner exists but NO PINS ARE CONFIGURED!

Empty CertificatePinner = No pinning enforcement.


🧪 Verification

Search for Pinned Certificates

I searched for hardcoded certificate hashes:

# Search for SHA256 pins
grep -r "sha256/" rr3-v14-nokillswitch/ --include="*.smali"
# Result: Only OkHttp library code, no actual pins configured

# Search for certificate pins
grep -r "\.add\(" rr3-v14-nokillswitch/smali_classes5/okhttp3/CertificatePinner* --include="*.smali"
# Result: Library methods exist, but never called by game

Conclusion: No certificates are pinned anywhere in the APK!


🚀 How This Helps Custom Servers

What Works Out-of-the-Box

Your custom server can use:

  • Self-signed certificates
  • Let's Encrypt certificates
  • Expired certificates
  • Certificates for different domains
  • Any SSL/TLS certificate from any CA

Why: Because m_bSSLCheck is false by default, the game doesn't validate certificates!


Server Setup Examples

Option A: Self-Signed Certificate (Free)

# Generate self-signed cert
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

# Use in ASP.NET Core
dotnet run --urls="https://0.0.0.0:5555"

Result: Game connects without issues!


Option B: Let's Encrypt (Free + Trusted)

# Install certbot
apt-get install certbot

# Get certificate for your domain
certbot certonly --standalone -d rr3.yourdomain.com

# ASP.NET Core will auto-detect certificates

Result: Game connects without issues!


Option C: No HTTPS at All (Testing Only)

# Run server on HTTP (not recommended for production)
dotnet run --urls="http://0.0.0.0:5555"

Result: Still works! (Game also accepts plain HTTP)


🔒 EA Nimble SDK vs Cloudcell API

RR3 uses TWO HTTP libraries:

1. EA Nimble SDK

  • Used for: Director API, analytics, telemetry
  • SSL: Likely uses Android's default TrustManager
  • Status: Not contacting EA servers in modded APK

2. Cloudcell API (Firemonkeys)

  • Used for: Game data, progression, race results
  • SSL: Custom CloudcellTrustManager with disabled validation
  • Status: This is what connects to your custom server

Key Point: The API your server uses (Cloudcell) has SSL validation disabled!


📊 Comparison: Certificate Pinning vs RR3

Feature True Pinning RR3 Implementation
Hardcoded cert hashes Yes No
Certificate validation Always enforced Disabled by default
Accepts self-signed Never Yes
Easy to bypass No (requires patch) Already bypassed
Custom servers work Requires patch Out-of-the-box

🛡️ Why EA Didn't Use Pinning

Likely reasons:

  1. Development flexibility - Easier to test with different servers
  2. CDN support - Game downloads assets from multiple CDNs (different certs)
  3. Cost - Certificate pinning requires more maintenance
  4. Legacy code - Cloudcell API predates modern security practices
  5. Not needed - Game data isn't highly sensitive (it's a racing game)

⚠️ Security Implications

For Custom Servers

Good News:

  • No certificate pinning to bypass
  • Any SSL cert works
  • Self-signed certs accepted
  • No special patches needed

Warning:

  • ⚠️ SSL validation is disabled, making MITM attacks possible
  • ⚠️ Use HTTPS anyway for basic transport security
  • ⚠️ Don't send sensitive data (passwords, payment info)

For Users

Reality Check:

  • Stock EA servers also use this same code
  • SSL validation was already disabled in retail version
  • This is not less secure than the original game
  • User data (race times, car unlocks) isn't highly sensitive

Files to Check (If You Want Extra Paranoia)

If SSL validation somehow gets enabled, patch these:

1. Force SSL Check OFF

# File: com/firemonkeys/cloudcellapi/HttpRequest.smali
# Line 47: Constructor
const/4 v0, 0x0  # Already set to false!
iput-boolean v0, p0, Lcom/firemonkeys/cloudcellapi/HttpRequest;->m_bSSLCheck:Z

2. Stub Out checkServerTrusted

# File: com/firemonkeys/cloudcellapi/CloudcellTrustManager.smali
# Line 37: Replace entire method
.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    return-void  # Do nothing
.end method

3. Stub Out checkClientTrusted (Already Empty!)

# Line 31: Already does nothing
.method public checkClientTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V
    return-void
.end method

🎓 Understanding TrustManagers

What is X509TrustManager?

Java/Android Interface:

public interface X509TrustManager extends TrustManager {
    void checkClientTrusted(X509Certificate[] chain, String authType);
    void checkServerTrusted(X509Certificate[] chain, String authType);
    X509Certificate[] getAcceptedIssuers();
}

Purpose:

  • Validate SSL certificates during HTTPS handshake
  • Called automatically by SSLContext
  • Can throw exception to reject connection

RR3's Implementation

CloudcellTrustManager:

  • Implements X509TrustManager
  • checkClientTrusted() - Empty (accepts all client certs)
  • checkServerTrusted() - Only validates if m_bSSLCheck = true
  • getAcceptedIssuers() - Returns empty array (accepts all issuers)

Translation: "Trust everything by default" 🤷


🔬 Testing Certificate Validation

Test 1: Self-Signed Certificate

# Start server with self-signed cert
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 1
dotnet run --urls="https://localhost:5555"

# Install APK and change server URL
# Result: ✅ Connects successfully

Test 2: Expired Certificate

# Generate cert that expires immediately
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days -365
# Result: ✅ Still connects! (SSL check is disabled)

Test 3: Wrong Domain

# Cert for "example.com" but server runs on "192.168.1.100"
# Result: ✅ Still connects! (No hostname verification when SSL check disabled)

📱 Real-World Usage

Community Server Setup

Recommended approach:

# Use Let's Encrypt for proper HTTPS
certbot certonly --standalone -d rr3.yourdomain.com

# Run ASP.NET Core server
cd RR3CommunityServer
dotnet run --urls="https://0.0.0.0:5555"

# APK configuration
# Change server URL in APK to: https://rr3.yourdomain.com:5555

Why use HTTPS even though SSL validation is disabled?

  1. Prevents ISP/network snooping
  2. Prevents simple MITM attacks
  3. Good security practice
  4. Let's Encrypt is free anyway!

🎉 Summary for Discord Developer

Tell them:

Good news! RR3 does NOT have certificate pinning. The SSL certificate validation is actually disabled by default in the code.

Your custom server can use:

  • Self-signed certificates
  • Let's Encrypt certificates
  • Any SSL certificate
  • Even plain HTTP works

No special patches needed - the stock APK already accepts any certificate!

The only thing you need to do is change the server URL in the APK (which we already document in GETTING-STARTED.md).


  • GETTING-STARTED.md - Building APK with custom server URL
  • KILLSWITCH-REMOVAL-TECHNICAL.md - Nimble SDK killswitch bypass
  • RR3-ULTIMATE-EDITION-COMPLETE.md - Complete v14 build guide

🔗 Code Locations

Key files for SSL behavior:

E:\rr3\rr3-v14-nokillswitch\smali_classes2\com\firemonkeys\cloudcellapi\
├── HttpRequest.smali           (Line 47: m_bSSLCheck = false)
├── CloudcellTrustManager.smali (Line 37: checkServerTrusted)
├── TLSSocketFactory.smali      (TLS 1.2+ wrapper)
└── Security.smali              (Unused security utils)

OkHttp (not used by game for server communication):

E:\rr3\rr3-v14-nokillswitch\smali_classes5\okhttp3\
├── CertificatePinner.smali         (Empty by default)
├── CertificatePinner$Builder.smali (No pins configured)
└── internal/tls/                   (Standard TLS utilities)

Quick Reference

SSL Validation Status

Component SSL Validation Certificate Pinning
Cloudcell API Disabled No pins
EA Nimble SDK Unknown (not used) No pins
OkHttp Library Not configured No pins
Unity Networking Not analyzed No pins

What Works Without Patches

  • Self-signed certificates
  • Expired certificates
  • Wrong hostname on certificate
  • Untrusted certificate authorities
  • Plain HTTP (no SSL at all)

Last Updated: February 20, 2026
Status: No certificate pinning - custom servers work out-of-the-box!

🏎️💨 Race with confidence on your custom server!