Coder Social home page Coder Social logo

android's Introduction

Android

Android 加固应用Hook方式-Frida

Java.perform(function () {
    var application = Java.use('android.app.Application');

    application.attach.overload('android.content.Context').implementation = function(context){

        var result = this.attach(context);
        var classloader = context.getClassLoader();
        Java.classFactory.loader = classloader;

        var yeyoulogin = Java.classFactory.use('com.zcm.主窗口');
        console.log("yeyoulogin:"+ yeyoulogin);


        yeyoulogin.按钮_用户登录$被单击.implementation = function(arg){
            console.log("retval:"+ this.返回值);
        }

    }
});

列出加载的类

Java.enumerateLoadedClasses(
  {
  "onMatch": function(className){ 
        console.log(className) 
    },
  "onComplete":function(){}
  }
)

Hook 动态加载类

获取构造函数的参数

Java.perform(function(){
        //创建一个DexClassLoader的wapper
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        //hook 它的构造函数$init,我们将它的四个参数打印出来看看。
        dexclassLoader.$init.implementation = function(dexPath,optimizedDirectory,librarySearchPath,parent){
            console.log("dexPath:"+dexPath);
            console.log("optimizedDirectory:"+optimizedDirectory);
            console.log("librarySearchPath:"+librarySearchPath);
            console.log("parent:"+parent);
            //不破换它原本的逻辑,我们调用它原本的构造函数。
          this.$init(dexPath,optimizedDirectory,librarySearchPath,parent);
        }
        console.log("down!");

});

获取动态加载的类

Java.perform(function(){
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");

        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
           //定义一个String变量,指定我们需要的类
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            //直接调用第二个重载方法,跟原本的逻辑相同。
            var result = this.loadClass(name,false);
            //如果loadClass的name参数和我们想要hook的类名相同
            if(name === hookname){
                //则拿到它的值
                hookClass = result;
                //打印hookClass变量的值
                console.log(hookClass);
                send(hookClass);
                return result;
            }
            return result;
        }
});

通过Java.cast处理泛型方法(JAVA中Class<?>表示泛型),在调用动态加载方法

Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var constructorclass = Java.use("java.lang.reflect.Constructor");
        var objectclass= Java.use("java.lang.Object");
        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                console.log("------------------------------CAST--------------------------------")
                //类型转换
                var hookClassCast = Java.cast(hookClass,ClassUse);
                //调用getMethods()获取类下的所有方法
                var methods = hookClassCast.getMethods();
                console.log(methods);
                console.log("-----------------------------NOT CAST-----------------------------")
                //未进行类型转换,看看能否调用getMethods()方法
                var methodtest = hookClass.getMethods();
                console.log(methodtest);
                console.log("---------------------OVER------------------------")
                return result;

            }
            return result;
        }


});

利用getDeclaredConstructor()获取具有指定参数列表构造函数的Constructor 并实例化

Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var objectclass= Java.use("java.lang.Object");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
        var Integerclass = Java.use("java.lang.Integer");
        //实例化MainActivity对象
        var mainAc = orininclass.$new();


        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                var hookClassCast = Java.cast(hookClass,ClassUse);
                console.log("-----------------------------BEGIN-------------------------------------");
                //获取构造器
                var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
                var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
                console.log("Constructor:"+Constructor);
                console.log("orinin:"+mainAc);
                //实例化,newInstance的参数也是Ljava.lang.Object;
                var instance = Constructor.newInstance([mainAc]);
                console.log("patchAc:"+instance);
                send(instance);
console.log("--------------------------------------------------------------------");
                return result;

            }
            return result;
        }
});

利用getDeclaredMethods(),获取本类中的所有方法

Java.perform(function(){
        var hookClass = undefined;
        var ClassUse = Java.use("java.lang.Class");
        var objectclass= Java.use("java.lang.Object");
        var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
        var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
        var Integerclass = Java.use("java.lang.Integer");
        //实例化MainActivity对象
        var mainAc = orininclass.$new();


        dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
            var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
            var result = this.loadClass(name,false);

            if(name == hookname){
                var hookClass = result;
                var hookClassCast = Java.cast(hookClass,ClassUse);
                console.log("-----------------------------BEGIN-------------------------------------");
                //获取构造器
                var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
                var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
                console.log("Constructor:"+Constructor);
                console.log("orinin:"+mainAc);
                //实例化,newInstance的参数也是Ljava.lang.Object;
                var instance = Constructor.newInstance([mainAc]);
                console.log("MainActivityPatchInstance:"+instance);
                send(instance);
                console.log("----------------------------Methods---------------------------------");
                var func = hookClassCast.getDeclaredMethods();
                console.log(func);
                console.log("--------------------------Need Method---------------------------------");
                console.log(func[0]);
                var f = func[0];
                console.log("---------------------------- OVER---------------------------------");
                return result;

            }
            return result;
        }
});

