AFL基于代码插桩来生成测试用例,这样生成的样本就比较的好,而且针对 linux 做了许多性能优化使得速度也非常快。

AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。其工作流程大致如下:

①从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);

②选择一些输入文件,作为初始测试集加入输入队列(queue);

③将队列中的文件按一定的策略进行“突变”;

④如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;

⑤上述过程会一直循环进行,期间触发了crash的文件会被记录下来。

目录AFL状态窗口代表意义Fuzz网络程序 构建Modbus TCP Server 利用 preeny库 编译 modbus server 获取样本数据 使用获取的样本再次fuzz 总结AFL Persistent Mode总结

使用 afl 的常规步骤

  • 如果有源码,用 afl-gcc 或者 afl-clang-fast 编译源码,afl 会利用这些工具在编译期间对代码进行插桩,为后面的测试提供代码覆盖率,测试样本的变异则会基于代码覆盖率进行。无源码的话可以使用 qemu 进行插桩

  • 搜集好 初始样本集,如果必要的话使用 afl-cmin 把样本集进行精简。

  • 然后用 afl-fuzz 开始 fuzz

afl-cmin -i in/ -o out/ /path/to/program

in/ 是初始样本集目录

out/ 是 精简后的样本集存放的目录

afl-fuzz -i in/ -o out/ /path/to/program

in/ 是初始样本集目录

out/ 用于保存 fuzz 过程中的一些文件

afl-fuzz 默认是往 stdin 中写测试数据,它同时支持从文件喂 测试数据给目标程序,只要把设置文件的参数修改为 @@fuzz 过程中 afl-fuzz 会把它替换成 文件名。

比如 ./a 这个程序的 第二个参数是要处理的文件的名称, 那么相应的 afl-fuzz 的命令就是

afl-fuzz -i in/ -o out/ ./a arg1 @@

AFL状态窗口代表意义

① Process timing:Fuzzer运行时长、以及距离最近发现的路径、崩溃和挂起经过了多长时间。

② Overall results:Fuzzer当前状态的概述。

③ Cycle progress:我们输入队列的距离。

④ Map coverage:目标二进制文件中的插桩代码所观察到覆盖范围的细节。

⑤ Stage progress:Fuzzer现在正在执行的文件变异策略、执行次数和执行速度。

⑥ Findings in depth:有关我们找到的执行路径,异常和挂起数量的信息。

⑦ Fuzzing strategy yields:关于突变策略产生的最新行为和结果的详细信息。

⑧ Path geometry:有关Fuzzer找到的执行路径的信息。

⑨ CPU load:CPU利用率

因为afl-fuzz永远不会停止,所以何时停止测试很多时候就是依靠afl-fuzz提供的状态来决定的。

Fuzz 网络程序

这里以 libmodbus 这个库为目标进行 fuzz 。

构建 Modbus TCP Server

库的官网地址如下

http://libmodbus.org/documentation/

这是一个用于 modbus 通讯的库, 通过这个库可以很方便的实现 modbus 服务器 和 客户端。这里以 modbus tcp 的服务端作为 fuzz 的对象。

首先在官网下载好源码

http://libmodbus.org/releases/libmodbus-3.1.4.tar.gz

源码目录下的 tests 目录里面有一些示例程序, 其中 tests/bandwidth-server-one.c 就实现了一个 modbus tcp server

精简得到

#include #include #include #include #include 

int main(int argc, char *argv[]){  int s = -1;  modbus_t *ctx = NULL;  modbus_mapping_t *mb_mapping = NULL;  int rc;  int use_backend;  ctx = modbus_new_tcp("127.0.0.1", 1502);  s = modbus_tcp_listen(ctx, 1);  modbus_tcp_accept(ctx, &s);  mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,                                  MODBUS_MAX_READ_REGISTERS, 0);  if (mb_mapping == NULL) {      modbus_free(ctx);      return -1;  }  uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];  memset(query, 0, MODBUS_TCP_MAX_ADU_LENGTH);  rc = modbus_receive(ctx, query); // 获取客户端的请求数据  if (rc > 0) {      modbus_reply(ctx, query, rc, mb_mapping); // 处理并响应之  }  modbus_mapping_free(mb_mapping);  if (s != -1) {      close(s);  }  /* For RTU, skipped by TCP (no TCP connect) */  modbus_close(ctx);  modbus_free(ctx);  return 0;}

