Swiftz Protocal
Swiftz is a project aims to create a portable replacement of Edusupplicant, base on the analysis on Edusupplicant 3.6.5.
The project is FOR STUDY ONLY. Attacking, hacking or any other illegal purposes are STRICTLY PROHIBITED.
Protocal version
The original analysis is based on Edusupplicant 3.6.5.
Tested compatible with Edusupplicant 3.7.8.
Implementations based on this analysis
- SwiftzMac (for Mac OS X >= 10.8)
- swiftz.js (for Linux/Mac, command-line, STILL IN PROGRESS)
- Swiftoid (for Android >= 4.0.3, STILL IN PROGRESS)
- OpenSupplicant (for Windows, stopped development for a long time)
Representation
The protocal in this documentation is written as procedure.
For example:
local:3848 -> server:3848 crypto3848
LOGIN:
MAC as data(16)
USERNAME as string
PASSWORD as string
IP as string
ENTRY as string
DHCP as boolean
VERSION as string
- Send to remote server port
3848from local port3848(all packets send viaUDP). - The packet is encrypted with
crypto3848. - The packet represents an
LOGINaction (see Consts / Actions section below). - The packet contains these fields (see Consts / Fields section below):
- MAC: Binary data, with fixed length 16 bytes
- USERNAME: String
- PASSWORD: String
- IP: String
- ENTRY: String
- DHCP: Boolean
- VERSION: String
Encoding
Charset
All strings in the protocal is in GB2312 charset.
Types
-
String: strings transport in
GB2312. -
Char: number in 1 byte. (i.e.
0xFFfor255) -
Integer: number in 4 bytes. (i.e.
0x00 0x00 0x00 0xFFfor255) -
Boolean:
0x00forfalse,0x01fortrue. -
Data: as it is. (i.e.
0x4A 0x21 0x39 0xC0for4A2139C0)
Packet
A naked (non-encrypted) packet is in this structure:
- 1 byte of
ACTIONrepresented what does the packet do - 1 byte represented the length of whole packet
- 16 bytes
MD5hash - 1 byte of the
keyof the first field - 1 byte of the
lengthof the first field (including thekeyand thelengthitself) - the
dataof the first field - 1 byte of the
keyof the second field (including thekeyand thelengthitself) - 1 byte of the
lengthof the second field - the
dataof the second field - ......
Note that fields' length is 2 bytes shorter than the field is.
Generate a packet
- Construct a packet as it's descripted above, of course, leaving those 16 bytes of
MD5hash be 16 bytes of0x00 - Generate a
MD5hash of the whole packet - Fill those 16 bytes with the hash you got
- Encrypt it of course :)
Parse a packet you recieved
- Decrypt it
- Check the
MD5hash (optional but recommanded for security) - You know how to do :)
Encryption/Decryption
"crypto3848"
The crypto3848 algorithm is a very simple encryption algorithm that changes the order of every byte from ABCDEFGH to HDEFCBAG.
Here's a piece of code in JavaScript for reference:
function encrypt (buffer) {
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (buffer[i] & 0x80) >> 6
| (buffer[i] & 0x40) >> 4
| (buffer[i] & 0x20) >> 2
| (buffer[i] & 0x10) << 2
| (buffer[i] & 0x08) << 2
| (buffer[i] & 0x04) << 2
| (buffer[i] & 0x02) >> 1
| (buffer[i] & 0x01) << 7
}
}
function decrypt (buffer) {
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (buffer[i] & 0x80) >> 7
| (buffer[i] & 0x40) >> 2
| (buffer[i] & 0x20) >> 2
| (buffer[i] & 0x10) >> 2
| (buffer[i] & 0x08) << 2
| (buffer[i] & 0x04) << 4
| (buffer[i] & 0x02) << 6
| (buffer[i] & 0x01) << 1
}
}
"crypto3849"
Like crypto3848 but different order that from ABCDEFGH to ECBHAFDG.
function encrypt (buffer) {
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (buffer[i] & 0x80) >> 4
| (buffer[i] & 0x40) >> 1
| (buffer[i] & 0x20) << 1
| (buffer[i] & 0x10) >> 3
| (buffer[i] & 0x08) << 4
| (buffer[i] & 0x04)
| (buffer[i] & 0x02) >> 1
| (buffer[i] & 0x01) << 4
}
}
function decrypt (buffer) {
for (var i = 0; i < buffer.length; i++) {
buffer[i] = (buffer[i] & 0x80) >> 4
| (buffer[i] & 0x40) >> 1
| (buffer[i] & 0x20) << 1
| (buffer[i] & 0x10) >> 4
| (buffer[i] & 0x08) << 4
| (buffer[i] & 0x04)
| (buffer[i] & 0x02) << 3
| (buffer[i] & 0x01) << 1
}
}
Flow
Startup
Just send a initialization packet to 1.1.1.8 without waiting for any response
First-run initialization
- Search for authorization server
- Get entries list
Get online
During you're online
- Make a breathe every 30 seconds
- Also, listen for disconnecting notification you may recieved.
Get offline
Make a offline request to end the session.
Packets
Initialize
Just send this plain text to 1.1.1.8 port 3850 via UDP.
info sock ini
- No decryption is required.
- No response would be make by the server.
Search for authorization server
Send
local:3848 -> 1.1.1.8:3850 crypto3848
SERVER:
SESSION as string
IP as string(16)
MAC as data(16)
Receive
1.1.1.8:3850 -> local:3848 crypto3848
SERVER_RET:
SERVER as data(4)
UNKNOWN0D as data(4)
-
SERVERis received as 4-byte data that each byte indicates a part of an IPv4 address, e.g.AC1001B4for172.16.1.180.
Get entries
Send
local:3848 -> server:3848 crypto3848
ENTRIES:
SESSION as string
MAC as binary(16)
Receive
server:3848 -> local:3848 crypto3848
ENTRIES_RET:
ENTRY as string
ENTRY as string
...
- It may be more than one
ENTRYfield.
Online
Send
local:3848 -> server:3848 crypto3848
LOGIN:
MAC as data(6)
USERNAME as string
PASSWORD as string
IP as string
ENTRY as string
DHCP as boolean
VERSION as string
Receive
server:3848 -> local:3848 crypto3848
LOGIN_RET:
SUCCESS as boolean
SESSION as string
UNKNOWN05 as char
UNKNOWN06 as char
MESSAGE as string
UNKNOWN95 as data(24)
UNKNOWN06 as char
BLOCK34 as data(4)
BLOCK35 as data(4)
BLOCK36 as data(4)
BLOCK37 as data(4)
BLOCK38 as data(4)
WEBSITE as string
UNKNOWN23 as char
UNKNOWN20 as char
- Only contains
SUCCESS,SESSIONandMESSAGEif not successed (i.e. wrong password) - Will contains an unknown field
UNKNOWN95with 24 bytes of0x00and an unknown fieldUNKNOWN06if it is in low-speed mode. - The
lengthof fieldSESSIONreceived is 2 bytes shorter than the field is. - The
lengthof fieldMESSAGEreceived is 2 bytes shorter than the field is.
Confirm
Send
local:random -> server:3849 crypto3849
CONFIRM:
USERNAME as string
MAC as data(6)
IP as string
ENTRY as string
Receive
server:3849 -> local:random crypto3849
CONFIRM_RET
UNKNOWN30 as data(4)
UNKNOWN31 as data(4)
UNKNOWN32 as char
- The packet is sent and receive via the same random port
- 3 bytes of
0x00appears before the md5 checksum of the packet possibly by a BUG. The length of the whole packet is calculated with those 3 bytes included.
Breathe
Send
local:3848 -> server:3848 crypto3848
BREATHE:
SESSION as string
IP as string(16)
MAC as data(16)
INDEX as integer
BLOCK2A as data(4)
BLOCK2B as data(4)
BLOCK2C as data(4)
BLOCK2D as data(4)
BLOCK2E as data(4)
BLOCK2F as data(4)
- The initial value of
INDEXis0x01000000and increases 3 after every breathe. -
INDEXshould not be increased unless a validBREATHE_RETis recevied, because you may lost packets in low-speed mode.
Receive
server:3848 -> local:3848 crypto3848
BREATHE_RET:
SUCCESS as boolean
SESSION as string
INDEX as integer
- The
lengthof fieldSESSIONreceived is 2 bytes shorter than the field is.
Offline
Send
local:3848 -> server:3848 crypto3848
LOGOUT:
SESSION as string
IP as string(16)
MAC as data(16)
INDEX as integer
BLOCK2A as data(4)
BLOCK2B as data(4)
BLOCK2C as data(4)
BLOCK2D as data(4)
BLOCK2E as data(4)
BLOCK2F as data(4)
- The initial value of
INDEXis0x01000000and increases 3 after every breathe. -
INDEXshould not be increased unless a validBREATHE_RETis recevied, because you may lost packets in low-speed mode. - It's very like a
BREATHEpacket.
Receive
server:3848 -> local:3848 crypto3848
LOGOUT_RET:
SUCCESS as boolean
SESSION as string
INDEX as integer
- The
lengthof fieldSESSIONreceived is 2 bytes shorter than the field is.
Being disconnected
Once you have bean disconnected, you'll receive a packet from the server:
server:2001 -> local:4999 crypto3848
DISCONNECT:
SESSION as string
REASON as char
- Possible values of
REASON:-
0x00- No valid breathe is made for a long time. -
0x01- You're disconnected forcibly (by administrator, maybe). -
0x02- You've reached your network traffic limit.
-
- The
lengthof fieldSESSIONreceived is 2 bytes shorter than the field is.
Consts
Actions
// login
LOGIN = 0x01
// login result
LOGIN_RET = 0x02
// breathe
BREATHE = 0x03
// breathe result
BREATHE_RET = 0x04
// logout
LOGOUT = 0x05
// logout result
LOGOUT_RET = 0x06
// get access point
ENTRIES = 0x07
// return access point
ENTRIES_RET = 0x08
// disconnect
DISCONNECT = 0x09
// confirm login
CONFIRM = 0x0A
// confirm login result
CONFIRM_RET = 0x0B
// get server
SERVER = 0X0C
// return server
SERVER_RET = 0x0D
Fields
// username
USERNAME = 0x01
// password
PASSWORD = 0x02
// whether it is success
SUCCESS = 0x03
// unknown, appears while login successfully
UNKNOWN05 = 0x05
UNKNOWN06 = 0x06
// mac address
MAC = 0x07
// session (NOTE: wrong in return packet)
SESSION = 0x08
// ip address
IP = 0x09
// access point
ENTRY = 0x0A
// message (NOTE: wrong in return packet)
MESSAGE = 0x0B
// server ip address
SERVER = 0x0C
// unknown, appears while received server ip address
UNKNOWN0D = 0x0D
// is dhcp enabled
DHCP = 0x0E
// self-services website link
WEBSITE = 0x13
// serial no
INDEX = 0x14
// version
VERSION = 0x1F
// unknown, appears while login successfully
UNKNOWN20 = 0x20
UNKNOWN23 = 0x23
// disconnect reason
REASON = 0x24
// 4 bytes blocks, send in breathe and logout
BLOCK2A = 0x2A
BLOCK2B = 0x2B
BLOCK2C = 0x2C
BLOCK2D = 0x2D
BLOCK2E = 0x2E
BLOCK2F = 0x2F
// unknown 4 bytes blocks, appears while confirmed
BLOCK30 = 0x30
BLOCK31 = 0x31
// unknown
UNKOWN32 = 0x32
// 4 bytes blocks, appears while login successfully
BLOCK34 = 0x34
BLOCK35 = 0x35
BLOCK36 = 0x36
BLOCK37 = 0x37
BLOCK38 = 0x38