调用Method.invoke()去执行方法(invoke方法的第一个参数是执行这个方法的对象实例,第二个参数是带入的实际值数组,返回值是Object,也既是该方法执行后的返回值)

f.invoke(instance,Array);

read-std-string

/*
 * Note: Only compatible with libc++, though libstdc++'s std::string is a lot simpler.
 */

function readStdString (str) {
  const isTiny = (str.readU8() & 1) === 0;
  if (isTiny) {
    return str.add(1).readUtf8String();
  }

  return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}

whereisnative

Java.perform(function() {

    var SystemDef = Java.use('java.lang.System');

    var RuntimeDef = Java.use('java.lang.Runtime');

    var exceptionClass = Java.use('java.lang.Exception');

    var SystemLoad_1 = SystemDef.load.overload('java.lang.String');

    var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String');

    var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String');

    var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String');

    var ThreadDef = Java.use('java.lang.Thread');

    var ThreadObj = ThreadDef.$new();

    SystemLoad_1.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        return SystemLoad_1.call(this, library);
    }

    SystemLoad_2.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        SystemLoad_2.call(this, library);
        return;
    }

    RuntimeLoad_1.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        RuntimeLoad_1.call(this, library);
        return;
    }

    RuntimeLoad_2.implementation = function(library) {
        send("Loading dynamic library => " + library);
        stackTrace();
        RuntimeLoad_2.call(this, library);
        return;
    }

    function stackTrace() {
        var stack = ThreadObj.currentThread().getStackTrace();
        for (var i = 0; i < stack.length; i++) {
            send(i + " => " + stack[i].toString());
        }
        send("--------------------------------------------------------------------------");
    }

})

Non-ASCII

 int ֏(int x) {
        return x + 100;
    }

甚至有一些不可视, 所以可以先编码打印出来, 再用编码后的字符串去 hook.<\br>