代码逻辑简单理一下

  • modbus_new_tcp 初始化 modbus_t 结构体

  • modbus_tcp_acceptmodbus_tcp_listen 就是调用 socket 监听端口

  • modbus_mapping_new 初始化一个缓冲区,用于模拟寄存器信息

  • 然后 modbus_receive 接收客户端的请求和输入

  • 获取输入后就 通过 modbus_reply 处理 请求,以及构造响应数据包, 同时返回响应

  • 然后就是释放掉分配的一些内存

利用 preeny 库辅助

afl 默认只能 fuzz 通过 stdin 和 文件 获取输入的程序, 要 fuzz 网络相关的程序,需要使用一个库

https://github.com/zardus/preeny

这个库利用 LD_PRELOAD 机制,重写了 很多库函数, 其中 desock.c 这个文件负责重写 socket 相关的函数,其实现的功能就是当应用从 socket 获取输入时,其实是从 stdin 获取输入。

首先下载编译下

git clone https://github.com/zardus/preeny.gitcd preeny/sudo apt-get install libseccomp-devmake

然后会在 x86_64-linux-gnu 目录下生成编译好的 lib

写个测试脚本,测试一下 (根据 tests 目录里面的 sock.c 改造)

#include #include #include #include 

int main(){  int s = socket(AF_INET, SOCK_STREAM, 0);  char buf[1024]={0};  char send_msg[] = "hello, send by send() :\n";  send(s, send_msg, strlen(send_msg), 0);  recv(s, buf, 1024, 0);  printf("recv from recv() : %s\n", buf);}编译运行

编译运行

gcc sock_test.c -o sock_testLD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" ./sock_test

╭─birdpwn@ubuntu ~/vuln╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" ./sock_testhello, send by send() :AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAArecv from recv() : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

socket 调用 send , 成功往 stdout 输出了 字符串。

stdin 输入 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ,可以看到成功写入 buf 里面

所以我们就可以利用 preenyfuzz modbus tcp server

编译 modbus server

首先使用 afl-gcc 编译 libmodbus ,对 libmodbus 插桩。

cd libmodbus-master/CC=afl-gcc CXX=afl-g++ ./configure --enable-staticmake -j4

--enable-static:用于生成静态库

然后在 src/.libs 下就可以看到编译好的库

─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4╰─$ ls src/.libs/libmodbus.a libmodbus.la libmodbus.lai libmodbus.so libmodbus.so.5 libmodbus.so.5.1.0 modbus-data.o modbus.o modbus-rtu.o modbus-tcp.o

libmodbus.a 就是编译好的静态库

然后使用我们修改过的 bandwidth-server-one.c 编译 和 fuzz

cd tests/vim bandwidth-server-one.cafl-gcc bandwidth-server-one.c -I../src ../src/.libs/libmodbus.a -o servermkdir inecho 11111 > in/1LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i in -o out ./server

这里 直接用 echo 生成了一个 测试文件,如果直接用这个去测的话会发现速度非常的慢。

─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ afl-gcc bandwidth-server-one.c -I../src ../src/.libs/libmodbus.a -o serverafl-cc 2.52b by afl-as 2.52b by [+] Instrumented 24 locations (64-bit, non-hardened mode, ratio 100%).╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ mkdir in╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ echo 11111 > in/1╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i in -o out ./serverafl-fuzz 2.52b by [+] You have 1 CPU core and 7 runnable tasks (utilization: 700%).[*] Checking core_pattern...[*] Setting up output directories...[*] Scanning 'in'...[+] No auto-generated dictionary tokens to reuse.[*] Creating hard links for all input files...[*] Validating target binary...[*] Attempting dry run with 'id:000000,orig:1'...[*] Spinning up the fork server...[+] All right - fork server is up.  len = 6, map size = 108, exec speed = 501074 us[+] All test cases processed.[!] WARNING: The target binary is pretty slow! See /usr/local/share/doc/afl/perf_tips.txt.[+] Here are some useful stats:  Test case count : 1 favored, 0 variable, 1 total      Bitmap range : 108 to 108 bits (average: 108.00 bits)      Exec timing : 501k to 501k us (average: 501k us)[*] No -t option specified, so I'll use exec timeout of 1000 ms.[+] All set and ready to roll!

