Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
executable file 163 lines (136 sloc) 5.93 KB
#!/usr/bin/env python3
import sys
import datetime
import socket
import time
# it's like encodesms.py but CDMA! :D
# https://www.3gpp2.org/Public_html/Specs/X.S0048-0_v1.0_071111.pdf
# ("The payload of the SIP MESSAGE shall contain a binary encoded SMS transport layer SMS Point-to-Point message [C.S0015]")
# https://www.3gpp2.org/Public_html/Specs/C.S0015-A_v2.0_051006.pdf
# https://www.cnblogs.com/leolcao/archive/2013/02/06/2904591.html
# https://cs.android.com/android/platform/superproject/+/master:frameworks/base/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java;l=203;drc=master
def rev(a):
return "".join(reversed(a))
def bitascii(instr):
return "".join(bin(i | 0x100)[3:] for i in instr)
def bitstrtobytes(instr):
return bytes([int(instr[i:i + 8], 2) for i in range(0, len(instr), 8)])
# actually ASCII-7, lol, because I'm too lazy to build a proper gsm encoder
# see encodesms.py
def gsmencode(s):
s = s.encode("ascii")
# yes I know you can do this more efficiently
# convert to 7-digit binary, put into a string of (little endian) bits
b = "".join([rev(bin(i | 0b10000000)[3:]) for i in s])
# pad to 8 bits
t = (len(b) + 7) & ~7
b = b + "0" * (t - len(b))
return bytes([int(rev(b[i:i + 8]), 2) for i in range(0, len(b), 8)])
def bitpad8(frombitstr):
frombitstr += "0" * (((len(frombitstr) + 0x7) & ~0x7) - len(frombitstr))
return frombitstr
def datebcd(a):
return ((a // 10) << 4) | (a % 10)
def encode_bearer(text, timestamp, message_id=0):
text_encoded = gsmencode(text)
# GSM 7-bit encoding. (ok I'm lying I don't have a GSM 7-bit encoder, so I should use ASCII-7 (00010), but who cares)
text_encoded_bitstr = "01001" + bin(len(text) |
0x100)[3:] + bitascii(text_encoded)
user_data_bytes = bitstrtobytes(bitpad8(text_encoded_bitstr))
ts = timestamp.astimezone(datetime.timezone.utc)
bearer_data = bytes([
0x00, # SUBPARAMETER_ID = Message Identifier
0x03, # SUBPARAMETER_LEN
0b00010000 |
(message_id >>
12), # MESSAGE_TYPE = 0001 (Deliver), MESSAGE_ID = message id
(message_id >> 4) & 0xff,
(message_id & 0xf) |
0b00000000, # HEADER_IND = 0 (user data has no header)
0x01, # SUBPARAMETER_ID = User Data
len(user_data_bytes), # SUBPARAMETER_LEN
]) + user_data_bytes + bytes([
0x03, # SUBPARAMETER_ID = Message Center Time Stamp
0x06, # SUBPARAMETER_LEN = 6
datebcd(ts.year % 100),
datebcd(ts.month),
datebcd(ts.day),
datebcd(ts.hour),
datebcd(ts.minute),
datebcd(ts.second),
])
return bearer_data
def encodesms(fromnum, text, timestamp):
bearer_data = encode_bearer(text, timestamp)
fromnumbytes = fromnum.encode("ascii")
# DIGIT_MODE = 1 (8-bit ASCII), NUMBER_MODE = 0 (phone number), NUMBER_TYPE = 001 (international number), NUMBER_PLAN = 0001 (telephone)
frombitstr = "100010001" + bin(len(fromnumbytes) |
0x100)[3:] + bitascii(fromnumbytes)
frombytes = bitstrtobytes(bitpad8(frombitstr))
outbytes = bytes([
0x00, # SMS_MSG_TYPE = Point-to-Point
0x00, # PARAMETER_ID = Teleservice identifier
0x02, # PARAMETER_LEN = 16 bits (2 bytes)
# https://cs.android.com/android/platform/superproject/+/master:frameworks/base/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java;l=35;drc=master
0x10, # Wireless Messaging Teleservice
0x02,
# we skip service category since we're not doing an emergency broadcast
0x02, # PARAMETER_ID = Originating Address
len(frombytes), # PARAMETER_LEN
]) + frombytes + bytes([
# we skip Bearer Acknowledge since we don't need an ACK
0x08, # PARAMETER_ID = Bearer Data
len(bearer_data), # PARAMETER_LEN
]) + bearer_data
return outbytes
def encode_emergency(text, timestamp):
message_id = int(time.time()) & 0xffff
bearer_data = encode_bearer(text, timestamp, message_id)
outbytes = bytes([
0x01, # SMS_MSG_TYPE = Broadcast
0x01, # PARAMETER_ID = Service category
0x02, # PARAMETER_LEN
# https://cs.android.com/android/platform/superproject/+/master:frameworks/base/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java;l=58;drc=master
0x10, # Extreme threat
0x01,
0x08, # PARAMETER_ID = Bearer Data
len(bearer_data), # PARAMETER_LEN
]) + bearer_data
return outbytes
def sendsip(smsdata, sipaddr):
headers = "MESSAGE " + sipaddr + " SIP/2.0\r\n" + \
"Via: SIP/2.0/UDP [::1];branch=0\r\n" + \
"From: <sip:+15555555555@localhost>\r\n" + \
"To: <" + sipaddr + ">\r\n" + \
"Call-Id: " + str(time.time_ns()) + "\r\n" + \
"CSeq: 1 MESSAGE\r\n" + \
"Content-Length: " + str(len(smsdata)) + "\r\n" + \
"Content-Type: application/vnd.3gpp2.sms\r\n\r\n"
payload = headers.encode("utf-8") + smsdata
with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s:
s.connect(("::1", 5060))
s.send(payload)
def main():
if len(sys.argv) != 4:
print("usage: encodesms_cdma <from|emerg> <to|emu> <text>")
print("e.g. encodesms_cdma 15556667777 13334445555 \"hello\"")
print("send from emerg for a cell broadcast alert")
print("send to emu for a Java array dump")
return
fromaddr = sys.argv[1]
tonum = sys.argv[2]
text = sys.argv[3]
if fromaddr == "emerg":
smsdata = encode_emergency(text, datetime.datetime.now())
else:
smsdata = encodesms(fromaddr, text, datetime.datetime.now())
if tonum == "emu":
header = bytes([
0x01, # RECEIVED_READ
len(smsdata)
])
print(",".join("(byte)" + hex(a) for a in (header + smsdata)))
return
sendsip(smsdata, "sip:+" + tonum + "@localhost")
if __name__ == "__main__":
main()