Giter Site home page Giter Site logo

voidsec / driverbuddyreloaded Goto Github PK

View Code? Open in Web Editor NEW
298.0 14.0 46.0 245 KB

Driver Buddy Reloaded is an IDA Pro Python plugin that helps automate some tedious Windows Kernel Drivers reverse engineering tasks

Home Page: https://voidsec.com/driver-buddy-reloaded

License: GNU General Public License v3.0

Python 100.00%
reverse-engineering windows-kernel ida ida-plugin idapython windows-driver driver-exploitation

driverbuddyreloaded's Introduction

Driver Buddy Reloaded Quickstart

Table of Contents

Installation

Copy the DriverBuddyReloaded folder and the DriverBuddyReloaded.py script file into the IDA plugins folder, for example:

  • %APPDATA%\Hex-Rays\IDA Pro\plugins\
  • C:\Program Files\IDA Pro 7.6\plugins\
  • ~/.idapro/plugins/

If you use Python v. 3.x, run the idapyswitch.exe binary (located in IDA's folder) from an admin command prompt.

NOTE: IDA SDK > v.7.5 is required in order for this script to run.

Quick Usage

To use the auto-analysis feature:

  1. Start IDA and load a Windows kernel driver.
  2. Go to Edit -> Plugins -> Driver Buddy Reloaded or press CTRL+ALT+A to start the auto-analysis.
  3. Check the "Output" window for the analysis results.
  4. A <DRIVER_NAME>.sys-YYYY-MM-DD-TIME_STAMP-DriverBuddyReloaded_autoanalysis.txt file containing the analysis results, will be written under IDA's DB directory.

To decode an IOCTL:

  1. Place the mouse cursor on the line containing a suspected IOCTL code.
  2. Right-click and select Driver Buddy Reloaded -> Decode IOCTL; alternatively, press the CTRL+ALT+D shortcut.

To decode ALL IOCTLs within a function:

  1. Place the mouse cursor on the first instruction of the function you believe to be the IOCTL dispatcher ( DispatchDeviceControl, DispatchInternalDeviceControl, Possible_DispatchDeviceControl_#)
  2. Right-click and select Driver Buddy Reloaded -> Decode ALL IOCTLs in Function; alternatively, press the CTRL+ALT+F shortcut.
  3. A DriverName.sys-2021-12-10-TIME_STAMP-IOCTLs.txt/DriverName.sys-2021-12-10-TIME_STAMP-IOCTLs.txt_dumb.txt file, containing all the decoded IOCTLs up to that moment, will be written under IDA's DB directory.

Advanced Usage

  • The vulnerable_function_lists directory contains a lists of potentially dangerous/problematic functions, Windows APIs and opcodes; a brief description on why a specific function/API has been listed is provided. You can edit the custom list including driver's specific functions.

    Note: winapi_function_prefixes will partial match to start of function name (e.g. Zw will match ZwClose, ZwCommitComplete and so on) while winapi_functions will perform exact matches only.

  • In find_opcodes.py, the find_opcode_data option will prevent Driver Buddy Reloaded to find opcodes in data sections. Switching it to True will print something along this line: Found jnz short loc_15862 in sub_15820 at 0x00015852 Usually, going at the showed address and re-defining the selection as code will bring the searched opcode back.

    Watch out: switching it to True, will generates more false positives!

About Driver Buddy Reloaded

Driver Buddy Reloaded is an IDA Pro Python plugin that helps automate some tedious Windows Kernel Drivers reverse engineering tasks. It has a number of handy features, such as:

  • Identifying the type of the driver
  • Locating DispatchDeviceControl / DispatchInternalDeviceControl functions
  • Populating common structures for WDF and WDM drivers
    • Attempts to identify and label structures like the IRP and IO_STACK_LOCATION
    • Label calls to WDF functions that would normally be unlabeled
  • Finding and decoding IOCTL codes
  • Flagging functions prone to misuse
  • Finding potential DeviceName
  • Dumping Pooltags

Finding DispatchDeviceControl

The tool can automatically locate and identify the DispatchDeviceControl routine. This function is used to route all incoming DeviceIoControl codes to the specific driver function associated with that code. Automatically identifying this function makes finding the valid DeviceIoControl codes for each driver much quicker. Additionally, when investigating possible vulnerabilities in a driver due to a crash, knowing the location of this function helps narrow the focus to the specific function call associated with the crashing DeviceIoControl code.

When the analysis is successful some subs will be renamed as follow:

  • DriverEntry: the original first driver-supplied routine that is called after a driver is loaded. It is responsible for initializing the driver.
  • Real_Driver_Entry: usually the function where the execution from DriverEntry has been transferred to. It is usually where the DeviceName is initialized.
  • DispatchDeviceControl/DispatchInternalDeviceControl: if the tool was able to recover the functions at some specific offsets, the functions will then be renamed with the appropriate name.
  • Possible_DispatchDeviceControl_#: if the tool was not able to recover DispatchDeviceControl or DispatchInternalDeviceControl, it employs an experimental searching, following the execution flow, and checking for cases where the function is loading known IO_STACK_LOCATION & IRP addresses; indicating that the function could be the DispatchDeviceControl. As it is based on heuristic, it could return more than one result, and it is prone to false positives.

Labelling WDM and WDF Structures

Several driver structures are shared among all WDM/WDF drivers. The tool is able to automatically identify these structures, such as the IO_STACK_LOCATION, IRP, and DeviceObject structures and can help save time during the reverse engineering process and provide context to areas of the driver where these functions are in use.

Finding and Decoding IOCTL Codes

While reversing drivers, it is common to come across IOCTL codes as part of the analysis. These codes, when decoded, reveal useful information and may draw focus to specific parts of the driver where vulnerabilities are more likely to exist.

By right-clicking on a potential IOCTL code, a context menu option is presented (alternatively using the Ctrl+Alt+D shortcut when the cursor is on the line containing a suspected IOCTL code) and can be used to decode the value. This will print out a table with all decoded IOCTL codes. By right-clicking on a decoded IOCTL code, in the disassembly view, it's possible to mark it as invalid; this will leave any non-IOCTL comment intact.

If you right-click, alternatively using the Ctrl+Alt+F shortcut, on the first instruction of the function you believe to be the IOCTL dispatcher ( DispatchDeviceControl, DispatchInternalDeviceControl, Possible_DispatchDeviceControl_#) under the Driver Buddy Reloaded menu, a “Decode All” option appears, this attempt to decode all the IOCTL codes it can find in the function. This is a bit hacky but most of the time it can speed things up.

  • A DriverName.sys-2021-12-10-TIME_STAMP-IOCTLs.txt/DriverName.sys-2021-12-10-TIME_STAMP-IOCTLs.txt_dumb.txt file, containing all the decoded IOCTLs up to that moment, will be written under IDA's DB directory.

Flagging Functions

Driver Buddy Reloaded has lists of C/C++ functions, opcodes and Windows APIs (defined in the vulnerable_function_lists directory) that are commonly vulnerable or that can facilitate buffer overflow conditions. All found instances are reported back during the auto-analysis and can help while looking for possible user-controlled code paths reaching sensitive functions.

Finding DeviceName

The tool automatically attempts to find the drivers registered device paths (DeviceName), if no paths can be found by looking at Unicode strings inside the binary, then the analyst can manually try to use Madiant’s FLOSS in an attempt to find obfuscated paths.

Dumping Pooltags

During the auto-analysis, the tool also dumps the Pooltags used by the binary in a format that works with pooltags.txt. The output can then be copy-pasted at the end of the file and later picked up by WinDbg.

  • A DriverName.sys-2021-12-10-TIME_STAMP-pooltags.txt file, containing all the dumped Pooltags, will be written under IDA's DB directory.

Known Caveats and Limitations

  • Only IOCTL values >= 0x10000 will be automatically decoded, thus to prevent an high number of false positives. Issue #15
  • Experimental DispatchDeviceControl searching works only for x64 drivers
  • Shortcuts are incompatible with F-Secure's win_driver_plugin
  • Shortcuts are incompatible with findcrypt-yara
  • In find_opcodes.py, the find_opcode_data option will prevent Driver Buddy Reloaded to find opcodes in data sections. Switching it to True will print something along this line: Found jnz short loc_15862 in sub_15820 at 0x00015852 Usually, going at the showed address and re-defining the selection as code will bring the searched opcode back. Watch out: It is prone to false positives!

Credits and Acknowledgements

  • Created in 2021 by Paolo Stagno aka @Void_Sec:
    • Made it compatible with Python 3.x
    • Made it compatible with IDA 7.x
    • Updated C/C++ function and Windows APIs list
    • Various bug fixing
    • Various improvements
    • Integrated part of the functionalities presents in F-Secure's win_driver_plugin
  • DriverBuddy was originally written by Braden Hollembaek and Adam Pond of NCC Group.
  • Using Satoshi Tanda's IOCTL decoder.
  • The WDF functions struct is based on Red Plait's work and was ported to IDA Python by Nicolas Guigo, later updated by Braden Hollembaek and Adam Pond.
  • Using Sam Brown's F-Secure win_driver_plugin to retrieve device name and pool tags, specifically Alexander Pick fork.
  • The original code for adding items to the right-click menu (and possibly some other random snippets) came from 'herrcore'.
  • Prodly developed using PyCharm for Open Source development by JetBrains

driverbuddyreloaded's People

Contributors

eranzim avatar harelon avatar voidsec avatar zeze-zeze 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  avatar  avatar  avatar  avatar  avatar

driverbuddyreloaded's Issues

[FEATURE] enumeration of MajorCodes

It will be useful add the enumeration of MajorCodes

enum Major_Codes
{
  IRP_MJ_CREATE = 0x0,
  IRP_MJ_CREATE_NAMED_PIPE = 0x1,
  IRP_MJ_CLOSE = 0x2,
  IRP_MJ_READ = 0x3,
  IRP_MJ_WRITE = 0x4,
  IRP_MJ_QUERY_INFORMATION = 0x5,
  IRP_MJ_SET_INFORMATION = 0x6,
  IRP_MJ_QUERY_EA = 0x7,
  IRP_MJ_SET_EA = 0x8,
  IRP_MJ_FLUSH_BUFFERS = 0x9,
  IRP_MJ_QUERY_VOLUME_INFORMATION = 0xA,
  IRP_MJ_SET_VOLUME_INFORMATION = 0xB,
  IRP_MJ_DIRECTORY_CONTROL = 0xC,
  IRP_MJ_FILE_SYSTEM_CONTROL = 0xD,
  IRP_MJ_DEVICE_CONTROL = 0xE,
  IRP_MJ_INTERNAL_DEVICE_CONTROL = 0xF,
  IRP_MJ_SHUTDOWN = 0x10,
  IRP_MJ_LOCK_CONTROL = 0x11,
  IRP_MJ_CLEANUP = 0x12,
  IRP_MJ_CREATE_MAILSLOT = 0x13,
  IRP_MJ_QUERY_SECURITY = 0x14,
  IRP_MJ_SET_SECURITY = 0x15,
  IRP_MJ_QUERY_POWER = 0x16,
  IRP_MJ_SET_POWER = 0x17,
  IRP_MJ_DEVICE_CHANGE = 0x18,
  IRP_MJ_QUERY_QUOTA = 0x19,
  IRP_MJ_SET_QUOTA = 0x1A,
  IRP_MJ_PNP_POWER = 0x1B,
  IRP_MJ_MAXIMUM_FUNCTION = 0x1C,
};

If this enumeration exists in localtypes and is syncronized, you can press M in the code numbers and add the MJ function name.

This can be converted from

NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
  int v3; // ebx
  _QWORD *v4; // rcx
  __int64 v5; // rax
  struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-28h] BYREF
  struct _UNICODE_STRING SymbolicLinkName; // [rsp+50h] [rbp-18h] BYREF
  PDEVICE_OBJECT DeviceObject; // [rsp+70h] [rbp+8h] BYREF

  DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
  DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
  DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
  DriverObject->DriverStartIo = 0i64;
  DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_11520;

to

NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
  int v3; // ebx
  _QWORD *v4; // rcx
  __int64 v5; // rax
  struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-28h] BYREF
  struct _UNICODE_STRING SymbolicLinkName; // [rsp+50h] [rbp-18h] BYREF
  PDEVICE_OBJECT DeviceObject; // [rsp+70h] [rbp+8h] BYREF

+  DriverObject->MajorFunction[IRP_MJ_CREATE] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
+  DriverObject->MajorFunction[IRP_MJ_CLOSE] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
+  DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)&Possible_DispatchDeviceControl_0;
  DriverObject->DriverStartIo = 0i64;

