Coder Social home page Coder Social logo

srlabs / blue-merle Goto Github PK

View Code? Open in Web Editor NEW
165.0 165.0 26.0 2.08 MB

The blue-merle package enhances anonymity and reduces forensic traceability of the GL-E750 Mudi 4G mobile wi-fi router

License: BSD 3-Clause "New" or "Revised" License

Makefile 8.40% Shell 38.43% Python 18.36% Lua 3.25% JavaScript 31.56%

blue-merle's People

Contributors

kantorkel avatar linuzifer avatar muelli avatar nestire avatar rieck-srlabs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blue-merle's Issues

cfun=4 failed error

Hi,

when doing a sim swap/imei change the device gives an error and I'm not sure what is means:

CFUN=4 failed. trying again.
after this error : Warning: reset took longer than expected.

Additionally I noticed with the previous blue merle version you were able to cancel the sim swap by pulling the switch down within 5 seconds. would it be able to provide that option again?

I'm using the EP-06 module.

Thanks to all that worked on the blue merle.

feature request: If it is not done, than calculate the IMEI and it's last check digit by the Luhn algorithm

Issue: A few times I experienced that GL750 Mudi could not boot and/or the celluilar interface did not start witch some randomly generated IMEIs and I was assuming that they either may be gray/blacklisted ones nad/or simply not valid by syntax?

If syntaxial validity check is not done than please consider the Luhn algorithm formula for generated IMEIs

https://sndeep.info/en/tools/checksum_calculator

Blue merle is broken with GL-750 firmware > 4

blue-merle is broken with the new interface of GL-750 firmware > 4
Here is the install log from ssh

root@GL-E750:~# opkg install blue-merle*.ipk
Installing blue-merle (1.0.0-1) to root...
You have a GL.iNet GL-E750, running firmware version 4.3.6.
blue-merle has only been tested with GL-E750 Mudi Version 3.215.
The device or firmware version you are using have not been verified to work with blue-merle.
Would you like to continue on your own risk? (y/N): y
Configuring blue-merle.
patching file /www/src/temple/settings/index.js
Hunk #1 FAILED at 6.
Hunk #2 FAILED at 79.
Hunk #3 FAILED at 108.
Hunk #4 FAILED at 170.
patch: **** Can't reopen file /www/src/temple/settings/index.js : No such file or directory
patching file /www/src/temple/settings/index.html
Hunk #1 FAILED at 6.
Hunk #2 FAILED at 64.
Hunk #3 FAILED at 84.
patch: **** Can't reopen file /www/src/temple/settings/index.html : No such file or directory
patching file /usr/bin/switchaction
Hunk #1 FAILED at 10.
Hunk #2 FAILED at 136.
Hunk #3 FAILED at 157.
patch: **** Can't reopen file /usr/bin/switchaction : No such file or directory
patching file /usr/bin/switch_queue
Hunk #1 FAILED at 81.
Hunk #2 FAILED at 98.
patch: **** Can't reopen file /usr/bin/switch_queue : No such file or directory
The /tmp/ directory does not exist. This should be fine...
The /etc/ directory does not exist. Exiting...
TEST: DOES NOT EXIST
cp: can't stat '/etc/init.d/gl_tertf': No such file or directory
Updating database.

After that, it is in the opkg installed packages, but you can't access from the web interface and the switch have no effect.

GL-E750 Version 2

The current version of GL-E750 for Europe is sold out and not available anymore. GL said a version 2 will become available in one month from now. Do you plan to support that as well?

Unlocked carrier

So beffore i buy the mudi i just wana make this clear, by changing the imei, this DOES make it so i can use any carrier like trac phone or simple mobile, ect.? Esspesaly with the feater of the imei and imsi being changed in relation to the sim correct? I just dont wana pull the trigger and still be locked to t mobile

feature request: Add an option to disable the cellular modem, if the router is connected to a Wi-Fi.

I have a MacroDroid script on my phone, that automatically detects if my phone is connected to Wi-Fi, and then puts the phone into Airplane Mode, disabling the cellular modem, but without disabling the Wi-Fi connectivity. This prevents my phone from being associated with the locations where I spend the majority of my time.