这里 直接用 echo 生成了一个 测试文件,如果直接用这个去测的话会发现速度非常的慢。

获取样本数据

一组好的样本数据对 fuzzer 的影响还是非常大的,一般我们可以去网上搜索样本,比如图片,视频文件等。对于我们这次的目标 libmodbus , 它自带了很多的测试程序,我们可以利用这些测试程序测试,然后用 tcpdump 抓包, 最后在把其中的请求数据保存下来,作为测试样本集。

首先使用 random-test-server 在 127.0.0.1:1502 起一个 modbus tcp 服务

╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ ./random-test-server

然后开启 tcpdump , 保存数据包到 ~/modbus.pcap

─birdpwn@ubuntu ~╰─$sudo tcpdump -i lo -w ~/modbus.pcap[sudo] password for birdpwn:tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

最后使用 random-test-client 随机发送各种 modbus 请求到 127.0.0.1:1502

╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ ./random-test-clientConnecting to 127.0.0.1:1502[00][01][00][00][00][06][FF][05][00][00][FF][00]Waiting for a confirmation...<00><01><00><00><00><06><FF><05><00><00><FF><00>[00][02][00][00][00][06][FF][01][00][00][00][01]Waiting for a confirmation...

然后写一个脚本把 ~/modbus.pcap 中 由客户端发送的数据包 (也就是目的地为 127.0.0.1:1502的数据包) 的内容提取出来,每个数据包内容保存为一个单独的文件。

sudo apt-get install python-scapy

from scapy.all import *save_path = "/tmp/seeds/"uuid = 0

if not os.path.exists(save_path):  os.system("mkdir %s" %(save_path))

def save_to_file(data):  global uuid  with open("{}{}".format(save_path, uuid), "w") as fp:      fp.write(str(data))  uuid += 1  print "write test file: {}".format(uuid)

modbus_session = ''pg = rdpcap("modbus.pcap")session = pg.sessions()for k in session.keys():  if k.endswith("127.0.0.1:1502"):      modbus_session = session[k]

for s in modbus_session:  payload = s[TCP].payload  if len(payload) > 4:      save_to_file(payload)

print "Total: %d tests" %(uuid)

生成测试用例:

─birdpwn@ubuntu /tmp/seeds╰─$ ls0   120 143 166 189 210 233 256 279 300 323 346 369 391 413 436 459 481 503 526 549 571 594 616 639 661 684 706 729 751 774 797 819 841 864 887 909 931 954 9771   121 144 167 19   211 234 257 28   301 324 347 37   392 414 437 46   482 504 527 55   572 595 617 64   662 685 707 73   752 775 798 82   842 865 888 91   932 955 97810   122 145 168 190 212 235 258 280 302 325 348 370 393 415 438  460 483 505 528 550 573 596 618 640 663 686 708 730 753 776 799 820 843 866 889 910 933 956 979100 123 146 169 191 213 236 259 281 303 326 349 371 394 416 439 461 484 506 529 551 574 597 619 641 664 687 709 731 754 777 8   821 844 867 89   911 934 957 98101 124 147 17   192 214 237 26   282 304 327 35   372 395 417 44   462 485 507 53   552 575 598 62   642 665 688 71   732 755 778 80   822 845 868 890 912 935 958 980102 125 148 170 193 215 238 260 283 305 328 350 373 396 418 440 463 486 508 530 553 576 599 620 643 666 689 710 733 756 779 800 823 846 869 891 913 936 959 981103 126 149 171 194 216 239 261 284 306 329 351 374 397 419 441 464 487 509 531 554 577 6   621 644 667 69   711 734 757 78   801 824 847 87   892 914 937 96   982104 127 15   172 195 217 24   262 285 307 33   352 375 398 42   442 465 488 51   532 555 578 60   622 645 668 690 712 735 758 780 802 825 848 870 893 915 938 960 983105 128 150 173 196 218 240 263 286 308 330 353 376 399 420 443 466 489 510 533 556 579 600 623 646 669 691 713 736 759 781 803 826 849 871 894 916 939 961 984106 129 151 174 197 219 241 264 287 309 331 354 377 4   421 444 467 49   511 534 557 58   601 624 647 67   692 714 737 76   782 804 827 85   872 895 917 94   962 985107 13   152 175 198 22   242 265 288 31   332 355 378 40   422 445 468 490 512 535 558 580 602 625 648 670 693 715 738 760 783 805 828 850 873 896 918 940 963 986108 130 153 176 199 220 243 266 289 310 333 356 379 400 423 446 469 491 513 536 559 581 603 626 649 671 694 716 739 761 784 806 829 851 874 897 919 941 964 987109 131 154 177 2   221 244 267 29   311 334 357 38   401 424 447 47   492 514 537 56   582 604 627 65   672 695 717 74   762 785 807 83   852 875 898 92   942 965 98811   132 155 178 20   222 245 268 290 312 335 358 380 402 425 448 470 493 515 538 560 583 605 628 650 673 696 718 740 763 786 808 830 853 876 899 920 943 966 989110 133 156 179 200 223 246 269 291 313 336 359 381 403 426 449 471 494 516 539 561 584 606 629 651 674 697 719 741 764 787 809 831 854 877 9   921 944 967 99111 134 157 18   201 224 247 27   292 314 337 36   382 404 427 45   472 495 517 54   562 585 607 63   652 675 698 72   742 765 788 81   832 855 878 90   922 945 968112 135 158 180 202 225 248 270 293 315 338 360 383 405 428 450 473 496 518 540 563 586 608 630 653 676 699 720 743 766 789 810 833 856 879 900 923 946 969113 136 159 181 203 226 249 271 294 316 339 361 384 406 429 451 474 497 519 541 564 587 609 631 654 677 7   721 744 767 79   811  834 857 88   901 924 947 97114 137 16   182 204 227 25   272 295 317 34   362 385 407 43   452 475 498 52   542 565 588 61   632 655 678 70   722 745 768 790 812 835 858 880 902 925 948 970115 138 160 183 205 228 250 273 296 318 340 363 386 408 430 453 476 499 520 543 566 589 610 633 656 679 700 723 746 769 791 813 836 859 881 903 926 949 971116 139 161 184 206 229 251 274 297 319 341 364 387 409 431 454 477 5   521 544 567 59   611 634 657 68   701 724 747 77   792 814 837 86   882 904 927 95   972117 14   162 185 207 23   252 275 298 32   342 365 388 41   432 455 478 50   522 545 568 590 612 635 658 680 702 725 748 770 793  815 838 860 883 905 928 950 973118 140 163 186 208 230 253 276 299 320 343 366 389 410 433 456 479 500 523 546 569 591 613 636 659 681 703 726 749 771 794 816 839 861 884 906 929 951 974119 141 164 187 209 231 254 277 3   321 344 367 39   411 434 457  48   501 524 547 57   592 614 637 66   682 704 727 75   772 795 817 84   862 885 907 93   952 97512   142 165 188 21   232 255 278 30   322 345 368 390 412 435 458 480 502 525 548 570 593 615 638 660 683 705 728 750 773 796 818 840 863 886 908 930 953 976

使用获取的样本再次 fuzz

然后以生成的样本集作为初始样本集进行 fuzz

LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -i /tmp/seeds -o out ./server

刚开始一直报错

在一顿查找原因后,是配置有问题