Java.perform(
        function x() {

            var targetClass = "com.example.hooktest.MainActivity";

            var hookCls = Java.use(targetClass);
            var methods = hookCls.class.getDeclaredMethods();

            for (var i in methods) {
                console.log(methods[i].toString());
                console.log(encodeURIComponent(methods[i].toString().replace(/^.*?\.([^\s\.\(\)]+)\(.*?$/, "$1")));
            }

            hookCls[decodeURIComponent("%D6%8F")]
                .implementation = function (x) {
                    console.log("original call: fun(" + x + ")");
                    var result = this[decodeURIComponent("%D6%8F")](900);
                    return result;
                }
        }
    )

Hook 数据库

var SQLiteDatabase = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
    var Set = Java.use("java.util.Set");
    var ContentValues = Java.use("android.content.ContentValues");
    SQLiteDatabase.insert.implementation = function (arg1,arg2,arg3) {

        this.insert.call(this, arg1, arg2, arg3);
        console.log("[insert] -> arg1:" + arg1 + "\t arg2:" + arg2);
        var values = Java.cast(arg3, ContentValues);
        var sets = Java.cast(values.keySet(), Set);
        
        var arr = sets.toArray().toString().split(",");
        for (var i = 0; i < arr.length; i++){
            console.log("[insert] -> key:" + arr[i] + "\t value:" + values.get(arr[i]));
        }
    };
    

Hook JNI Native GetStringUTFChars

function hook_native_GetStringUTFChars() {
    var env = Java.vm.getEnv();
    var handlePointer = Memory.readPointer(env.handle);
    console.log("env handle: " + handlePointer);
    var GetStringUTFCharsPtr = Memory.readPointer(handlePointer.add(0x2A4));
    console.log("GetStringUTFCharsPtr addr: " + GetStringUTFCharsPtr);
    Interceptor.attach(GetStringUTFCharsPtr, {
        onEnter: function (args) {
            var str = "";
            Java.perform(function () {
                str = Java.cast(args[1], Java.use('java.lang.String'));
            });
            console.log("GetStringUTFChars: " + str);

        }
    });
}

主动弹窗

Java.perform(function() {
    var System = Java.use('java.lang.System');
    var ActivityThread = Java.use("android.app.ActivityThread");
    var AlertDialogBuilder = Java.use("android.app.AlertDialog$Builder");
    var DialogInterfaceOnClickListener = Java.use('android.content.DialogInterface$OnClickListener');

    Java.use("android.app.Activity").onCreate.overload("android.os.Bundle").implementation = function(savedInstanceState) {
        var currentActivity = this;

        // Get Main Activity
        var application = ActivityThread.currentApplication();
        var launcherIntent = application.getPackageManager().getLaunchIntentForPackage(application.getPackageName());
        var launchActivityInfo = launcherIntent.resolveActivityInfo(application.getPackageManager(), 0);

        // Alert Will Only Execute On Main Package Activity Creation
        console.log(this.getComponentName().getClassName())
        /**
         * non protective application
         * if (launchActivityInfo === this.getComponentName().getClassName()) {
         *     ...
         * }
         */

        if (this.getComponentName().getClassName() === "com.xxx") {

            var alert = AlertDialogBuilder.$new(this);
            var jString = Java.use('java.lang.String');
            var CharSequence = Java.use('java.lang.CharSequence');
            var charSequence = Java.cast(jString.$new("What you want to do now?"), CharSequence);
            var charSequence1 = Java.cast(jString.$new("Dismiss"), CharSequence);
            var charSequence2 = Java.cast(jString.$new("Force Close!"), CharSequence);
            alert.setMessage(charSequence);

            alert.setPositiveButton(charSequence1, Java.registerClass({
                name: 'il.co.realgame.OnClickListenerPositive',
                implements: [DialogInterfaceOnClickListener],
                methods: {
                    getName: function() {
                        return 'OnClickListenerPositive';
                    },
                    onClick: function(dialog, which) {
                        // Dismiss
                        dialog.dismiss();
                    }
                }
            }).$new());

            alert.setNegativeButton(charSequence2, Java.registerClass({
                name: 'il.co.realgame.OnClickListenerNegative',
                implements: [DialogInterfaceOnClickListener],
                methods: {
                    getName: function() {
                        return 'OnClickListenerNegative';
                    },
                    onClick: function(dialog, which) {
                        // Close Application
                        //currentActivity.finish();
                        System.exit(0);
                    }
                }
            }).$new());

            // Create Alert
            alert.create().show();
        }
        return this.onCreate.overload("android.os.Bundle").call(this, savedInstanceState);
    };
});

Hook prettyMethod

const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
    constructor() {
        this.handle = Memory.alloc(STD_STRING_SIZE);
    }

    dispose() {
        const [data, isTiny] = this._getData();
        if (!isTiny) {
            Java.api.$delete(data);
        }
    }

    disposeToString() {
        const result = this.toString();
        this.dispose();
        return result;
    }

    toString() {
        const [data] = this._getData();
        return data.readUtf8String();
    }

    _getData() {
        const str = this.handle;
        const isTiny = (str.readU8() & 1) === 0;
        const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
        return [data, isTiny];
    }
}

function prettyMethod(method_id, withSignature) {
    const result = new StdString();
    Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
    return result.disposeToString();
}

bypass frida detection

result = pthread_create(v102, 0LL, sub_F74, 0LL);

function hook_pthread_create(){
    var pt_create_func = Module.findExportByName(null,'pthread_create');
    var detect_frida_loop_addr = null;
    console.log('pt_create_func:',pt_create_func);
 
   Interceptor.attach(pt_create_func,{
       onEnter:function(){
           if(detect_frida_loop_addr == null)
           {
                var base_addr = Module.getBaseAddress('libnative-lib.so');
                if(base_addr != null){
                    detect_frida_loop_addr = base_addr.add(0x0000000000000F74)
                    console.log('this.context.x2: ', detect_frida_loop_addr , this.context.x2);
                    if(this.context.x2.compare(detect_frida_loop_addr) == 0) {
                        hook_anti_frida_replace(this.context.x2);
                    }
                }
 
           }
 
       },
       onLeave : function(retval){
           // console.log('retval',retval);
       }
   })
}
function hook_anti_frida_replace(addr){
    console.log('replace anti_addr :',addr);
    Interceptor.replace(addr,new NativeCallback(function(a1){
        console.log('replace success');
        return;
    },'pointer',[]));
 
}

setImmediate(hook_pthread_create());

find svc

let target_code_hex;
let call_number_openat;
let call_number_faccessat;
let arch = Process.arch;
let call_number_sendto;
let call_number_sendmsg;
let call_number_kill;
if ("arm" === arch){
    target_code_hex = "00 00 00 EF";
    call_number_openat = 322;
    call_number_faccessat = 334;
    call_number_sendto = 206;
    call_number_sendmsg = 221;
    call_number_kill = 129;
}else if("arm64" === arch){
    target_code_hex = "01 00 00 D4";
    call_number_openat = 56;
    call_number_faccessat = 48;
    call_number_sendto = 206;
    call_number_sendmsg = 221;
    call_number_kill = 129;

}else {
    console.log("arch not support!")
}