Thanks for a good tool

[BUG] "IOCTL Decode All" is missing some cases

In

# If the penultimate instruction is cmp or sub against an immediate value immediately preceding a 'jz'

"IOCTL Decode All" function is missing some cases as can be shown in the following image:
image

The only immediate fix I can think of is reworking the heuristic functionality:

  1. processing the entire block for cmp, mov, sub operations
  2. checking that the immediate operand has 10 digits
  3. excluding NTSTATUS values
  4. decoding the suspected IOCTL code

Will probably generate some more false positives but it will cover "switch" cases falling into "default" case and such

[BUG] WDF Structures

In commit 43eba17 I've finished updating IDA's APIs and fixing breaking code changes.

Unfortunately, despite the script is not breaking anymore, it seems that it still fails this condition at:

if ida_bytes.create_struct(wdf_func, size, id) and idc.set_name(wdf_func, 'WdfFunctions', 0):

for a reason that, at the moment, is unknown. The logic behind https://github.com/VoidSec/DriverBuddyReloaded/blob/main/DriverBuddyReloaded/wdf.py is pretty "hacky" and somewhat "obscure". In addition to that, I'm not sure that the logic detecting the WDF version at

version = int(str(idc.get_wide_dword(addr + ptr_size + 0x4)))
makes complete sense.

