# free5GC Project 1 free5GC_CTF WriteUp
URL: https://hackmd.io/@n0ball/SyyFieepxl
[ToC]
## 介紹
在介紹整個 Project 的 Writeup 過程中,很多東西如果再插進去介紹基本上會造成整個講解流程的不順利,因此我們這邊就先做基本的介紹,然後再詳細描述怎麼拿到 `FLAG` 的。由於在嘗試第三個 `Flag` 的時候,實在嘗試了太多方法了,更還有用到 `EAP_AKA_PRIME` 的驗證方式,因此**我用的 `UERANSIM` 版本是 `85a0fbf`**,因此有可能 **`diff` 會跟最新的版本有不同的結果**,請再注意。以下報告都有請 Perplexity 進行潤飾,撰寫或產圖。
首先,先給個圖,這是我請 Perplexity 生的,Prompt 是 `請根據 github 上面 free5gc 的 source code 生出一張從 UE 開始接入核網到連接DN的流程圖`。雖然少了跟 `PLMN` 的檢查跟`gNB` 交互的 `RCC` 階段。但由於這次的 `Project` 用不到這些流程,其他看起來沒什麼大差異,因此可以用此圖定義一些階段與每個階段在做甚麼。

Perplexity 也順便生出了以下內容可供參考。(只擷取完成 `Project` 需要知道的流程)
註冊階段(Registration Procedure)
初始連接建立
註冊階段是 UE 接入 5G 網絡的第一步,相當於 4G 網絡中的 Attach 流程。UE 首先與 gNB 建立 RRC 連接,然後發起註冊請求。
步驟 1-2:註冊請求傳輸
UE 通過 Uu 介面向 gNB 發送包含註冊類型、SUCI/GUTI、安全參數和請求的 NSSAI 等資訊的註冊請求。gNB 隨後通過 N2 介面將此請求轉發給 AMF,同時包含 UE Policy Container 和其他 N2 參數。
AMF 選擇與身份驗證
步驟 3-4:身份驗證流程
AMF 接收到註冊請求後,會基於 SUPI 選擇合適的 AUSF 進行身份驗證。AMF 通過 N12 介面向 AUSF 發送 Nausf_UEAuthenticate_authenticate Request,AUSF 再透過 N13 介面與 UDM 交互,獲取身份驗證向量和密鑰材料。
步驟 5-6:安全模式建立
完成身份驗證後,AMF 通過 N1 介面向 UE 發送 Security Mode Command,建立安全上下文。UE 確認後回應 Security Mode Complete 訊息。
訂閱數據獲取與策略控制
步驟 7:UDM 註冊
AMF 通過 N8 介面向 UDM 發送 Nudm_UECM_Registration Request,註冊 UE 並獲取訂閱數據。UDM 會儲存與接入類型相關的 AMF 身份資訊。
步驟 8:策略關聯
AMF 通過 N15 介面向 PCF 發送 Npcf_AMPolicyControl_Create Request,建立接入移動策略關聯,獲取 QoS 策略和移動限制等策略規則。
步驟 9:註冊完成
AMF 向 UE 發送 Registration Accept 訊息,包含分配的 5G-GUTI、配置的 NSSAI 和 TAI 清單。UE 回應 Registration Complete 確認註冊完成。
## Challenges
### 1 Registration Accept (10 pt)
FLAG: `free5GC_CTF{Welc0me_to_freeFiveGcWorld}`