if (arch){
    console.log("\nthe_arch = " + arch);
    // 直接Process.enumerateModules(),可能会因为某些地址不可读造成非法访问
    Process.enumerateRanges('r--').forEach(function (range) {
        if(!range.file || !range.file.path){
            return;
        }
        let path = range.file.path;
        if ((!path.startsWith("/data/app/")) || (!path.endsWith(".so"))){
            return;
        }
        let baseAddress = Module.getBaseAddress(path);
        console.log("\npath = " + path + " , baseAddress = " + baseAddress + " , rangeAddress = " + range.base + " , size = " + range.size);

        Memory.scan(range.base, range.size, target_code_hex, {
            onMatch: function (match){
                let code_address = match;
                let code_address_str = code_address.toString();
                if (code_address_str.endsWith("0") || code_address_str.endsWith("4") || code_address_str.endsWith("8") || code_address_str.endsWith("c")){
                    console.log("--------------------------");
                    let call_number = 0;
                    if ("arm" === arch){
                        // call_number = (code_address.sub(0x4).readS16() - 28672);  // 0x7000
                        call_number = (code_address.sub(0x4).readS32()) & 0xFFF;
                    }else if("arm64" === arch){
                        call_number = (code_address.sub(0x4).readS32() >> 5) & 0xFFFF;
                    }else {
                        console.log("the arch get call_number not support!")
                    }
                    console.log("find svc : address = " + code_address + " , call_number = " + call_number + " , offset = " + code_address.sub(baseAddress));

                    // hook svc __NR_openat
                    if (call_number_openat === call_number){
                        let target_hook_addr = code_address;
                        let target_hook_addr_offset = target_hook_addr.sub(baseAddress);
                        console.log("find svc openat , start inlinehook by frida!")
                        Interceptor.attach(target_hook_addr, {
                            onEnter: function (args){
                                console.log("\nonEnter_" + target_hook_addr_offset + " , __NR_openat , args[1] = " + args[1].readCString());
                                this.new_addr = Memory.allocUtf8String("/proc/self/status11");
                                args[1] = this.new_addr;
                                console.log("onEnter_" + target_hook_addr_offset + " , __NR_openat , args[1] = " + args[1].readCString());
                            }, onLeave: function (retval){
                                console.log("onLeave_" + target_hook_addr_offset + " , __NR_openat , retval = " + retval)
                            }
                        });

                    }
                    // hook svc __NR_faccessat
                    if (call_number_faccessat === call_number){
                        let target_hook_addr = code_address;
                        let target_hook_addr_offset = target_hook_addr.sub(baseAddress);
                        console.log("find svc faccessat , start inlinehook by frida!")
                        Interceptor.attach(target_hook_addr, {
                            onEnter: function (args){
                                console.log("\nonEnter_" + target_hook_addr_offset + " , __NR_faccessat , args[1] = " + args[1].readCString());
                                // this.new_addr = Memory.allocUtf8String("/proc/self/status11");
                                // args[1] = this.new_addr;
                                console.log("onEnter_" + target_hook_addr_offset + " , __NR_faccessat , args[1] = " + args[1].readCString());
                            }, onLeave: function (retval){
                                console.log("onLeave_" + target_hook_addr_offset + " , __NR_faccessat , retval = " + retval)
                            }
                        });

                    }
                }
            }, onComplete: function () {}
        });

    });
}

USB Debugging Bypass

function USBDebuggingBypass() {
	var Secure = Java.use('android.provider.Settings$Secure');
	var System = Java.use('android.provider.Settings$System');
	var Global = Java.use('android.provider.Settings$Global');
	
	Secure.getInt.overload('android.content.ContentResolver', 'java.lang.String', 'int').implementation = function(arg1, arg2, arg3) {
		if(arg2.indexOf('adb_enabled') !== -1) {
			console.warn('[!] USB Debugging Check Bypass !');
			return 0;
		} else {
			return this.getInt(arg1, arg2, arg3);
		}
	}
	System.getInt.overload('android.content.ContentResolver', 'java.lang.String', 'int').implementation = function(arg1, arg2, arg3) {
		if(arg2.indexOf('adb_enabled') !== -1) {
			console.warn('[!] USB Debugging Check Bypass !');
			return 0;
		} else {
			return this.getInt(arg1, arg2, arg3);
		}
	}
	Global.getInt.overload('android.content.ContentResolver', 'java.lang.String', 'int').implementation = function(arg1, arg2, arg3) {
		if(arg2.indexOf('adb_enabled') !== -1) {
			console.warn('[!] USB Debugging Check Bypass !');
			return 0;
		} else {
			return this.getInt(arg1, arg2, arg3);
		}
	}

	var Debug = Java.use('android.os.Debug');
	Debug.isDebuggerConnected.implementation = function() {
		console.warn('[*] Debug.isDebuggerConnected() Bypass !');

		return false;
	}
}