We should also update the WDF structures in order to include updated ones and keep them updated as I'm pretty sure the latest WDF version is >= 1.13.

DriverBuddy entry does not shown in plugins menu

I use IDA Pro 7.6 and Python 3.10, after copying the folder and py script, it should be appeared in Edit->Plugins, but I cannot see the plugin listed?, exact for which IDA and Python versions, will this work?

[BUG] TypeError: %d format: a number is required, not struc_t

Failed while executing plugin_t.run():
Traceback (most recent call last):
  File "D:/IDA/plugins/DriverBuddyReloaded.py", line 466, in run
    driver_type = utils.get_driver_id(driver_entry_addr, log_file)
  File "D:\IDA/plugins\DriverBuddyReloaded\utils.py", line 209, in get_driver_id
    populate_wdf()
  File "D:\IDA/plugins\DriverBuddyReloaded\wdf.py", line 759, in populate_wdf
    id = add_struct(version)
  File "D:\IDA/plugins\DriverBuddyReloaded\wdf.py", line 50, in add_struct
    idc.add_struc_member(struc, "pfnWdfChildListCreate", idc.BADADDR, idc.FF_DATA | FF_PTR, None, ptr_size)
  File "D:\IDA\python\3\idc.py", line 3919, in add_struc_member
    return eval_idc('add_struc_member(%d, "%s", %d, %d, %d, %d);' % (sid, ida_kernwin.str2user(name or ""), offset, flag, typeid, nbytes))
