Created
November 17, 2013 17:41
-
-
Save linusyang/7515929 to your computer and use it in GitHub Desktop.
New WebKit-based Opera Mobile Off-Road Proxy Modifier
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#-*- encoding: utf-8 -*- | |
# | |
# Opera Mobile Off-Road Proxy Modifier | |
# By Linus Yang <[email protected]> | |
# Licensed under GPLv3 | |
# | |
from optparse import OptionParser | |
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED | |
from M2Crypto import BIO, SMIME, X509 | |
import os | |
import zlib | |
import hashlib | |
import base64 | |
class OperaPackageTuner(object): | |
VERSION = '0.1' | |
OVERRIDE_DOMAIN_LIST = ['server4.operamini.com', 'mini5.opera-mini.net', 'mini5cn.opera-mini.net', 'thumbnails.opera.com'] | |
DEFAULT_HTTP_PORT = 80 | |
DEFAULT_SOCKET_PORT = 1080 | |
KEY_CN = 'jGDSpoEfhTZq8jGuQWgxsJQJthTpz6j96NhXfokmNuDgt6FR+WAbkwv1J+qKIr/m+19yUGvT6Bs7VdGJrxfjWy1+ph2Euk5izxwBeJ7bLD88APw8Ce4fyWJzZylHJ+Uq9MmQUW2Neq1OANarUM2MpjcF3wryQ+Zm2tKC1lFLZWeA4QjVkc94kg973uIe0UGaCAZVyirNrcTmTboBtazPcw==' | |
KEY_GLOBAL = 'wd16t34slndG/hBoECbJIPhkgRMhvLi+a7+loD/aThbJyNs68oD3cDNm53jpPFXnFZqIUtKxOB5SGjN/IrFAbN30GjEUrstPS/554MWqK6iCT8mJy4vcv47FzvUXa/1AWfIpuRv6AlEmspX5xAnnX29kFe4JT9f139OVofQxZoxaCOiN6JHcTdONTpqpucANxgSgQo46paKMz6da8JkUew==' | |
PKEY = '''-----BEGIN PRIVATE KEY----- | |
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWkxkE3sYLJLHt | |
x2Lg2dglPj7NbOsd4v8GjKjovKjNa9N4bqcKp2zmDrsPmTVZ/9k+d6lD5+g9S2S4 | |
5P6i0+ZW8eJnqBu/sjC1eMIEQ75Mchi4RvUhFYbwOKFOicK+OH+Ovs+PysPaHuMw | |
yeqT0KfD3ErzUCINUAgHMuCAlxfuagUzWeamlOwss/KEoKRmyHqU2DsxCTpnNy4v | |
ZBLAbm1C8VgY3/4DgcwM1ETabN3DuCRYGUgBsyVkE0+/3pjJKHdI2/VnalQNgVTI | |
u8oHueJHVTMRxGua92/e7MyOaefIotCOeCYglD+Zcn08BP5ymR2Z35uuOKCyF3+j | |
HVtq/ukfAgEDAoIBAQCPDLtYlIQHbcvz2kHrO+VuKX8znfIT7KoEXcXwfcXeR+JQ | |
ScSxxPNECdIKZiORVTt++nDX7/Ao3O3QmKnB4pmPS+xFGr0qdssjpdatgn7doWXQ | |
L04WDln1exY0W9cpev+0fzUKhy08FJd12/G34G/X6DH3isFeNVqvd0BVug/0RXWi | |
hnmONcUztAJ25E5YNqHadWSt+vU4pJOpvxDyE6ZXrBIpHBvlaZf8atJ7maf8iXfS | |
ZUzrqnx1O5zaTGRnGo7o/UdrfuLDfpVXnXBEHm+rk6QTq2ZKyZj6JZQ/K1LB+cXq | |
ZO9KG8oBSecXohQBeJYIDEikB9xHdsvelr1MoYR7AoGBAOrAmRccm5UnjAe/npdF | |
GIVXkXaep7Ur9rqT4NaoSMSnDRim6Kii2lNoZ2szvvKYuxRNmvi1u60iRvQsLM10 | |
duqyG+FKdx+S5632ALWTKvdH97l3VYcRCrDYAyMYdotYavF8bcT9QKgYHoWHb18K | |
LL27A4afIXmrVXCnWXp1e2GbAoGBAOn+9xk0qK83mecSq5edXgJ1lq2NaRVmSZYc | |
5KKtCC8YYiQ0TSuIiRSpzJ3tR28wLtxO5lvqd72R8vBMPzS6CbY5RCj7tOBVW8bP | |
TuwOYUN+AAN87csZvlmPsUsXMmBNQTYycvo0Keh/ZR0RIoFmN37SyagZC1ybj90t | |
4cUCkUDNAoGBAJyAZg9oZ7jFCAUqabouEFjlC6RpxSNypHxileRwMIMaCLsZ8HBs | |
kYzwRPIif0xl0g2JEfsj0nNsL01yyIj4T0chZ+uG+hUMmnP5Vc5iHKTapSZPjloL | |
XHXlV2y6+bI68fZS89io1cVlaa5aSj9cHdPSAlm/a6ZyOPXE5lGjp5ZnAoGBAJv/ | |
T2YjGx96ZpoMcmUTlAGjuckI8Lju27lomGxzWsoQQW14M3JbBg3GiGlI2kogHz2J | |
7ufxpSkL90rdf3h8Bnl7gsX9I0A459nfifK0QNepVVeonodmfuZfy4dkzEAzgM7M | |
TKbNcUWqQ2i2FwDuz6nh28VmB5MSX+jJQS4BtiszAoGAYyqt2RrdpGLZlaZyYlsF | |
zalGIfTpWXPuj5ot63Ghwawb0xoN1qKJdYcbanvrblVhtKEsYKOkae96d1grNcf4 | |
Vbm3bMrPwHdIRf6pRS+x46mMBfuap1JoGcXESY4NwdsbpYo71PuBgykeNHaO2nq0 | |
BYcm/RyNFHuJZd+PFfOevDc= | |
-----END PRIVATE KEY----- | |
''' | |
CERT = '''-----BEGIN CERTIFICATE----- | |
MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD | |
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g | |
VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE | |
AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe | |
Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET | |
MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G | |
A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p | |
ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI | |
hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM | |
qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 | |
wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy | |
4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU | |
RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s | |
zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw | |
HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ | |
AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE | |
CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH | |
QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG | |
CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud | |
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa | |
J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y | |
LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe | |
+ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX | |
31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr | |
sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= | |
-----END CERTIFICATE----- | |
''' | |
def __init__(self, http_domain, socket_domain=None, http_port=80, socket_port=1080): | |
if socket_domain == None: | |
socket_domain = http_domain | |
self.http_domain = http_domain | |
self.socket_domain = socket_domain | |
self.http_port = http_port | |
self.socket_port = socket_port | |
self.file_date = None | |
def log(self, message): | |
print('%s' % (message)) | |
def bin_replace(self, source, orig, dest): | |
pad_num = len(orig) - len(dest) | |
if pad_num < 0: | |
self.log('Warning: Ignore replacing with shorter string: "%s" -> "%s"' % (orig, dest)) | |
return source | |
pad_dest = dest + '\x00' * pad_num | |
if len(orig) != len(pad_dest): | |
self.log('Warning: Unexpected inequality of length') | |
return source | |
return source.replace(bytes(orig), bytes(pad_dest)) | |
def opm_patch(self, source): | |
patch_dict = {self.KEY_CN: self.KEY_GLOBAL} | |
for orig_domain in self.OVERRIDE_DOMAIN_LIST: | |
http_orig = 'http://%s:%d' % (orig_domain, self.DEFAULT_HTTP_PORT) | |
socket_orig = 'socket://%s:%d' % (orig_domain, self.DEFAULT_SOCKET_PORT) | |
http_dest = 'http://%s:%d' % (self.http_domain, self.http_port) | |
socket_dest = 'socket://%s:%d' % (self.socket_domain, self.socket_port) | |
patch_dict[http_orig] = http_dest | |
patch_dict[socket_orig] = socket_dest | |
for orig in patch_dict: | |
dest = patch_dict.get(orig) | |
source = self.bin_replace(source, orig, dest) | |
return source | |
def sha1_hash_base64(self, data): | |
return base64.b64encode(hashlib.sha1(data).digest()) | |
def get_hash_entry(self, file_name, file_data): | |
mf_entry = "Name: %s\r\nSHA1-Digest: %s\r\n\r\n" % (file_name, self.sha1_hash_base64(file_data)) | |
sf_entry = "Name: %s\r\nSHA1-Digest: %s\r\n\r\n" % (file_name, self.sha1_hash_base64(mf_entry)) | |
return mf_entry, sf_entry | |
def smime_sign(self, data): | |
signer = SMIME.SMIME() | |
bio_pkey = BIO.MemoryBuffer(self.PKEY) | |
bio_cert = BIO.MemoryBuffer(self.CERT) | |
signer.load_key_bio(bio_pkey, bio_cert) | |
bio_raw = BIO.MemoryBuffer(data) | |
p7 = signer.sign(bio_raw, flags=SMIME.PKCS7_NOATTR | SMIME.PKCS7_DETACHED) | |
bio_signed = BIO.MemoryBuffer() | |
p7.write_der(bio_signed) | |
return bio_signed.read() | |
def get_cert_date(self): | |
if not self.file_date: | |
cert = X509.load_cert_string(self.CERT) | |
cert_time = cert.get_not_before().get_datetime() | |
self.file_date = (cert_time.year, cert_time.month, cert_time.day, cert_time.hour, cert_time.minute, cert_time.second) | |
return self.file_date | |
def zip_write(self, zip_file, file_name, data): | |
zip_info = ZipInfo(file_name, date_time=self.get_cert_date()) | |
zip_info.compress_type = ZIP_DEFLATED | |
zip_info.create_system = 0 | |
zip_file.writestr(zip_info, data) | |
def modify_apk(self, apk_name): | |
self.log('Modify libom.so...') | |
name_list = os.path.splitext(apk_name) | |
dest_name = name_list[0] + '-mod' + name_list[-1] | |
apk_file = ZipFile(apk_name, 'r', ZIP_DEFLATED) | |
dest_file = ZipFile(dest_name, 'w', ZIP_DEFLATED) | |
mf_file_data = 'Manifest-Version: 1.0\r\nCreated-By: 1.0 (Android SignApk)\r\n\r\n' | |
sf_file_data = '' | |
for item in apk_file.infolist(): | |
if not item.filename.startswith('META-INF'): | |
item_data = apk_file.read(item.filename) | |
if item.filename.endswith('libom.so'): | |
item_data = self.opm_patch(item_data) | |
mf_entry, sf_entry = self.get_hash_entry(item.filename, item_data) | |
mf_file_data += mf_entry | |
sf_file_data += sf_entry | |
self.zip_write(dest_file, item.filename, item_data) | |
self.log('Sign and align package...') | |
sf_file_data = "Signature-Version: 1.0\r\nCreated-By: 1.0 (Android SignApk)\r\nSHA1-Digest-Manifest: %s\r\n\r\n%s" % (self.sha1_hash_base64(mf_file_data), sf_file_data) | |
rsa_file_data = self.smime_sign(sf_file_data) | |
self.zip_write(dest_file, 'META-INF/MANIFEST.MF', mf_file_data) | |
self.zip_write(dest_file, 'META-INF/CERT.SF', sf_file_data) | |
self.zip_write(dest_file, 'META-INF/CERT.RSA', rsa_file_data) | |
dest_file.close() | |
apk_file.close() | |
self.log('Patch done: %s' % dest_name) | |
def main(): | |
print('Opera Mobile Off-Road Proxy Modifier v%s' % OperaPackageTuner.VERSION) | |
print('Copyright (c) 2013 Linus Yang <[email protected]>\n') | |
parser = OptionParser('Usage: %prog [options] [Opera Mobile apk file]') | |
parser.add_option('-d', '--httpdomain', dest="http_domain", help="specify HTTP protocol proxy domain") | |
parser.add_option('-s', '--socketdomain', dest="socket_domain", default=None, help="specify Socket protocol proxy domain (default is the same as HTTP domain)") | |
parser.add_option('-p', '--httpport', dest="http_port", type="int", default=80, help="specify HTTP protocol proxy port (default is 80)") | |
parser.add_option('-k', '--socketport', dest="socket_port", type="int", default=1080, help="specify Socket protocol proxy port (default is 1080)") | |
(options, args) = parser.parse_args() | |
if len(args) < 1: | |
parser.error('no apk input file') | |
if options.http_domain == None: | |
parser.error('no HTTP domain specified') | |
tuner = OperaPackageTuner(options.http_domain, options.socket_domain, options.http_port, options.socket_port) | |
tuner.modify_apk(args[0]) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
您的 libom-patch-py 用来修改 opera 28 和 opera mini 8.0~9.0 后还是正常使用,
但是新版 opera mini 11.0 上修改了服务器后,启动就总是说网络无法连接了,反复重试也没有用。
能请 @linusyang 有空看看 opera mini 11.0 吗,谢谢啦
这儿有多个版本的 opera 和 opera mini
ftp://ftp.opera.com/pub/opera/android/mini/
ftp://ftp.opera.com/pub/opera/android/