fox-it / log4j-finder Goto Github PK
View Code? Open in Web Editor NEWFind vulnerable Log4j2 versions on disk and also inside Java Archive Files (Log4Shell CVE-2021-44228, CVE-2021-45046, CVE-2021-45105)
License: MIT License
Find vulnerable Log4j2 versions on disk and also inside Java Archive Files (Log4Shell CVE-2021-44228, CVE-2021-45046, CVE-2021-45105)
License: MIT License
Add an optional parameter to exit with a error status when vulnerability is found.
This will make it easy to add this to automated pipelines.
Hi,
would it be possible to let the cmd stay open if users just double-click the exe? It will be usefull for non IT-people just to click, instead of open a cmd and start the *.exe form there :).
Like here:
vercel/pkg#644
We would link this (fantastic tool) and gave that link to our end users for home use. Most of them are non IT-people.
cheers
Niko
Under which License the code is published?
Online documentation could use a parameter list with their uses.
It mentions log2j-finder.py, while only log4j-finder.py is present.
Greetings from Primevision across the road
Apache Nifi uses a proprietary format to package their jar/war files, and decided to call this .nar.
Please include in scannable file extensions.
By default, the scanner uses "/" as the starting point. Under Windows, os.scandir
assumes "/" as the root of the system partition, i.e. other partitions are not scanned by default.
Your doco says python 3.6+ but you're using zipfile.Path that wasn't introduced until 3.8. Just FYI. So this can't be used on any RHEL boxes without custom python installs.
While I appreciate your team's efforts, thought I'd pass on our feedback. When we ran the Log4j-finer.exe on a Windows 10 machine, first we noticed that windows smart screen filter warning popped up waring me even though the app is supposed to be signed? Then the app went into scanning mode for about 5 minutes, then Dropbox suddenly just opened up by itself, and stated that it was syncing, and then downloading a file, again, all by itself? This has never happened before on this machine. I forgot that dropbox was even on this machine. I went and killed the Log4j-finder.exe and the 3 spawned Dropbox instances that are not setup to start with the OS. Hoping nothing jumped the shark here that could affect our network. Thanks...Howie
Unless I have misunderstood, this vulnerability affects all versions of log4j from 2.0-beta9 to 2.14.1. I have scanned a directory that contains 2.0.2 and it didn't show up, presumably as there isn't a hash for it. I can supply a hash or a PR if needed.
Hello,
Thank you so much for providing the python script and binary.
Are you able to release a new binary and signed version since the log4j version 2.17.1 is not recognized as clean ?
Thank you !
I found out that repackaging JAR files and renaming them is a thing in Java land. For example the following elasticsearch APM package is vulnerable but not picked up by log4j-finder:
Advisory: https://discuss.elastic.co/t/apache-log4j2-remote-code-execution-rce-vulnerability-cve-2021-44228-esa-2021-31/291476
Package file: https://github.com/elastic/apm-agent-java/releases/v1.28.0/
When looking into this Zip file (which log4j-finder now supports btw), we can find the following file:
Because the extension is esclazz
instead of class
, this file is not checked. However the MD5 is also not known, most likely because everything was recompiled from source so it doesn't match anything from known good or bad.
There are several things I want to tackle in this issue:
With the assumption that the filename (without extension) should always stay the same as the Java class name, we can just check for "JndiManager.*", or any other ClassNames we want to check for that matter. So basically ignore the file extension.
This is not difficult to achieve, we could change FILENAMES to have glob patterns, like JndiManager.*
or maybe even better just change FILENAMES to CLASS_NAMES, and only contain the class name(s), such as JndiManager
. My preference would be the latter.
It then shows up as:
[2021-12-18 21:14:27] Scanning: apm
[2021-12-18 21:15:03] UNKNOWN: elastic-apm-java-aws-lambda-layer-1.28.0.zip -> elastic-apm-agent-1.28.0.jar -> agent/org/apache/logging/log4j/core/net/JndiManager.esclazz [fbe4c72a5c9e17b3728f62e64778c2ac: Unknown MD5]
[2021-12-18 21:14:27] UNKNOWN: apm/elastic-apm-agent-1.28.0.jar -> agent/org/apache/logging/log4j/core/net/JndiManager.esclazz [fbe4c72a5c9e17b3728f62e64778c2ac: Unknown MD5]
[2021-12-18 21:14:27] Finished scan, elapsed time: 0.12 seconds
I will probably refactor the code soon to just look for ClassNames
in the filename (so file extension agnostic)
log4j-finder could maybe also read and parse pom.xml
and METADATA.MF
files when it finds them, to determine versions used in the Jar file.
If have checked how most packages return their versions, and they seem to all get from that metadata files instead of returning a string from their own codebase itself.
This might be a bit more work and would get a bit messy with the current code base so a refactor of some functions might be better to suit this functionality.
Can we somehow use this to remotely scan (internal) servers? Like foreach server in .txt or from an AD filter?
@yunzheng : you use standard path libraries for the lookups of files in zip files: this approach is incorrect.
On windows the Exception will always be hit becasue the path for lookup of the class gets passed in the wrong format:
'org\\apache\\logging\\log4j\\core\\lookup\\JndiLookup.class'
rather than
'org/apache/logging/log4j/core/lookup/JndiLookup.class'
And since you have defaulted to setting has_lookup = False, VULNERABLE jars are being labeled PATCHED.
This is very bad if people using windows are to rely on your tool...
The behavior is inconsistent across your codebase... this is OK:
Furtheron it is not OK:
Given that the removal of problematic Java classes from files is trivial, it would very much improve this software if it added an option to do so.
zip -q -d $myfile org/apache/logging/log4j/core/net/JndiLookup.class
zip -q -d $myfile org/apache/logging/log4j/core/net/JndiManager.class
Hi
I found following string not reported by log4j-finder.
... /xxx/lib/log4j-1.2.15.jar
please consider to catch log4j 1.x jar also.
Thanks
Hi,
One of my servers spits out an error.
I've replaced the servername and username.
My first wild guess is that the path is too long and the application can't handle that. But that's just a wild guess on the first glance at the path.
Do note that the broken lines is copied as-is from the output I got.
Traceback (most recent call last):
+ CategoryInfo : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
+ PSComputerName : _<servername>_
Traceback (most recent call last):
+ CategoryInfo : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
+ PSComputerName : _<servername>_
File "log4j-finder.py", line 298, in <module>
File "log4j-finder.py", line 298, in <module>
File "log4j-finder.py", line 264, in main
File "log4j-finder.py", line 264, in main
File "pathlib.py", line 1193, in open
File "pathlib.py", line 1193, in open
File "pathlib.py", line 1046, in _opener
File "pathlib.py", line 1046, in _opener
FileNotFoundError
FileNotFoundError
:
:
[Errno 2] No such file or directory: 'C:\\Documents and Settings\\_<username>_\\AppData\\Local\\Application Data\\Application
Data\\Application Data\\Application Data\\Application Data\\Application Data\\Application Data\\Application Data\\Appli
cation Data\\Temp\\tmphc250ssu\\org\\apache\\logging\\log4j\\core\\net\\JndiManager.class'
[Errno 2] No such file or directory: 'C:\\Documents and Settings\\_<username>_\\AppData\\Local\\Application Data\\Application Data\\Application Data\\Application Data\\App
lication Data\\Application Data\\Application Data\\Application Data\\Application Data\\Temp\\tmphc250ssu\\org\\apache\\logging\\log4j\\core\\net\\JndiManager.class'
NotSpecified: (:) [], RemoteException
NotSpecified: (:) [], RemoteException
[
[
3189
3189
7
7
2] Failed
2] Failed
NotSpecified: (:) [], RemoteException
NotSpecified: (:) [], RemoteException
to execute
to execute
NotSpecified: (:) [], RemoteException
NotSpecified: (:) [], RemoteException
script l
script l
o
o
g4j-finder
g4j-finder
As said in the title, the github aciton packaging the py-file didnt finish and/or release the files.
Hello,
I have this message :
DEBUG Got <ZipInfo filename='js-sources/tinymce_3.5.9.zip' compress_type=deflate filemode='-rw-rw-r--' file_size=1449915 compress_size=646119>: Bad magic number for central directory, falling back to BytesIO
Traceback (most recent call last):
File "log4j-finder.py", line 371, in <module>
File "log4j-finder.py", line 345, in main
File "log4j-finder.py", line 173, in iter_jarfile
File "log4j-finder.py", line 173, in iter_jarfile
File "log4j-finder.py", line 158, in iter_jarfile
File "zipfile.py", line 1225, in __init__
File "zipfile.py", line 1310, in _RealGetContents
ValueError: negative seek value -27762772
[9308] Failed to execute script log4j-finder
I tried to exclude the directory or file but nothing works.
[root@host tmp]# mount | grep /tmp
/dev/mapper/VolGroup01-tmp on /tmp type xfs (rw,nosuid,nodev,noexec,relatime,seclabel,attr2,inode64,noquota)
[root@host tmp]# /srv/log4j-finder
/srv/log4j-finder: error while loading shared libraries: libz.so.1: failed to map segment from shared object: Operation not permitted
[root@host tmp]# mount /tmp -o remount,exec
[root@host tmp]# mount | grep /tmp
/dev/mapper/VolGroup01-tmp on /tmp type xfs (rw,nosuid,nodev,relatime,seclabel,attr2,inode64,noquota)
[root@host tmp]# /srv/log4j-finder
__ _____ __ ___ __ __
| |.-----.-----.| | ||__|______.' _|__|.-----.--| |.-----.----.
| || _ | _ ||__ | |______| _| || | _ || -__| _|
|__||_____|___ | |__|| | |__| |__||__|__|_____||_____|__|
|_____| |___| v1.2.0 https://github.com/fox-it/log4j-finder
[2021-12-30 15:29:02] host Scanning: /
^C
Aborted!
this is not expected, especially not from a security scan utility, as noexec is set with a reason on /tmp mount point. It would be nice to provide a different tmp dir for the binary.
This tool will be popular and live for a long time.
Please add -V to report version information(with cool banner) only.
or may just add version info from --help output but it doesn't have cool banner.
I would like to report an issue on Windows, regarding the scanning of very large zip files.
This seems to be problematic for very large zip files with sizes around 2-4GB containing a large amount of files and/or a very large jar file.
The tools seems to be scanning for a couple of hours (on this zip file alone), after which I manually abort.
When extracted the tool does not have a problem with scanning the jar file(s).
All the zips are oracle related software.
As a workaround I use the -e (exclude) option to exclude these very large zip files.
I just wanted to tip you on this issue.
Perhaps you can add an option: do not scan zip files and/or disable the scanning of very large zip files.
I appreciate all the good work you guys have done.
Hi, is there a possibility to exclude certain directories from the search? Is it possible to start the tool via an exclude directive?
regards
Michael
can you create a new binary for Linux with the update that 2.15 is vulnerable (newly found CVE)? Should only be 2.16 and up now
I think the documentation should explicitly mention that only JAR, EAR and WAR files are scanned.
For example, often a rolled out software comes in the form of a ZIP file or RAR, TAR, TAR.GZ (TGZ), TAR.BZ2 or platform-specific files types like an extractable EXE or MSI files.
Other programming languages use ZIP the file format but with a different suffix as an archive format as well, e.g. Python uses the WHL suffix.
Such archive files might contain Java classes as well, probably wrapped in *.JAR archives inside the outer archive.
These file types will not be scanned and I think this should be mentioned.
Because when created with 3.9 or 3.10 the binary will not work, and result in "api-ms-win-core-path-l1-1-0.dll is missing"
It says so on the download page of python.org, but it can't hurt to mention it.
(lower is obviously also possible, but 3.8.10 is easiest to use in this case)
Would you mind adding the instruction on how windows and Linux got generated ?
[me@rocky8t01 log4j-finder]$ sudo ./dist/log4j-finder -bq /tmp
$HOSTNAME [shorter timestamp ] VULNERABLE: /tmp/apache-log4j-2.8-bin/log4j-core-2.8.jar -> org/apache/logging/log4j/core/net/JndiManager.class [415c13e7c8505fb056d540eac29b72fa: log4j 2.7 - 2.8.1]
[me@rocky8t01 log4j-finder]$
[me@rocky8t01 log4j-finder]$ sudo ./dist/log4j-finder -bq /tmp
usage: log4j-finder [-h] [-v] [-n] [-b] [PATH ...]
log4j-finder: error: argument -b/--no-banner: ignored explicit argument 'q'
[me@rocky8t01 log4j-finder]$ sudo ./dist/log4j-finder -b /tmp
[2021-12-16 20:55:05.573146] Scanning: /tmp
[2021-12-16 20:55:05.596662] VULNERABLE: /tmp/apache-log4j-2.8-bin/log4j-core-2.8.jar -> org/apache/logging/log4j/core/net/JndiManager.class [415c13e7c8505fb056d540eac29b72fa: log4j 2.7 - 2.8.1]
[2021-12-16 20:55:05.679576] Finished scan, elapsed time: 0.11 seconds
Summary:
Processed 291 files and 23 directories
Scanned 44 files
Found 1 vulnerable files
Elapsed time: 0.11 seconds
[me@rocky8t01 log4j-finder]$
Looking to somehow run this EXE on all our Windows systems remotely and then somehow aggregate all the results and parse looking for findings.. Any work on possibly implementing this? Thanks for all the hard work!
Hi,
It would be nice if you include instructions on how to download the file, for example with wget
.
Also, when I run the file, I see at the end "Summary:" and nothing after it. Maybe there should be some indication that no files were found.
Thanks,
Uri.
on RHEL6 I see:
[22407] Error loading Python lib '/tmp/_MEI7VUFgo/libpython3.7m.so.1.0': dlopen: /lib64/libc.so.6: version 'GLIBC_2.14' not found (required by /tmp/_MEI7VUFgo/libpython3.7m.so.1.0)
When I compile and install glibc-2.14 manually and export as an env variable, I get:
export LD_LIBRARY_PATH=/var/opt/glibc-2.14/lib && /tmp/log4j-finder -b
Traceback (most recent call last):
File "log4j-finder.py", line 29, in <module>
File "/root/.pyenv/versions/3.7.5/lib/python3.7/site-packages/PyInstaller/loader/pyimod03_importers.py", line 623, in exec_module
File "platform.py", line 116, in <module>
File "/root/.pyenv/versions/3.7.5/lib/python3.7/site-packages/PyInstaller/loader/pyimod03_importers.py", line 623, in exec_module
File "subprocess.py", line 153, in <module>
ImportError: /var//opt/glibc-2.14/lib/libc.so.6: version `GLIBC_2.15' not found (required by /tmp/_MEIWmgHt6/select.cpython-37m-x86_64-linux-gnu.so)
You guessed it, so I build and compiled glibc-2.15 too, and then it gives again the first error.
What I don't understand is that seemingly it's not using these glibc versions:
objdump -T /tmp/log4j-finder
/tmp/log4j-finder: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __strcat_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 getenv
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __snprintf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 raise
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 free
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __vfprintf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 unlink
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strncpy
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strncmp
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strcpy
0000000000000000 DF *UND* 0000000000000000 inflate
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mkdir
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 ferror
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fread
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mbstowcs
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 setenv
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 getpid
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fclose
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 opendir
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 stpcpy
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 rmdir
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strlen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 setbuf
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strchr
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dirname
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 memset
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strnlen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strncat
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.4 __realpath_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 closedir
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __strdup
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 wcsncpy
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 calloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strcmp
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 signal
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 ftell
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 feof
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 clearerr
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3 realpath
0000000000000000 DF *UND* 0000000000000000 inflateEnd
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 kill
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __xpg_basename
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fileno
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __stpcpy_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __xstat
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 readdir
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dlopen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 malloc
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fflush
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 nl_langinfo
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fseek
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __vsnprintf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __strncpy_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __strcpy_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 mkdtemp
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 setlocale
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fchmod
0000000000000000 DF *UND* 0000000000000000 inflateInit_
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 waitpid
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fopen
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 perror
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strtok
0000000000000000 w D *UND* 0000000000000000 _Jv_RegisterClasses
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 unsetenv
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 execvp
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 strcat
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fwrite
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __fprintf_chk
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.3.4 __strncat_chk
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dlsym
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 fork
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 dlerror
0000000000607a40 g DO .bss 0000000000000008 GLIBC_2.2.5 stdout
0000000000607a48 g DO .bss 0000000000000008 GLIBC_2.2.5 stdin
0000000000607a50 g DO .bss 0000000000000008 GLIBC_2.2.5 stderr
Could you compile the binary also for older versions of glibc, like 2.12? Or perhaps there's some option that does something dynamic, I'm no expert.
I know that acces time of a file only gives a mere indication but ik could help a little bit. On linux (and aix) you could use the touch command to save the timestamp in a temporary file. And restore it afterwards.
like:
save_command = "touch -r '{}' /tmp/timestamp.save"
rest_command = "touch -r /tmp/timestamp.save '{}'"
run(save_command.format(<jar_file>))
<do the check>
run(rest_command.format(<jar_file>))
I tried to patch the code with this but I can't. And I don't really now if it is a good idea to do that anyway
FYI i wrote a run function in python.
Kaspersky Anti-Virus found this on .exe file
Type: Cheval de Troie
Nom: Trojan-Downloader.Win32.Bitser.cue
Exactitude: Exactement
Niveau de menace: Élevé
Type d'objet: Fichier
Nom de l'objet: log4j-finder.exe
Chemin de l'objet: ***********
MD5: 0E08555A17CA3E08D02DBD4CFB87C1AA
Raison: Consigné dans le rapport
Kind regards.
Jipi69
Hello Team,
Thanks for developing this tool. I was trying to run it on my PC(windows), just installed python and ran it as explained by you, however, I am seeing a lot of access denied(even after opening cmd as admin and running if from there).
Is there a way to give it privilges?
We are using logpresso scanner and it seems to be flagging a lot more files with potential vulnerability after log4j 1.x was added to the CVEs.
log4j-finder is however skipping those files entirely and not flagging anything.
I am curious as to which one is reliable and why is it that log4j-finder thinks that this one is not potentially vulnerable.
Attaching a file for reference
log4j.jar.zip
.
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.