TypeError: %d format: a number is required, not struc_t

[BUG] WDF/KMDF detection

Describe the bug
WDF detection library correctly detect a KmdfLibrary but the driver is marked as WSF anyway

To Reproduce

[WDF]: Found KmdfLibrary string at 0x14000f388
[WDF]: Creating struct for KmdfLibrary Functions version 1.11
[WDF]: doStruct (size=0xdb0) at 0x140010430
[WDF]: Success
[+] Driver type detected: WDF

Expected behavior
[+] Driver type detected: KMDF

[BUG] TypeError: in method 'get_struc', argument 1 of type 'ea_t'

Testing cdrom.sys

Failed while executing plugin_t.run():
Traceback (most recent call last):
  File "D:/IDA/plugins/DriverBuddyReloaded.py", line 466, in run
    driver_type = utils.get_driver_id(driver_entry_addr, log_file)
  File "D:\IDA/plugins\DriverBuddyReloaded\utils.py", line 208, in get_driver_id
    populate_wdf()
  File "D:\IDA/plugins\DriverBuddyReloaded\wdf.py", line 759, in populate_wdf
    id = add_struct(version)
  File "D:\IDA/plugins\DriverBuddyReloaded\wdf.py", line 45, in add_struct
    idc.del_struc(idaapi.get_struc(id))
  File "D:\IDA\python\3\idc.py", line 3855, in del_struc
    s = ida_struct.get_struc(sid)
  File "D:\IDA\python\3\ida_struct.py", line 532, in get_struc
    return _ida_struct.get_struc(*args)
TypeError: in method 'get_struc', argument 1 of type 'ea_t'

[BUG] module 'idaapi' has no attribute 'compiled_binpat_vec_t'

Traceback (most recent call last):
  File "C:/Program Files/IDA 7.0/plugins/DriverBuddyReloaded.py", line 465, in run
    driver_type = utils.get_driver_id(driver_entry_addr, log_file)
  File "C:/Program Files/IDA 7.0/plugins\DriverBuddyReloaded\utils.py", line 209, in get_driver_id
    populate_wdf()
  File "C:/Program Files/IDA 7.0/plugins\DriverBuddyReloaded\wdf.py", line 102, in populate_wdf
    binpat = idaapi.compiled_binpat_vec_t()
AttributeError: module 'idaapi' has no attribute 'compiled_binpat_vec_t'

Version 7.5.201028 Windows x64 (64-bit address size)

7.6 minimum required?

[FEATURE] Some pooltags aren't recognized

