crmulliner / adbi Goto Github PK
View Code? Open in Web Editor NEWAndroid Dynamic Binary Instrumentation Toolkit
Home Page: http://www.mulliner.org/android
Android Dynamic Binary Instrumentation Toolkit
Home Page: http://www.mulliner.org/android
Hi, thanks for your adbi!!!
I have read hijack.c, and got some questions on 2 functions: find_linker
, find_linker_mem
.
find_linker
seems copied from find_name
, are the variables' name(libcaddr
, libc
) unsuitable?static int find_linker(pid_t pid, unsigned long *addr)
{
struct mm mm[1000];
unsigned long libcaddr; // variable name "libcaddr" not suitable?
int nmm;
char libc[256]; // variable name "libc" not suitable?
symtab_t s;
if (0 > load_memmap(pid, mm, &nmm)) {
printf("cannot read memory map\n");
return -1;
}
if (0 > find_linker_mem(libc, sizeof(libc), &libcaddr, mm, nmm)) {
printf("cannot find libc\n");
return -1;
}
*addr = libcaddr;
return 1;
}
find_linker_mem
seems copied from find_libc
p+=4
wrong(because the string "linker" is 6 bytes)?if (!strncmp(".so", p, 3) || (p[0] == '-' && isdigit(p[1])))
below, we are to find address of "/system/bin/linker", isn't it?static int
find_linker_mem(char *name, int len, unsigned long *start,
struct mm *mm, int nmm)
{
int i;
struct mm *m;
char *p;
for (i = 0, m = mm; i < nmm; i++, m++) {
//printf("name = %s\n", m->name);
//printf("start = %x\n", m->start);
if (!strcmp(m->name, MEMORY_ONLY))
continue;
p = strrchr(m->name, '/');
if (!p)
continue;
p++;
if (strncmp("linker", p, 6))
continue;
break; // <--- hack
p += 4; // Is this wrong? p += 6?
/* here comes our crude test -> 'libc.so' or 'libc-[0-9]' */
if (!strncmp(".so", p, 3) || (p[0] == '-' && isdigit(p[1]))) // what's here find for? we are find "/system/bin/linker", isn't it?
break;
}
if (i >= nmm)
/* not found */
return -1;
*start = m->start;
strncpy(name, m->name, len);
if (strlen(m->name) >= len)
name[len-1] = '\0';
return 0;
}
Apologize for my poor English, look forward to your reply, and thank you again : )
Hi ,
I am getting the below error when building the project.Please help on this below error.
D:/dummy_data/adbi_master/hijack/jni/../hijack.c:529:76: warning: comparison of unsigned expression < 0 is always false
[-Wtautological-compare]
if ((*p = ptrace(PTRACE_PEEKTEXT, pid, (void )(pos+(i4)), (void *)*p)) < 0)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~
D:/dummy_data/adbi_master/hijack/jni/../hijack.c:764:11: error: use of undeclared identifier 'PTRACE_GETREGS'
ptrace(PTRACE_GETREGS, pid, 0, ®s);
^
D:/dummy_data/adbi_master/hijack/jni/../hijack.c:792:9: error: use of undeclared identifier 'PTRACE_GETREGS'
ptrace(PTRACE_GETREGS, pid, 0, ®s);
^
D:/dummy_data/adbi_master/hijack/jni/../hijack.c:873:9: error: use of undeclared identifier 'PTRACE_SETREGS'
ptrace(PTRACE_SETREGS, pid, 0, ®s);
^
2 warnings and 3 errors generated.
Hi all,
I compiled hijack successfully, however, when I run ./hijack -d -p 2039 -l libname.so, I got this error message:
mprotect: 0x1422fcf0
Can't open /proc/yyy/maps for reading
cannot read memory map
dlopen: 0x2821adf9
cannot attach to xxx, error!
I'm new to Linux programming, please shed me some light.
In testing on CyanogenMod 12, I saw consistent SEGV_ACCERR
with the "shell code" entrypoint as the fault address, and -1
value in r0
. I believe this means the mprotect()
call to make the stack executable failed. If I turn off SELinux enforcement (setenforce 0
), it appears to get further (I believe it was an ARM/Thumb hook mismatch that crashed it later.) I don't know a lot about it, but there appears to be an SELinux feature execstack
that prevents the stack from being made executable.
I don't know if this is a CM-specific SELinux policy, or how widely it will be implemented in other Android 5.0 builds, but it would make sense if this is in AOSP.
There's another Android injector here, which appears to function without mprotect()
(you have to adjust inject.c for your needs.) I haven't tested it on Android 5 yet, though.
I know solving this is not trivial, so I'm not requesting it, just noting the issue.
the following code failed with errno = 2, when filename = "/system/lib/nb/libc.so",
fd = open(filename, O_RDONLY)
call stack:
hook(..., "libc.", "epoll_wait", ...)->find_name->load_symtab->open
I have tried "cat /system/lib/nb/libc.so", and succeed.
Great project. After carefully read the source code and implementation, I have one concern, not sure it is correct. My concern is if the hooked API is called by multiple threads in one process, is it safe?
That is saying if the hooked thread is called and goes into hook_postcall() but not completed, another thread is trying call the same hooked API, how to deal with this case? Thanks
--TK
I follow the step by step, but i got the error message when I execute the command: ./hijack -d -p PID -l /data/local/tmp/libexample.so
error: tmp-mksh: ./hijack: can't execute: Permission denied.
Device: Nexus 5 root 5.1.1
I am a freshman, so this is a low-level mistake.
Could you pls give me some help, thank you.
Hi all,
I have problems running hijack process for this example. Do you know how to fix the problem?
root@MLA-TL10:/data/local/tmp # ./hijack -d -p 11329 -l libexample.so
./hijack -d -p 11329 -l libexample.so
Open maps file successfullymprotect: 0x1422fcf0
Open maps file successfullyOpen maps file successfullydlopen: 0xb77d0378
Attached to process successfullypc=d000 lr=d000 sp=2 fp=400f4c26
r0=1c010030 r1=40217cc4
r2=401b2df8 r3=0
libaddr: ffffffa2
stack: 0xbfcbb000-0xbfcdc000 leng = 135168
cannot write library name (libexample.so) to stack, error 5 addr=-94!
I'm using MEMU emulator.
static int new_property_get(const char *name, char *value) {
if(strcmp(name,"ro.product.board") == 0){
strcpy("google.com",value);
// int a = old_property_get(name, value);
Log("================Data native hook... %s -> %s | %d ", name, value,7);
return 7;
}
int v = old_property_get(name, value);
Log("Data native hook... %s -> %s | %d ", name, value,v);
return v;
}
08 11:31:35.851 443-443/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/grandppltedx/grandpplte:6.0.1/MMB29T/G532GDXU1APJ3:user/release-keys'
Revision: '0'
ABI: 'arm'
pid: 11717, tid: 11830, name: RenderThread >>> com.xx.xx.xx<<<
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0xb340c0f8
05-08 11:31:35.871 443-443/? A/DEBUG: r0 b340c0f8 r1 b3377e35 r2 0000006d r3 72000000
r4 b649d59a r5 00000000 r6 b3377e34 r7 b337be74
r8 b3377e34 r9 b345b6e3 sl 00000001 fp b649ee08
ip b340dffc sp b3377df0 lr b340c091 pc b6cef586 cpsr 600b0030
05-08 11:31:35.881 443-443/? A/DEBUG: backtrace:
#00 pc 00018586 /system/lib/libc.so (strcpy+5)
#1 pc 0000108d /data/app/com.xx.xx.xx-1/lib/arm/libhook.so
Title says it all. We should check return values and report errors accordingly.
Hello,
Iam following the manual to test the adbi toolkit. But every time, at the point with the hijack file "./hijack -d -p PID -l /data/local/tmp/libexample.so" Iam getting theses error message "error usage: ./hijack -p PID -l LIBNAME [-d (debug on)] [-z (zygote)] [-m (no mprotect)] [-s (appname)] [-Z (trace count)] [-D (debug level)]". Iam having no idea whats the problem, please can someone help me.
When try to compile, i have an error:
[arm64-v8a] Compile : base <= hook.c
~/adbi/instruments/base/jni/../hook.c:43:5: error: unknown register name 'r0' in asm
: "r0", "r1", "r7"
First, I'm very interested by this tool's possibilities, and would like to thank you for sharing your work.
I'm testing with a KitKat 4.4.4 device, kernel 3.4.42. Android NDK tools are the latest (r10, toolchains 4.6 and 4.8). GNU Make 3.81 et GCC 4.8.2.
I'm simply following the example included in the README.
Compilation and invocation go smooth:
root@falcon_umts:/data/local/tmp # ./hijack -d -p 1316 -l ./libexample.so
mprotect: 0x400f0670
dlopen: 0x400a8f31
pc=400f190c lr=40159653 sp=beeda328 fp=beeda4bc
r0=fffffffc r1=beeda348
r2=10 r3=ffffffff
stack: 0xbeeba000-0xbeedb000 leng = 135168
executing injection code at 0xbeeda2d8
calling mprotect
library injection completed!
But, the adbi_example.log file is empty, and remains empty for ever.
Reading logcat, it seems that the target process (in this case com.android.phone, as suggested) is killed as soon as injected code begins execution:
F/libc ( 1316): Fatal signal 11 (SIGSEGV) at 0x0000000c (code=1), thread 1316 (m.android.phone)
I/DEBUG ( 266): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG ( 266): AM write failure (32 / Broken pipe)
I/DEBUG ( 266): Build fingerprint: 'motorola/falcon_retfr/falcon_umts:4.4.4/KXB21.14-L1.40/37:user/release-keys'
I/DEBUG ( 266): Revision: 'p3c0'
I/DEBUG ( 266): pid: 1316, tid: 1316, name: m.android.phone >>> com.android.phone <<<
I/DEBUG ( 266): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0000000c
I/DEBUG ( 266): r0 00000000 r1 6128a561 r2 ffffff4c r3 4011e394
I/DEBUG ( 266): r4 beeda200 r5 40128334 r6 beeda200 r7 6128a561
I/DEBUG ( 266): r8 00000002 r9 400b8678 sl 00000000 fp 6128be38
I/DEBUG ( 266): ip 6128bf7c sp beed9b20 lr 400f8125 pc 400f6f3c cpsr 400d0030
Could you confirm that this is supposed to work on KitKat 4.4.4, or should I look for some recent Android OS changes that may affect this software behavior ?
(I've already tried setenforce 0
before launching the hijack tool, but result is identical)
Am I missing something ?
Thank you for your help.
I tested adbi in android 4.3, hook process netd success, but com.android.phone failed, com.android.phone maps have libexample.so, bu cat /data/local/tmp/adbi_example.log have nothing. what's wrong?
When hooking malloc using the following code:
#define LOGP(...) ((void) __android_log_print (ANDROID_LOG_ERROR, "HOOKERJB: ", __VA_ARGS__))
hook (&bhook_malloc, getpid (), "libc.", "malloc", NULL, hbimp_malloc);
typedef void * (*func_malloc) (size_t size);
void * hbimp_malloc (size_t size)
{
LOGP ("hbimp_malloc () called");
static int counter = 10;
func_malloc imp_malloc = (void *) bhook_malloc.orig;
// hook_precall (&bhook_malloc);
void * mem = imp_malloc (size);
// hook_postcall (&bhook_malloc);
return mem;
}
Segfaults occurs after multiple callings to hbimp_malloc () - well if one enables hook_precall and hook_postcall, segfaults take place immediately
E/HOOKERJB: ( 2415): hbimp_malloc () called
E/HOOKERJB: ( 2415): hbimp_malloc () called
E/HOOKERJB: ( 2415): hbimp_malloc () called
E/HOOKERJB: ( 2415): hbimp_malloc () called
E/HOOKERJB: ( 2415): hbimp_malloc () called
E/HOOKERJB: ( 2415): hbimp_malloc () called
I/ActivityManager( 592): Process com.xxx.xxx (pid 2415) has died.
Verbose kmsg shows address here:
<1>[ 868.312316] pgd = eac98000
<1>[ 868.312438] [740a1ff8] *pgd=aa94a831, *pte=00000000, *ppte=00000000
<4>[ 868.312683]
<4>[ 868.312744] Pid: 2477, comm: Thread-120
<4>[ 868.312835] CPU: 3 Not tainted (3.4.0-cyanogenmod-gb7efce1-dirty #6)
<4>[ 868.312927] PC is at 0x40067e80
<4>[ 868.312988] LR is at 0x4006c651
<4>[ 868.313079] pc : [<40067e80>] lr : [<4006c651>] psr: 80000030
<4>[ 868.313079] sp : 740a2000 ip : 00000008 fp : 741a0b84
<4>[ 868.313262] r10: 00000000 r9 : 00000016 r8 : 740a20b8
<4>[ 868.313323] r7 : 740a20fc r6 : 761434ea r5 : 00000016 r4 : 740a270c
<4>[ 868.313446] r3 : 00000200 r2 : 00000016 r1 : 761434ea r0 : 740a276c
<4>[ 868.313507] Flags: Nzcv IRQs on FIQs on Mode USER_32 ISA Thumb Segment user
The PC corresponds to libc.so it seems (maybe malloc..), according to the following
4004b000-40091000 r-xp 00000000 b3:16 38543 /system/lib/libc.so
40091000-40093000 r--p 00045000 b3:16 38543 /system/lib/libc.so
40093000-40095000 rw-p 00047000 b3:16 38543 /system/lib/libc.so
Should adbi be used to hook functions such as malloc?
after add below line in instruments/base/jni/Application.mk
APP_ABI := armeabi armeabi-v7a arm64-v8a,
build.sh report several error.
so how to support arm64-v8a?
/tmp/ccHKfvB7.s: Assembler messages:
/tmp/ccHKfvB7.s:24: Error: operand 1 should be an integer register -- mov r0,x3' /tmp/ccHKfvB7.s:25: Error: operand 1 should be an integer register --
mov r1,x4'
/tmp/ccHKfvB7.s:26: Error: operand 1 should be an integer register -- mov r7,x2' /tmp/ccHKfvB7.s:27: Error: operand 1 should be an integer register --
mov r2,#0x0'
/tmp/ccHKfvB7.s:177: Error: operand 1 should be an integer register -- mov r0,x2' /tmp/ccHKfvB7.s:178: Error: operand 1 should be an integer register --
mov r1,x4'
/tmp/ccHKfvB7.s:179: Error: operand 1 should be an integer register -- mov r7,x3' /tmp/ccHKfvB7.s:180: Error: operand 1 should be an integer register --
mov r2,#0x0'
/tmp/ccHKfvB7.s:440: Error: operand 1 should be an integer register -- mov r0,x2' /tmp/ccHKfvB7.s:441: Error: operand 1 should be an integer register --
mov r1,x4'
/tmp/ccHKfvB7.s:442: Error: operand 1 should be an integer register -- mov r7,x3' /tmp/ccHKfvB7.s:443: Error: operand 1 should be an integer register --
mov r2,#0x0'
/tmp/ccHKfvB7.s:683: Error: operand 1 should be an integer register -- mov r0,x2' /tmp/ccHKfvB7.s:684: Error: operand 1 should be an integer register --
mov r1,x4'
/tmp/ccHKfvB7.s:685: Error: operand 1 should be an integer register -- mov r7,x3' /tmp/ccHKfvB7.s:686: Error: operand 1 should be an integer register --
mov r2,#0x0'
/tmp/ccHKfvB7.s:798: Error: operand 1 should be an integer register -- mov r0,x2' /tmp/ccHKfvB7.s:799: Error: operand 1 should be an integer register --
mov r1,x4'
/tmp/ccHKfvB7.s:800: Error: operand 1 should be an integer register -- mov r7,x3' /tmp/ccHKfvB7.s:801: Error: operand 1 should be an integer register --
mov r2,#0x0'
make: *** [/home/jingtao/git/adbi/instruments/base/obj/local/arm64-v8a/objs/base/__/hook.o] Error 1
Great project. But I found it can't run on art runtime while google have released android-l which change it's runtime---“art”,so I want to know did you have plan to support android-l? Thank you very much!
hi,
have you tried to hook void functions ?
i.e. void AudioRecord::stop() into /system/lib/libmedia.so
it crashes after returning from the hook, or after a few calls. probably compiler optimizations on functions returning void.
seems your library works fine in any other case except this ....
regards,
valerio
Good morning!
I am having some problems testing adbi in my rooted device (Lollipop).
First, let me say that original code gives me "Only PIE are supported" error, so here's what I did to bypass that:
In every Application.mk to build:
APP_PLATFORM := android-16
APP_ABI := armeabi-v7a
In every Android.mk to build:
LOCAL_CFLAGS += -fPIE
Final thing:
before launching the hijacker on the device:
chmod 666 /data/local/tmp/adbi_example.log
With these modifications, everything build fine and I can inject the library in my device.
Now, the problem. Hooking doesn't work because the chosen process crashes as soon as I execute the hijacker, and then restarts with a different PID. Here are the highlights from logcat:
I/rmt_storage( 220): rmt_storage_connect_cb: clnt_h=0x1f conn_h=0xb8bc7820
I/rmt_storage( 220): rmt_storage_rw_iovec_cb: /boot/modem_fs1: clnt_h=0x1: req_h=0x23 msg_id=3: R/W request received
I/rmt_storage( 220): wakelock acquired: 1, error no: 42
I/rmt_storage( 220): rmt_storage_client_thread: /boot/modem_fs1: clnt_h=0x1 Unblock worker thread (th_id: -1195608776)
I/rmt_storage( 220): rmt_storage_client_thread: /boot/modem_fs1: clnt_h=0x1: req_h=0x23 msg_id=3: Bytes written = 1572864
I/rmt_storage( 220): rmt_storage_client_thread: /boot/modem_fs1: clnt_h=0x1: req_h=0x23 msg_id=3: Send response: res=0 err=0
I/rmt_storage( 220): rmt_storage_client_thread: /boot/modem_fs1: clnt_h=0x1 About to block rmt_storage client thread (th_id: -1195608776) wakelock released: 1, error no: 22
I/rmt_storage( 220):
I/rmt_storage( 220): rmt_storage_disconnect_cb: clnt_h=0x0x1f conn_h=0x0xb8bc7820
F/libc (26103): Fatal signal 11 (SIGSEGV), code 1, fault addr 0xc in tid 26103 (m.android.phone)
I/DEBUG ( 254): property debug.db.uid not set; NOT waiting for gdb.
I/DEBUG ( 254): HINT: adb shell setprop debug.db.uid 100000
I/DEBUG ( 254): HINT: adb forward tcp:5039 tcp:5039
I/DEBUG ( 254): Build fingerprint: 'motorola/condor_retgb/condor_umts:4.4.4/KXC21.5-40/46:user/release-keys'
I/DEBUG ( 254): Revision: '33456'
I/DEBUG ( 254): ABI: 'arm'
I/DEBUG ( 254): pid: 26103, tid: 26103, name: m.android.phone >>> com.android.phone <<<
E/DEBUG ( 254): AM write failure (32 / Broken pipe)
I/DEBUG ( 254): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
I/DEBUG ( 254): r0 ffffffff r1 bed15068 r2 00000010 r3 0000000c
I/DEBUG ( 254): r4 b6e470f8 r5 00000008 r6 bed15008 r7 00000000
I/DEBUG ( 254): r8 00000000 r9 b7dff5a0 sl 00000000 fp ffffffff
I/DEBUG ( 254): ip b6e4b31d sp bed14ff8 lr b6e4b325 pc b6e4b32a cpsr 00000030
I/DEBUG ( 254):
I/DEBUG ( 254): backtrace:
I/DEBUG ( 254): #.00 pc 0001632a /system/lib/libc.so (_set_errno+13)
I/DEBUG ( 254): #.01 pc 00011f15 /system/lib/libc.so (epoll_pwait+40)
I/DEBUG ( 254): #.02 pc 00011f27 /system/lib/libc.so (epoll_wait+10)
I/DEBUG ( 254): #.03 pc 00012fd7 /system/lib/libutils.so (android::Looper::pollInner(int)+98)
I/DEBUG ( 254): #.04 pc 000132c1 /system/lib/libutils.so (android::Looper::pollOnce(int, int, int, void/_)+40)
I/DEBUG ( 254): #.05 pc 00095311 /system/lib/libandroid_runtime.so (android::NativeMessageQueue::pollOnce(JNIEnv, int)+24)
I/DEBUG ( 254): #.06 pc 000b6f53 /data/dalvik-cache/arm/system@framework@boot,oat
I/DEBUG ( 254):
I/DEBUG ( 254): Tombstone written to: /data/tombstones/tombstone_07
I/BootReceiver( 932): Copying /data/tombstones/tombstone_07 to DropBox (SYSTEM_TOMBSTONE)
I/ServiceManager( 213): service 'isub' died
I/ServiceManager( 213): service 'simphonebook' died
I/ServiceManager( 213): service 'iphonesubinfo' died
I/ServiceManager( 213): service 'isms' died
_I/ServiceManager( 213): service 'phone' died*
I/ServiceManager( 213): service 'sip' died
D/ConnectivityService( 932): unregisterNetworkFactory for Telephony
I/MmsServiceBroker( 932): MmsService unexpectedly disconnected
D/WifiService( 932): Client connection lost with reason: 4
I/Zygote ( 269): Process 26103 exited due to signal (11)
I/ActivityManager( 932): Process com.android.phone (pid 26103) has died
W/ActivityManager( 932): Scheduling restart of crashed service com.android.stk/.StkAppService in 1000ms
W/ActivityManager( 932): Scheduling restart of crashed service com.android.phone/.TelephonyDebugService in 0ms
W/ActivityManager( 932): Scheduling restart of crashed service com.android.mms.service/.MmsService in 11000ms
I/ActivityManager( 932): Start proc 26324:com.android.phone/1001 for restart com.android.phone
This is the output in adbi_example.log after launching ./hijack:
/Users/bran/Documents/adbi-master/instruments/example/jni/../epoll.c started hooking: epoll_wait = 0xb6e46f1d THUMB using 0xa47224f1
Any idea about how can I solve this?
Also, what's the difference between m.android.phone and com.android.phone?
Thank you
Hi,
I made some modifications to your tool. Bellow, the changelist
The fork is available here : https://github.com/Flo354/adbi
how can i hook c++ method in so library
nomally we can hook c method
but, it can not hook the c++ method( TEST::test() )
error message "cannot find function: Test::test"
how can i do?
Does this have Marshmallow support?
Success here:
./hijack -d -p 5660 -l libexample.so <
mprotect: 0xf74058a0
dlopen: 0xf765ac89
pc=f7405d40 lr=f73db4bb sp=ffc41348 fp=1c
r0=fffffffc r1=ffc413c8
r2=10 r3=ffffffff
stack: 0xff444000-0xffc43000 leng = 8384512
executing injection code at 0xffc412f8
calling mprotect
library injection completed!
root@flounder:/data/local/tmp #
nothing here:
root@flounder:/data/local/tmp # cat /proc/5660/maps | grep libexample
1|root@flounder:/data/local/tmp #
Too many memory mapping
cannot read memory map
can't find address of mprotect(), error!
Hi crmulliner and everyone,
I came across this error when hijacking an user app process. The target so library is right in the lib folder of the /data/data/ and the target function is an JNI function (aka Java_com___get_).
Anyone knows how to fix it? I tried it on several devices, all turned out the same error. Does the target app have some protection trick? Or hijack tool's issue?
Thanks in advance.
follow the doc to build hijack tool and it shows up on my mac osx 10.11.3
Is Instruction level instrumentation( e.g. done by Valgrind, PIN) supported with this tool on Android( ART or dalvik)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.