hook read & open

var TraceSysFD = {};
function prettyLog(str) {
    console.log("---------------------------\n" + str);
}

Interceptor.attach(Module.findExportByName(null, "read"), {
    // fd, buff, len
    onEnter: function (args) {
        var bfr = args[1], sz = args[2].toInt32();
        var path = (TraceSysFD["fd-" + args[0].toInt32()] != null) ? TraceSysFD["fd-" + args[0].toInt32()] : "[unknow path]";
        prettyLog("[Libc::read] Read FD (" + path + "," + bfr + "," + sz + ")\n");
    },
    onLeave: function (ret) {
    }
});
Interceptor.attach(Module.findExportByName(null, "open"), {
    // path, flags, mode
    onEnter: function (args) {
        this.path = args[0].readCString();
    },
    onLeave: function (ret) {
        TraceSysFD["fd-" + ret.toInt32()] = this.path;
        prettyLog("[Libc::open] Open file '" + this.path + "' (fd: " + ret.toInt32() + ")");
    }
});

hook readlink

var aaa,bbb,ccc;
Interceptor.attach(Module.findExportByName(null, "readlink"),{
    onEnter: function(args){
        aaa = args[0];
        bbb = args[1];
        ccc = args[2];
        },
    onLeave: function(retval){
        // console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
        if(bbb.readCString().indexOf("frida")!==-1 ||
            bbb.readCString().indexOf("gum-js-loop")!==-1||
            bbb.readCString().indexOf("gmain")!==-1 ||
            bbb.readCString().indexOf("tmp")!==-1 ||
            bbb.readCString().indexOf("linjector")!==-1){
            console.log('\nreadlink(' + 's1="' + aaa.readCString() + '"' + ', s2="' + bbb.readCString() + '"' + ', s3="' + ccc + '"' + ')');
            bbb.writeUtf8String("/system/framework/boot.art")
            //console.log("replce with: "+bbb.readCString())
            retval.replace(0x1A)
            //console.log("retval: "+retval)
        }
    }
});

hook strstr

var strstr = Module.findExportByName(null, "strstr");
if (null !== strstr) {
    Interceptor.attach(strstr, {
        onEnter: function (args) {
            this.frida = Boolean(0);

            this.haystack = args[0];
            this.needle = args[1];

            if (this.haystack.readCString() !== null && this.needle.readCString() !== null) {
                if (this.haystack.readCString().indexOf("frida") !== -1 ||
                    this.needle.readCString().indexOf("frida") !== -1 ||
                    this.haystack.readCString().indexOf("gum-js-loop") !== -1 ||
                    this.needle.readCString().indexOf("gum-js-loop") !== -1 ||
                    this.haystack.readCString().indexOf("gmain") !== -1 ||
                    this.needle.readCString().indexOf("gmain") !== -1 ||
                    this.haystack.readCString().indexOf("linjector") !== -1 ||
                    this.needle.readCString().indexOf("linjector") !== -1) {
                    this.frida = Boolean(1);
                }
            }
        },
        onLeave: function (retval) {
            if (this.frida) {
                retval.replace(ptr("0x0"));
            }

        }
    })
    console.log("anti anti-frida");
}

hook strcmp

var strcmp = Module.findExportByName(null,"strcmp");
console.log("find strcmp:",strcmp);
Interceptor.attach(strcmp, {
    onEnter: function (args) {
            if(ptr(args[1]).readCString().indexOf("frida")>=0){
                // ptr(args[1]).writeUtf8String('fuck u');
                console.log("[*] strcmp (" + ptr(args[0]).readCString() + "," + ptr(args[1]).readCString()+")");
                
                this.ishack = true;
            }
        
    },
    onLeave: function(retval){
        if(this.ishack){
            retval.replace(ptr("0x0"))
            console.log("the ishack's result :",retval);
        }
        
    }
});

android's People

Contributors

xiaokanghub avatar

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.