#### Introduction
SQN 全稱為 Sequence Number(序列號),根據 3GPP TS 33.102 和 TS 33.501 規範定義。它是用於身份驗證過程中的一個遞增計數器。
主要作用
- 防止重放攻擊(Replay Protection)
- 確保身份驗證的新鮮性(Freshness)
- 提供 UE 與網絡間的同步機制
- 支援網絡對 UE 的身份驗證
由於每次溝通都會新增 SQN 所以有一個 Hard limit 限制他不會過大不然有可能會 `OverFlow`。
另外再介紹 這個 SQN 後面會成為身分驗證的一個很重要的條件(雖然這個 Project 用不到,但是由於在找第三個 `Flag` 的時候有研究了一下,因此寫在這裡。
```
AUTN = SQN ⊕ AK || AMF || MAC
其中 MAC = f1_K(SQN || RAND || AMF)
```
#### Steps
參考 `webconsole` 顯示的資訊進行修改 `config/free5gc-ue.yaml` 即可

```diff
diff --git a/config/free5gc-ue.yaml b/config/free5gc-ue.yaml
index 6172db4..b0adf9d 100644
--- a/config/free5gc-ue.yaml
+++ b/config/free5gc-ue.yaml
@@ -1,5 +1,5 @@
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 digits)
-supi: 'imsi-208930000000001'
+supi: 'imsi-208930000007487'
# Mobile Country Code value of HPLMN
mcc: '208'
# Mobile Network Code value of HPLMN (2 or 3 digits)
@@ -14,9 +14,9 @@ homeNetworkPublicKeyId: 1
routingIndicator: '0000'
# Permanent subscription key
-key: '8baf473f2f8fd09487cccbd7097c6862'
+key: '5122250214c33e723a5dd523fc145fc0'
# Operator code (OP or OPC) of the UE
-op: '8e27b6af0e692e750f32667a3b14605d'
+op: '981d464c7c52eb6e5036234984ad0bcf'
# This value specifies the OP type and it can be either 'OP' or 'OPC'
opType: 'OPC'
# Authentication Management Field (AMF) value
@@ -60,7 +60,7 @@ configured-nssai:
# Default Configured NSSAI for this UE
default-nssai:
- sst: 1
- sd: 1
+ sd: 0x010203
# Supported integrity algorithms by this UE
integrity:
```
#### Problems Encounter
會看到 `SQN out of range`,可以看出來是太大,因此修改一下即可。但是看起來 `AMF` 不會檢查,而是報其他 `Error` ,但也不會跟 `UE` 回報,可能可以再研究發生甚麼事,說不定會有邏輯漏洞,有機會可以找出 `Zeroday` 也說不定。

基本上 Trace 一下 `./src/ue/nas/mm/auth.cpp:493: bool sqn_ok = m_usim->m_sqnMng->checkSqn(receivedSQN);` 和 `./src/ue/nas/usim/sqn_mng.cpp:46:bool SqnManager::checkSqn(uint64_t sqn)`
:::spoiler
```cpp=46
bool SqnManager::checkSqn(uint64_t sqn)
{
uint64_t seq = getSeqFromSqn(sqn);
uint64_t ind = getIndFromSqn(sqn);
if (seq - getSeqMs() > m_wrappingDelta)
return false;
if (seq <= getSeqFromSqn(m_sqnArr[ind]))
return false;
m_sqnArr[ind] = sqn;
return true;
}
```
:::
可以看出有兩個範圍檢查
1. Wrapping Delta (`m_wrappingDelta`)
2. 歷史順序
又根據 `./src/ue/nas/usim/usim.cpp:18: m_sqnMng = std::make_unique<SqnManager>(5ull, 1ull << 28ull);` 可以看出 `m_wrappingDelta` 的值是 `1ull << 28ull` 所以 Sqn 應該 (getSeqMs() 推測可能是最初始的 SQN 如果是初始的話就是0) `0x0fffffff`。反正知道找一個夠小的就可以了,我就把前面的數字都設定`0`。
### 2 PDU Session Establishment Reject with cause Insufficient resources for specific slice and DNN (20 pt)
FLAG: `free5GC_CTF{InsuFficientRes0urcesForSpecificSLIce4nDdnn}`

#### Steps
由於之前的 `Lab` 做過的時候,沒有看 GUI 給的 `Error` 就直接按 `OK` 所以本來就有遇過這個問題,因此馬上就知道怎麼複現。只要在 `Static IP` 放上一個不在 `Static Pools` (amfcfg.yaml) 裡面指定的範圍就會出現。這邊一樣不要管這個提示 按下 `Update` 就好。聽起來也蠻符合題目裡說的 **直接在DB裡面修改,沒有透過 webconsole 幫你驗證。** (但是好像也沒真的驗證就是了。)

### 3 Authentication Failure (20 pt)
FLAG: `free5GC_CTF{4uthent1CatIon_fa11ure}`

:::spoiler
~~這個是我花最久時間達成的不得不說題目敘述跟提示真的給太不好了。由於可以 Authentication Failure 的地方實在是太多了,因此嘗試了各種可能的方法,最後還是靠著逆向原本的程式碼,去看附近有甚麼會印出來的東西,猜通靈出這個東西QQ~~

:::
#### Introduction
**Integrity** 和 **Ciphering** 是 5G 網絡安全的兩個核心機制,它們在 free5GC 的安全模式建立階段扮演關鍵角色。
Integrity Protection
完整性保護確保訊息在傳輸過程中沒有被篡改或偽造。
它的運作方式是:
1. 發送端使用特定算法計算訊息的 MAC(Message Authentication Code)
2. 將 MAC 附加到原始訊息一起傳送
3. 接收端收到後重新計算 MAC,並與接收到的 MAC 比對
4. 如果 MAC 匹配,證明訊息完整無損;不匹配則拒絕該訊息
Ciphering
加密確保訊息的機密性,即使被第三方截取也無法讀取明文內容。運作方式:
它的運作方式是:
1. 發送端使用密鑰和算法將明文轉換成密文
2. 傳輸密文到接收端
3. 接收端使用相同密鑰和算法解密還原明文
以下為 `Perplexity` 產生的流程圖。

根據[這篇文章](https://nuradiocncpts.wordpress.com/2024/04/04/5g-authentication-security/)
在 `free5GS` 裡面我相信 `free5GC` 裡面也差不多。
完整性保護算法(NIA)
- NIA0:無保護(僅緊急通話使用)
- 128-NIA1:基於 SNOW 3G 的完整性算法
- 128-NIA2:基於 AES-CMAC 的完整性算法
- 128-NIA3:基於 ZUC 的完整性算法
加密算法(NEA)
- NEA0:無加密(測試用途)
- 128-NEA1:基於 SNOW 3G 的流密碼
- 128-NEA2:基於 AES-CTR 模式的加密
- 128-NEA3:基於 ZUC 的流密碼
AMF 可以根據 UE 的選則應該用甚麼方式來傳輸訊息。值得注意的是 `NIA0 && NEA0` 一起使用的話只有某些特定的訊息才可以。
>Messages that can be sent without security protection
>- MIB
>- Paging
>- RRCReconfiguration*
>- RRCReconfigurationComplete*
>- RRCReject
>- RRCRelease
>- RRCSetup
>- RRCSetupComplete
>- RRCSetupRequest
>- SIB1
>- Security Mode Command (Integrity protected but not ciphered)
>- Security Mode Failure
>- SystemInformation
>- UECapabilityEnquiry
>- UECapabilityInformation
也可以從 `UE` 的 `log` 看出目前在使用甚麼加密/驗證方式。

#### Steps
到這邊要怎麼解題的方式就很清楚了(前提是知道要複現的是 NAS MAC 錯誤)。稍微對應一下流程 `Security Mode Command` 之後的訊息是一定要選一個不是`IA0`的驗證方式。而且透過 `free5GC` github 上的 `source code (config/amfcfg.yml)` 可以知道,預設就只能提供 `NIA2` 這一種做法。
```yaml=61
# NAS Security Configuration
# the priority of integrity algorithms
# the priority of ciphering algorithms
security:
integrityOrder:
- NIA2
# - NIA0
cipheringOrder:
- NEA0
- NEA2
```
也可以透過修改 `UE` 的 `config` 得到驗證
```diff
diff --git a/config/free5gc-ue.yaml b/config/free5gc-ue.yaml
index 6172db4..524ed89 100644
--- a/config/free5gc-ue.yaml
+++ b/config/free5gc-ue.yaml
# Supported integrity algorithms by this UE
integrity:
IA1: true
- IA2: true
+ IA2: false
IA3: true
```

因此只要強制讓需要 `Integrity` 的時候讓 `IA` 強制是一個不可能的答案就行。
```diff
diff --git a/src/ue/nas/mm/messaging.cpp b/src/ue/nas/mm/messaging.cpp
index 3ac2807..0a27775 100644
--- a/src/ue/nas/mm/messaging.cpp
+++ b/src/ue/nas/mm/messaging.cpp
@@ -174,6 +174,7 @@ EProcRc NasMm::sendNasMessage(const nas::PlainMmMessage &msg)
}
else
{
+ m_usim->m_currentNsCtx->integrity = nas::ETypeOfIntegrityProtectionAlgorithm::IA1_128;
auto encrypted = nas_enc::Encrypt(*m_usim->m_currentNsCtx, msg, false, false);
nas::EncodeNasMessage(*encrypted, pdu);
}
```
### 4 AMF: NGAP ID not found (25 pt)
FLAG: `free5GC_CTF{4MF:Ng4p1dN0tF0uNd}`

#### Introduction
根據 `Perplexity`
NGAP ID 的傳輸時機
重要澄清:UE 本身並不直接傳輸 NGAP ID。NGAP ID 是在 gNB 和 AMF 之間的 N2 介面上使用的標識符,用於在 NGAP 信令中識別特定的 UE 上下文。
NGAP ID 的類型和分配
1. RAN UE NGAP ID
分配者:gNB (NG-RAN 節點)
用途:在 NG 介面上唯一識別該 gNB 內的特定 UE
首次傳輸時機:Initial UE Message
2. AMF UE NGAP ID
分配者:AMF
用途:在 NG 介面上唯一識別該 AMF 內的特定 UE
首次傳輸時機:AMF 對 Initial UE Message 的首次回應(如 Downlink NAS Transport)
詳細傳輸流程
階段 1:初始接觸(Initial UE Message)
當 UE 發起註冊請求時,傳輸順序如下:
UE → gNB:UE 透過 RRC Setup Complete 傳送 Registration Request(NAS 層訊息)
gNB → AMF:gNB 將此請求封裝在 Initial UE Message 中,首次分配並包含 RAN UE NGAP ID
text
Initial UE Message 內容:
├── RAN UE NGAP ID: 16798102 ← gNB 分配的 ID
├── NAS-PDU: Registration Request
├── User Location Information
└── RRC Establishment Cause
階段 2:AMF 回應
AMF → gNB:AMF 在首次回應(如 Downlink NAS Transport)中分配並包含 AMF UE NGAP ID
text
Downlink NAS Transport 內容:
├── AMF UE NGAP ID: 100 ← AMF 新分配的 ID
├── RAN UE NGAP ID: 16798102 ← 引用 gNB 的 ID
└── NAS-PDU: Authentication Request
階段 3:後續信令
所有後續 NGAP 信令:都必須同時攜帶這兩個 ID 來維持 UE 上下文的關聯。
#### Steps
只要進行一次UE的 `Release` 讓 AMF 釋放該 NGAP ID 就可以。
:::warning
此部分為後面補充 不在原本繳交作業內容
我的作法很簡單,在 `Registration` 結束前,送一個Failure就可以,因為 AMF 釋放 NGAP ID,但是 UE 仍舊繼續傳遞訊息,因此會出現 NGAP ID Not Found,AMF以為 MAC 沒過,所以沒有記起來。
```diff
diff --git a/src/ue/nas/mm/auth.cpp b/src/ue/nas/mm/auth.cpp
index c670185..f1132ef 100644
--- a/src/ue/nas/mm/auth.cpp
+++ b/src/ue/nas/mm/auth.cpp
@@ -304,6 +304,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest &
}
// =================================== Check the received ngKSI ===================================
+ sendFailure(nas::EMmCause::MAC_FAILURE);
if (msg.ngKSI.tsc == nas::ETypeOfSecurityContext::MAPPED_SECURITY_CONTEXT)
{
```
:::
### 5 UE Unexpected Release (25 pt)
FLAG: `free5GC_CTF{N0_F0LLOW0N_SENdUeCTXRelcMD}`

#### Introduction
根據 TS 23.502 (4.2.6) 和 TS 38.413 (8.3.3),AMF 收到 Registration Complete 後的決策邏輯:
##### 情境 A:Follow-on Request = 1
這是 `UERANSIM` Default 行為,甚麼都不用做即可。
AMF 行為:保留 UE Context 和 NAS signalling connection
結果:UE 保持 CM-CONNECTED 狀態,等待後續 NAS 信令
用途:支援立即的 PDU Session 建立或其他 NAS 程序
##### 情境 B:Follow-on Request = 0 且無 pending traffic
複現可以參見 [Steps](#Steps7)
AMF 行為:立即發送 UE Context Release Command 給 gNB
結果:UE 進入 CM-IDLE 狀態
>The AMF considers the NAS signalling connection is released if it detects the N2 context is released.
>[color=#f2ca04][TS 23.501 (5.3.3.3.3)](https://www.etsi.org/deliver/etsi_ts/123500_123599/123501/16.06.00_60/ts_123501v160600p.pdf)
##### 情境 C:Follow-on Request = 0 但有 pending MT traffic
複現要馬要讓 UE 變成 `Mobility Registration` (尚未研究如何達成) 或是使用 `Emergency Service` (現在 free5GC 並沒有支援),因此要再研究。
AMF 行為:仍可選擇保留 UE Context
> unless the connection was initiated as a response to paging of an MT event, or after a mobility registration procedure without Follow on Request Indication set or after a mobility registration procedure for regulatory prioritized services like Emergency services or exception reporting. .
>[color=#f2ca04][TS 23.501 (5.3.3.3.3)](https://www.etsi.org/deliver/etsi_ts/123500_123599/123501/16.06.00_60/ts_123501v160600p.pdf)
#### Steps
意外在搜尋過程找到類似的 [issue](https://forum.free5gc.org/t/uecontextreleasecommand-is-being-sent-from-free5gc/860)。基本上照著上面寫的做就OK了,具體流程是改一下 `UERANSIM` 的 code,就可以完成了。
```diff
diff --git a/src/ue/nas/mm/register.cpp b/src/ue/nas/mm/register.cpp
index e85c292..e2432eb 100644
--- a/src/ue/nas/mm/register.cpp
+++ b/src/ue/nas/mm/register.cpp
@@ -69,7 +69,8 @@ EProcRc NasMm::sendInitialRegistration(EInitialRegCause regCause)
auto requestedNssai = makeRequestedNssai(isDefaultNssai);
// Prepare FOR pending field
- nas::EFollowOnRequest followOn = nas::EFollowOnRequest::FOR_PENDING;
+ // nas::EFollowOnRequest followOn = nas::EFollowOnRequest::FOR_PENDING;
+ nas::EFollowOnRequest followOn = nas::EFollowOnRequest::NO_FOR_PENDING;
// Create registration request
auto request = std::make_unique<nas::RegistrationRequest>();
```