╭─birdpwn@ubuntu ~/vuln/libmodbus-3.1.4/tests╰─$ LD_PRELOAD="/home/giantbranch/afl-2.52b/preeny/x86_64-linux-gnu/desock.so" afl-fuzz -t 100+ -i seeds -o out ./server export AFL_NO_FORKSRV=1                                                     1 ↵afl-fuzz 2.52b by [+] You have 1 CPU core and 7 runnable tasks (utilization: 700%).[*] Checking core_pattern...

总结

afl + preenyfuzz 网络应用 速度还行, 关键的还是要找到好的样本,从程序自带的测试用例中抓取也是一个不错的思路。

AFL Persistent Mode

在介绍一个 在 fuzz 一些网络程序时可能用到的特性, AFLpersistent 模式。

persistent 模式就是在程序的某个代码位置不断喂生成的变异数据进行 fuzz , 而不用每次喂数据都得重新 fork 一个程序。

要使用这个特性,首先得编译 llvm_mode

cd afl-2.52b/cd llvm_mode/makecd ..sudo make install

此时就会有 afl-clang-fastafl-clang-fast++ 两个命令, 要使用这个模式,就要用这两个命令来编译目标应用。

面还是用 afl 自带的 测试文件 experimental/persistent_demo/persistent_demo.c 来看看。

#include #include #include #include #include int main(int argc, char** argv) {char buf[100];while (__AFL_LOOP(1000)) {  memset(buf, 0, 100);  read(0, buf, 100);  if (buf[0] == 'f') {    printf("one\n");    if (buf[1] == 'o') {      printf("two\n");      if (buf[2] == 'o') {        printf("three\n");        if (buf[3] == '!') {          printf("four\n");          abort();        }      }    }  }} // end of while (__AFL_LOOP(1000))return 0;}

最关键的 就是 AFL_LOOP(1000) 这个宏, 其中的参数指定循环的次数。

每一次循环 afl 都会生成 测试数据,然后喂到 stdin , 这样 fuzzer 就可以在 AFL_LOOP 宏 包围的内部,通过 read(0,buf, size) 来获取测试数据,然后喂给目标程序的数据处理的代码,这样可以减少 fork 等操作的开销。

对应到上面的程序,就是 afl 会在

while (__AFL_LOOP(1000)) {................}

里面 fuzz 1000 次,即生成 1000 次测试数据, 然后会 return 0 . 程序结束,然后 afl 会重新起一个程序。继续这样的 fuzz .

while (__AFL_LOOP(1000)) 包围的代码,就是不断的 从 stdin 获取测试数据,然后进入下面的 if 判断逻辑。

编译 然后用 afl-fuzz

afl-clang-fast persistent_demo.c -o persistent_demoafl-fuzz -i in/ -o out/ ./persistent_demo

总结

如果使用 aflfuzz 网络应用,有两种方式

  • 利用 preeny 把从 socket 获取数据,转变为 从 stdin 获取数据

  • 利用 aflpersistent 模式

此外 , afl 还有各种扩展模式,比如 利用 qemu 可以无源码 fuzz。