There's no use for expensive mobile data if you're connected to free Wi-Fi, and a mobile router than is stationary for an extended duration, while connected to the cellular network, puts the owner at risk of being "geographically profiled".

The router will necessarily spend the majority of its time in locations associated with the identity of the owner, such as their home, or their place of employment, allowing them to be identified.

The duration of the surveillance necessary to identity the owner of the device, via "geographic profiling", could be less than 7 days.

Even if the IMEI is changed every 7 days, this is not sufficient to prevent the owner from being identified using historical CSLI (Cell Site Location Information) data, which is retained by cellular providers for a minimum of 2 years, if not permanently.

It is conceivable that CSLI data recorded in proximity to a sensitive location, such as a place of worship, a political protest, an abortion clinic, could be used to trace a person directly to their residence.

As as example, if the owner attended a political protest, or traveled to a jurisdiction where abortion is legal, before returning home to a state where it is not, merely changing the IMSI/IMEI would not preserve their anonymity, as the association with locations associated with their identity would already have happened.

Would the Blue Merle developers be willing to implement a feature that automatically detects the presence of a Wi-Fi connection, and then disables the cellular modem?

It could also function in reverse, with the cellular modem being re-enabled after the Wi-Fi is disconnected.

This feature would significantly increase the "location privacy" provided by Blue Merle.

Thank you, please tell me what you think.

Switch to ICCID for deterministic IMEI generation

Current Status

blue-merle's deterministic IMEI generation currently likely does not work for PIN-protected SIM cards. If the PIN is not verified, get_imsi() in imei_generate.py returns an empty string (""). This is because a SIM's IMSI can only be read out after the SIM's PIN is verified (or PIN verification is disabled).
For PIN-protected SIM cards where the PIN has not been verified when blue-merle runs, the same IMEI will always be generated.

This has not yet been practically tested and validated.

Improvement Potential

Use a SIM's ICCID for deterministic IMEI generation. The ICCID ("Integrated Circuit Card Identifier") is a globally unique identifier for smart cards. It can be read out even prior to PIN verification.

Notes

To read a SIM's IMSI, use the CIMI AT command.
To read a SIM's ICCID, use the QCCID AT command.

IMEI randomisation makes IMEI looks like gibberish

Hi

IMG_2887

Blue merle is NOT generating valid IMEI. Most totally invalid and rejected by ISP.

The deal is your IMEIs fails check sum and have invalid TAC (in several countries).

I am not really good in GitHub so I cannot submit PR.

Fix for Python version

But this is simple code to make it look more valid:

import random

def luhn_check(imei):
    """
    Calculate the Luhn check digit for a given IMEI number.
    """
    sum = 0
    num_digits = len(imei)
    oddeven = num_digits & 1

    for i in range(0, num_digits):
        digit = int(imei[i])
        if not ((i & 1) ^ oddeven):
            digit *= 2
        if digit > 9:
            digit -= 9
        sum += digit

    return (10 - (sum % 10)) % 10

def imei_generate():
    # List of TACs for different manufacturers
    tacs = {
        "Samsung": "352073",
        "iPhone": "352074",
        "Sony": "352075",
        "LG": "352076",
        "Nokia": "352077",
        "Huawei": "352078",
        "Xiaomi": "352079",
        "OnePlus": "352080"
    }

    # Randomly choose a manufacturer
    manufacturer = random.choice(list(tacs.keys()))
    tac = tacs[manufacturer]

    # Generate FAC, USN
    fac = str(random.randint(10, 99)) # Final Assembly Code
    usn = str(random.randint(100000, 999999)) # Unique Serial Number

    # Combine all parts to form the IMEI without the check digit
    imei_without_check = tac + fac + usn

    # Calculate the check digit using the Luhn algorithm
    check_digit = luhn_check(imei_without_check)

    # Combine all parts to form the complete IMEI
    imei = imei_without_check + str(check_digit)

    return imei

# Example usage
print(imei_generate())

Fix for Bash version

#!/bin/bash