Describe the bug
Pooltags which aren't immediate values in the correct place, but possibly propagated via a register, aren't found.
Example code snippet:

...
mov     ebp, 'ABCD'
mov     rdx, rax        ; NumberOfBytes
mov     r8d, ebp        ; Tag
call    cs:ExAllocatePoolWithTag

Expected behavior
All Pooltags should be found

Desktop (please complete the following information):

  • OS and version: Windows 10 21H2 (19044.1586)
  • IDA version: IDA 7.7 SP1
  • DriverBuddyReloaded Version: latest (1.3)
  • Python Version: 3.9.5

[BUG] `parse_binpat_str` expected at least 4 arguments

Describe the bug
When I try to decode cdrom.sys, it will occur python warning.

To Reproduce
Steps to reproduce the behavior:

  1. use ida to reverse cdrom.sys
  2. Ctrl+Alt+A
Traceback (most recent call last):
  File "C:/Users/raven/Desktop/ida77sp1/x64_idapronw_hexarm64w_hexarmw_hexmipsw_hexppc64w_hexppcw_hexx64w_hexx86w_220118/plugins/DriverBuddyReloaded.py", line 466, in run
    driver_type = utils.get_driver_id(driver_entry_addr, log_file)
  File "C:/Users/raven/Desktop/ida77sp1/x64_idapronw_hexarm64w_hexarmw_hexmipsw_hexppc64w_hexppcw_hexx64w_hexx86w_220118/plugins\DriverBuddyReloaded\utils.py", line 205, in get_driver_id
    populate_wdf()
  File "C:/Users/raven/Desktop/ida77sp1/x64_idapronw_hexarm64w_hexarmw_hexmipsw_hexppc64w_hexppcw_hexx64w_hexx86w_220118/plugins\DriverBuddyReloaded\wdf.py", line 753, in populate_wdf
    idx = ida_bytes.bin_search(ea, idaapi.BADADDR, ida_bytes.parse_binpat_str("KmdfLibrary"),
  File "C:\Users\raven\Desktop\ida77sp1\x64_idapronw_hexarm64w_hexarmw_hexmipsw_hexppc64w_hexppcw_hexx64w_hexx86w_220118\python\3\ida_bytes.py", line 3903, in parse_binpat_str
    return _ida_bytes.parse_binpat_str(*args)
TypeError: parse_binpat_str expected at least 4 arguments, got 1

Expected behavior

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS and version: Windows 11
  • IDA version IDA 7.7
  • DriverBuddyReloaded Version: HEad
  • Python Version 3.9

Additional context
Add any other context about the problem here.

[BUG] IDA will automatically identifies the driver entry as "GsDriverEntry".

In IDA Pro 8.2, IDA automatically identifies the driver entry as "GsDriverEntry". However, this can cause a bug as DriverBuddyReloaded might mistakenly determine it is not a driver. To resolve this issue, we need to patch the is_driver function in utils.py.

def is_driver():
    """
    Determine if the loaded file is actually a Windows driver, checking if `DriverEntry` is in the exports section.
    :return: address of `DriverEntry` if found in exports, False otherwise
    """

    for segment_address in idautils.Segments():
        for func_addr in idautils.Functions(idc.get_segm_start(segment_address), idc.get_segm_end(segment_address)):
            func_name = idc.get_func_name(func_addr)
            if func_name == "DriverEntry":
                return func_addr
            elif func_name == "DriverEntry_0":
                return func_addr
            elif func_name == "GsDriverEntry":
                return func_addr
    return False

[FEATURE] Shortcut for "Decode All" action

Instead of going into the "Driver Buddy Reloaded" menu and selecting "Decode all IOCTLs in Function", it would be nice to have a shortcut "triggering" the functionality.

Something along the line of:

  • CTRL+ALT+D: Decode IOCTL
  • CTRL+ALT+A: Auto-analysis
  • CRTL+ALT+F: Decode all IOCTLs in Function

image

[BUG] IOCTLs with less than 10 decimal digits aren't found

Describe the bug
Any IOCTL with a code that has less than 10 decimal digits (e.g. 0x222003) won't be found by the current code.

Expected behavior
All IOCTLs should be found

Desktop (please complete the following information):

  • OS and version: Windows 10 21H2 (19044.1586)
  • IDA version: IDA 7.7 SP1
  • DriverBuddyReloaded Version: latest (1.3)
  • Python Version: 3.9.5

[BUG] find opcode

find opcode sometimes print out opcodes not related with the searching

[>] Searching for interesting opcodes...
	- Found mov     al, [rdi+rcx] in sub_231C4 at 0x0002327d

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.