c获取当前程序的路径_漏洞挖掘|使用AFL Fuzzing网络程序相关推荐

  1. js 获取上下文后面的路径_通过在数据后面显示上下文来可视化公众意见

    js 获取上下文后面的路径 In 1824, The Harrisburg Pennsylvanian, a newspaper from a town in Pennsylvania conduct ...

  2. 二进制漏洞挖掘_漏洞挖掘的艺术-面向二进制的静态漏洞挖掘

    本文首发于"合天智汇"公众号 作者: 萌新 0 本文是本系列的第二篇,将对面向二进制程序的静态漏洞挖掘技术进行介绍与分析. 面向二进制程序的静态漏洞的挖掘技术由于缺少源代码中的结构 ...

  3. 漏洞挖掘 符号执行_漏洞挖掘新思路:fuzz+符号执行——driller

    driller 符号执行+fuzz漏洞挖掘思路 先看一段程序 int main() { config_t* config=read_config(); if (config==null){ puts( ...

  4. 小程序项目实施_实施对关键业务IT应用程序的持续分析和管理

    在当今竞争激烈的商业环境中,组织必须能够快速有效地响应推动业务目标,优先级和要求发生变化的市场需求. 为了建立竞争优势,IT投资必须与这些目标和优先事项直接挂钩. 灵活,协作并集成到整个业务中的软件交 ...

  5. 漏洞 立即留言_漏洞挖掘小白入坑指南

    文章出处:FreeBuf 文章链接:http://www.freebuf.com/articles/neopoints/144967.html 写在前面的话在此之前,很多朋友都曾通过电子邮件在我直播的 ...

  6. 小程序 input自动换行_直播 | 最实用的微信小程序自动化测试技术独家揭秘

    随着微信小程序的功能和生态日益完善,很多企业级小程序项目的页面越来越多,业务逻辑也越来越复杂.如何做好微信小程序的自动化测试成为测试同学面临的一大难题. 如何合理构建多端架构和自动化测试体系? 如何深 ...

  7. 拼团小程序源码_带你了解拼团小程序

    随着智能手机的普及,移动互联网的快读发展,很多创业者抓住了社交电商的商机,2018年许多人通过微信公众号,微博,抖音等自媒体.流媒体迅速建立了流量入口.那么现在的社区团购系统又是怎样的呢? 一.拼团社 ...

  8. 超市微信小程序怎么做_新手如何制作一个超市小程序?能发挥什么作用?

    对于线下超市实体店来说,开发一个自己的社区超市小程序,能获得不少好处: (1)提升线下店铺曝光度 微信"附近的小程序"功能,会向用户展示其5公里内的开发了小程序的商家,也就是说,实 ...

  9. 根据id获取多维数组路径_程序员的进阶课-架构师之路(2)-数组

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37609579/article/ ...

最新文章

  1. c# 基于layui的通用后台管理系统_【SpringBoot】三十三、SpringBoot+LayUI后台管理系统开发脚手架...
  2. PHP-RSA加密跨域通讯实战
  3. @JsonProperty注解解析
  4. 从服务器请求文件流工具类,SpringBoot我想获取图片流,然后将图片流的数据传到服务器上...
  5. OpenCV基础篇之Mat数据结构
  6. 手把手教你用SPSS做出二元logistic回归分析
  7. “无线射频识别”可节省1/3物流成本
  8. 固态硬盘是什么接口_固态硬盘的各种接口
  9. 那些高中时曾经背得烂熟的古文(滕王阁序,阿房宫赋, 兰亭集序 , 师说,蜀道难 ...)再一次读读吧,慢慢的读,突然很想哭...有些岁月果真不曾忘怀
  10. html文本显示不完整,css 内容显示不全怎么办
  11. Nature Communications:使用连接组的嵌入向量表征映射大脑结构与功能之间的高阶关系
  12. 【论文笔记】Camera Style Adaption for Person Re-identification
  13. Lambda与Stream流
  14. 代理arp 无故arp 反向arp
  15. ASP.NET:母版页与内容页
  16. 【回忆】【Xposed】【Magisk】小米4c的ROOT,XP框架,Magisk,Viper4Android音效之路
  17. html星星坠落效果,抖音星球坠落特效怎么弄 摘下星星送给你
  18. 用Javascript开发《三国志曹操传》-开源讲座(三)-情景对话中,仿打字机输出文字
  19. 【论文详解】—AlphaGo中用到的3个关键技术
  20. 记事本中TAB键替换为空格键的方法

热门文章

  1. 学习总结——接口测试基础
  2. 关于2017年总结及2018年计划
  3. 理解Android安全机制
  4. oracle的一些学习
  5. C++ Notes(focus on c++)
  6. java 扫描包框架_java – 在Android中实现类似Spring的包扫描
  7. windows10 搜索桌面搜索功能失效的解决
  8. svn: 无法连接主机“127.0.0.1”: 拒绝连接
  9. hadoop错误总结
  10. MySQL设置或修改系统变量的几种方法