# Function to calculate the Luhn check digit
luhn_check() {
    local sum=0
    local num_digits=${#1}
    local oddeven=$((num_digits & 1))

    for ((i=0; i<num_digits; i++)); do
        local digit=${1:$i:1}
        if ((!((i & 1) ^ oddeven))); then
            digit=$((digit * 2))
        fi
        if ((digit > 9)); then
            digit=$((digit - 9))
        fi
        sum=$((sum + digit))
    done

    echo $((10 - (sum % 10)))
}

# Function to generate a random IMEI
generate_imei() {
    # TACs for different manufacturers
    local tacs=(
        "352073" # Samsung
        "352074" # iPhone
        "352075" # Sony
        "352076" # LG
        "352077" # Nokia
        "352078" # Huawei
        "352079" # Xiaomi
        "352080" # OnePlus
    )

    # Randomly choose a manufacturer
    local tac=${tacs[$RANDOM % ${#tacs[@]}]}

    # Generate FAC, USN
    local fac=$(printf "%02d" $((RANDOM % 90 + 10))) # Final Assembly Code
    local usn=$(printf "%06d" $((RANDOM % 900000 + 100000))) # Unique Serial Number

    # Combine all parts to form the IMEI without the check digit
    local imei_without_check="${tac}${fac}${usn}"

    # Calculate the check digit using the Luhn algorithm
    local check_digit=$(luhn_check "$imei_without_check")

    # Combine all parts to form the complete IMEI
    local imei="${imei_without_check}${check_digit}"

    echo "$imei"
}

# Example usage
generate_imei

This will make router mimic to one of this manufacturers:
Samsung, iPhone, Sony, LG, Nokia, Huawei, Xiaomi, OnePlus

This ensures that ISP will not reject IMEI, nor shape speed because of “not mobile use”.

You can extend list of manufacturers by TACs from this database

Limit frequency bands to those supported by the spoofed IMEI's phone model

As discussed on page 9 of the Documentation, a fingerprinting risk emerges when blue-merle generates an IMEI with a TAC of a phone model not supporting LTE frequency bands the Mudi router supports, namely B1, B3, B5, B7, B8, B20, B28, B32, B38, B40 and B41. When a blue merle Mudi uses a frequency band that does not match the TAC’s specification, an observer can deduce that the IMEI is spoofed.

As limiting the frequency bands might impact service quality and availability, the feature should be optional.

The command to limit the baseband to specific bands is
AT+QCFG=$band

See AT Commands Manual (alternative public link) for details.

Add more TACs to prevent be identified or overlapped

Hi!

You can use this database:
https://github.com/VTSTech/IMEIDB/blob/master/imeidb.csv

You should definitely add some. Because it is 6.2% like TAC will overlap.

This can cause issues as purely theoretically someone who spying can use this information to try to identify user of your application.

For example like this:

imei_prefix = ["35674108", "35290611", "35397710", "35323210", "35384110",
"35982748", "35672011", "35759049", "35266891", "35407115",
"35538025", "35480910", "35324590", "35901183", "35139729",
"35479164", "35326407", "35920507", "35915307", "35656908",
"35537508", "35381208", "35655408", "35669908", "35004331",
"35012763", "35013263", "35013596", "35014386", "35014545",
"35017890", "35022562", "35048105", "35048156", "35049626",
"35053352", "35063329", "35064217", "35068083", "35075982",
"35076156", "35076825", "35079439"]

Double check my TACs if you want to use my example!

Generate different per-Mudi IMEIs in deterministic mode

Possible enhancement, subject to additional discussion:

Currently, deterministic IMEI generation will generate the same IMEI regardless of underlying Mudi device. Since the generation algorithm is open source and the only seed input is the IMSI, network operators can identify deterministically generated IMEIs.

It could be useful to provide a new deterministic IMEI generation mode that generates different deterministic IMEIs per Mudi. This can be done by e.g. writing a random seed to disk at blue-merle installation time and using this seed in addition to the SIM's unique identifier (IMSI, ICCID) to derive the new IMEI. The random seed would not survive a factory reset, so it might be better to use another unique hardware identifier.

FYI: `blue-merle` should be compatible with GL-E750 Mudi version 3.217

blue-merle should be compatible with GL-E750 Mudi version 3.217 from 2023-05-08 02:41:42 (UTC+00:00) / sha256: 5dab717c1b418b5da4613bce6d82949d9a3defadc53e660fef08d1febe6e93d3 (from here).

This is how I performed the update and arrived at this conclusion:

  1. Initial condition: GL-E750 Mudi version 3.215 with blue-merle_1.0.0-1_mips_24kc.ipk installed
  2. Uninstalled blue-merle_1.0.0-1_mips_24kc.ipk
  3. Updated GL-E750 Mudi to version 3.217
  4. The following files are the only ones that already exist without blue-merle, and are modified by it. diff'd those after step 2 against the versions of them after step 1 - there were zero differences:
    1. /www/src/temple/settings/index.html
    2. /www/src/temple/settings/index.js
    3. /usr/bin/switchaction
    4. /usr/bin/switch_queue
  5. Installed blue-merle_1.0.0-1_mips_24kc.ipk
  6. Did a superficial check of the admin panel to see if the change introduced by blue-merle looks as it should
  7. Executed the pysical toggle button variant of generating and assigning a different IMEI, which still worked
  8. Used the Mudi for a couple of days without issues

Please note, however, that this does not prove that everything works as it should. Use this information at your own risk.

Add toggles to enable/disable options

I created code draft to implement the MAC logging and MAC address wiping toggles in Blue Merle:

  1. views/blue-merle.htm
<label>
  Logging
  <input type="checkbox" id="cbLogging">
</label>

<label>
  MAC Wiping
  <input type="checkbox" id="cbMacWiping">
</label>
  1. resources/view/blue-merle.js
// Configs
config.add('logging', true);
config.add('mac_wiping', false);

// UI init
function initUI() {
  document.getElementById('cbLogging').checked = config.get('logging');
  document.getElementById('cbMacWiping').checked = config.get('mac_wiping');

  document.getElementById('cbLogging').onchange = updateConfig;  
  document.getElementById('cbMacWiping').onchange = updateConfig;
}

// Update config
function updateConfig() {
  config.set('logging', this.checked);
  config.set('mac_wiping', this.checked);
  saveConfig();
}

// Save handler
function saveConfig() {

  // Validation

  // Call scripts

  // Persist config

  luci.http.submit();

}

// Init UI
initUI();

// Save on unload  
window.onbeforeunload = saveConfig;
  1. files/lib/blue-merle/functions.sh
toggle_mac_wiping() {

  // Wipe/restore MACs logic

}

This will:

  1. Add disable/enable logs toggle.

To stop writing logs at all you need to:

/etc/init.d/gl_clients disable
/etc/init.d/gl_clients stop
  1. Add toggle to disable/enable https://github.com/srlabs/blue-merle#mac-address-log-wiping
  2. Make sure that only one toggle can be enabled simultaneously (1 or 2)

But there is one more way - Lua. And it looks more correct than first one:

-- Define toggles
local log_toggle = nil
local wipe_toggle = nil

-- Function to initialize toggles
function init_toggles()

  -- Log toggle
  log_toggle = SimpleForm("log_toggle")
  log_toggle.title = "Log Toggle"
  log_toggle.reset = false

  log_toggle:append(TextValue("status", ""))
  log_toggle:append(Checkbox("enabled", "Enable Logging"))

  -- Wipe toggle  
  wipe_toggle = SimpleForm("wipe_toggle")  
  wipe_toggle.title = "Wipe Toggle"
  wipe_toggle.reset = false

  wipe_toggle:append(TextValue("status", ""))
  wipe_toggle:append(Checkbox("enabled", "Enable Wiping"))

end

-- Function to handle toggle changes
function toggle_change(section)

  if section == log_toggle then

    -- Disable wiping if logs enabled
    if log_toggle.enabled.value then
      wipe_toggle.enabled.disabled = true
    else
      wipe_toggle.enabled.disabled = false      
    end

    -- Update services based on log toggle
    if log_toggle.enabled.value then
      luci.sys.call("/etc/init.d/gl_clients enable")
      luci.sys.call("/etc/init.d/gl_clients start")      
    else
      luci.sys.call("/etc/init.d/gl_clients disable")
      luci.sys.call("/etc/init.d/gl_clients stop")
    end

  elseif section == wipe_toggle then

    -- Disable logs if wiping enabled  
    if wipe_toggle.enabled.value then
      log_toggle.enabled.disabled = true
    else
      log_toggle.enabled.disabled = false
    end

    -- Update services based on wipe toggle
    if wipe_toggle.enabled.value then
      -- Add code to enable wiping
    else
      -- Add code to disable wiping      
    end

  end

end

-- Initialize toggles
init_toggles()

-- Add toggles to page
entry({"admin", "services", "bluemerle"}, cbi("Blue Merle"), _("Blue Merle")).dependent = false
entry({"admin", "services", "bluemerle"}, firstchild()).dependent = false
entry({"admin", "services", "bluemerle"}, log_toggle, _("Log Toggle")).dependent = false
entry({"admin", "services", "bluemerle"}, wipe_toggle, _("Wipe Toggle")).dependent = false

-- Handle toggle changes
log_toggle.apply = function() toggle_change(log_toggle) end  
wipe_toggle.apply = function() toggle_change(wipe_toggle) end

———
Additional features:
———
This will (if toggle enabled):

  1. This will generate router passwords like:
    Original Password: MyPass123
    May 1st Password: MyPass123-01
    May 2nd Password: MyPass123-02
  2. Block all ports except following:
    Port 80 - HTTP (web browsing)
    Port 443 - HTTPS (secure web browsing)
    Port 53 - DNS (domain name resolution)
    Port 123 - NTP (network time synchronization)

Luci GUI (firewall.xml)

<form action="/cgi-bin/luci/admin/network/firewall" method="post">

<fieldset id="password">
  <input type="checkbox" name="password_dynamic">
  <label>Enable Dynamic Password</label>

  <select name="password_mode">
   <option value="reboot">Change on Reboot</option>
  </select>
</fieldset>

<fieldset id="max_security">  
  <input type="checkbox" name="max_security_enabled">
  <label>Enable Max Security</label>
</fieldset>

<button type="submit">Save</button>

</form>

uci.lua

password = {}
password.dynamic = luci.http.formvalue("password_dynamic")

max_security = {}
max_security.enabled = luci.http.formvalue("max_security_enabled")

uci:set("wireless", "radio0", "password", "")
uci:set("firewall", "max_security", "enabled", max_security.enabled)  
uci:commit("wireless")
uci:commit("firewall")

password.cron

PASSWORD=`uci get wireless.radio0.password`
DAY=`date +%d`
NEW_PASSWORD="$PASSWORD-$DAY"

uci set wireless.radio0.password="$NEW_PASSWORD"   
uci commit
/etc/init.d/firewall restart

firewall.lua

enabled = uci.get("firewall", "max_security", "enabled")

if enabled == "1" then

  iptables.filter.append("INPUT", "-p tcp --dport 80 -j ACCEPT")
  iptables.filter.append("INPUT", "-p tcp --dport 443 -j ACCEPT")
  iptables.filter.append("INPUT", "-p udp --dport 53 -j ACCEPT")
  iptables.filter.append("INPUT", "-p tcp --dport 123 -j ACCEPT")

  iptables.filter.append("OUTPUT", "-p tcp --sport 80 -j ACCEPT") 
  iptables.filter.append("OUTPUT", "-p tcp --sport 443 -j ACCEPT")
  iptables.filter.append("OUTPUT", "-p udp --sport 53 -j ACCEPT")
  iptables.filter.append("OUTPUT", "-p tcp --sport 123 -j ACCEPT")

  iptables.filter.append("INPUT", "-j DROP")
  iptables.filter.append("OUTPUT", "-j DROP")

else

  # normal rules

end

iptables.apply()

Remove Python dependency

We currently use a Python script to generate and set new IMEIs.

Our dependency on Python bloats the package and increases execution times. There is also a rare issue where the Python script fails to set an updated IMEI. Our current understanding is that this is due to our exclusive usage of the serial interface to the modem, which might break when the modem is used by other parts of the system at the same time.

In the future, we can migrate to Lua, which is natively supported by OpenWRT. Most of the actual work is already completed in luhn.lua.

WAN MAC address is static

I think there is a privacy risk in the GL-E750 implementation that blue-merle could, but currently does not take into account:

The WAN MAC address, which is used for WiFi station / "repeater" mode1, is static. This can potentially cause a tracking risk:

  • As the device hops across WiFi access points, an attacker could profile the user's location/movement
  • An attacker with access to the admin panel or physical access to the device could take note of the factory default WAN MAC address, potentially linking the abovementioned profile to the user's identity (even after the user has changed the WAN MAC address, see below)

The "MAC clone" feature already allows for changing the WAN MAC address (there's even a handy one-shot randomization feature), although the factory default WAN MAC address cannot be changed this way. If the user wishes to protect herself from the described tracking risk2, then, currently, the WAN MAC address must be changed manually, every time.

I'd therefore like to propose the following:

  • By default, automatically randomize the WAN MAC address on every boot, very similar to how the BSSIDs (and accompanying MAC addresses) are already randomized
  • It should be easy to switch the WAN MAC address randomization off if needed (e.g. to avoid having to repeatedly fend off a hotel's captive portal)

Provided you agree to the above assessment, I could pack up my changes into a pull request that implements this.

Unfortunately, it seems nontrivial to place an on/off switch for this in GL.iNet's admin panel properly: the new field would have to go through the API binary at /www/api, for which the source code does not seem to be available. You seem to have had a bit of luck with adding the SIM switch choice :)

Either way, I'd at least provide a simple CLI command (e.g. uci set network.@interface[4].randomize_macaddr=0)

Very neat project!

Footnotes

  1. It might be used for the LAN port in WAN mode too, but I havent tested this yet, to be exact

  2. I'd imagine users of blue-merle use WiFi or LAN less often than the cellular network to connect the Mudi to the internet (as I perceive IMEI randomization being the highlight), but it has its uses

[Feature request] Add flush DNS

Hello!

As written in this article (link clickable) DNS cashing can expose you browsing history:

“Your DNS cache is effectively a list of visited websites, much like your browser history. However, the DNS cache is usually managed by your device’s operating system and is therefore outside the scope of any single browser — and the safeguards browsers usually implement.

One such safeguard is incognito (or private) mode. While incognito mode doesn’t deliver on the vast majority of its advertised privacy promises, it does prevent your browser from storing your browsing history. Yet, a very similar list of websites is present inside the DNS cache and outlives your incognito session. A compromised device could therefore expose your browsing history via the DNS cache, even if you visited those websites in incognito mode.”

Suggestion: Additional Seed in deterministic mode

In the spec it says:

3.1.2.1 Link between IMEI and IMSI in deterministic mode
In deterministic mode, the IMEI is statically derived from the IMSI. By checking the relationship
between IMSI and IMEI, an observer can identify that a blue merle IMEI changer is in use.

I think by adding a secret / salt, that the user provides additionally, e.g. by a parameter --deterministic-salt, which is also used to seed the random generator ("salt + imsi"), the relationship between IMSI and IMEI could not be traced back anymore, right? Not sure if that would practically improve it (since there is already a random mode), though. Just as a suggestion / feedback.

Stale luci UI after installation or removal through CLI

For some reason I don't yet fully understand, the LuCI UI seems to cache the available modules causing the UI to display the non-existent blue-merle app after removal of the package

image

likewise, installing blue-merle through the CLI causes a stale UI that does not yet show blue-merle in the menu.

I've tried rm -f /tmp/luci-indexcache* ; rm -rf /tmp/luci-modulecache ; luc i-reload ; /etc/init.d/nginx restart but to no avail.

E750 v2

So how mamy people have gotten the newst mudi to work with merle ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.