---
title: Reversing.kr Write-up 1
description: 一些 Reversing.kr 篇幅不長的 Write-up 集中地
tags: CTF, Reversing
lang: zh_tw
---
# 🚩 Reversing.kr Write-up 1
[TOC]
## Easy_CrackMe
### 👀 Analyze the decompiled code
靜態分析嘛,先想辦法到比較關鍵點的 code
先執行看看

ok 有呼叫一個視窗出來講 Incorrect Password
這次用 Ghidra 這工具,跟它培養一下感情

從旁邊的 Symbol Tree 找到 MessageBoxA 這個創造視窗的 API
對它右鍵 -> Show References to
找到哪邊有 call 到它
就找到了這邊

尾巴就看到了 Congratulation 跟 Incorrect Password 兩種結局
那要怎樣才能進到好結局勒
從 Line 27 可以知道 cStack99 要是 a
從 Line 49 知道 cStack100 要是 E
從 Line 28 知道 acStack98 兩個字要是 5y

這樣就組出 Ea5y 了
在 Line 29 ~ 47 的 if 中定義
pbVar4 是 0x0040606c

pbVar2 是 abStack96
這兩個字串會一直比到結束,一次比 2 bytes,都需一樣
所以知道 abStack96 必須是 R3versing
合起來整個從 cStack100 開始的字串就是
就是答案
## Easy_KeygenMe
這支程式 run 一下

會要求輸入 Name 和 Serial
題目的 README 寫著要找到對應 Serial 為 **5B134977135E7D13** 的 Name
### 👀 Analyze the decompiled code
OK,那先從找哪邊用到`Input Name:`跟`Input Serial:`這兩個字串開始
用 Ghidra 打開,跑一下 Auto Analyze

主要要選到 ASCII Strings
跑完分析後,接著按一下 Window -> Defined Strings
搜尋字串 `Input Name`

對 00408060 辣個 Address 點兩下左鍵

再對 code 裡面的 00408060 辣個 Address 點右鍵 -> References
-> Show References to Address
找到哪邊有用到這個字串ㄌ
就在 0040102e

一樣對 Address 左鍵點兩下可以快速移動到該 Address
旁邊就有 Decompile 的 code 可以參考
嗯~ Ghidra 這免費分析工具不錯用

~~FUN_print FUN_scan 是我改名過後的~~
可以看到 Line 87 ~ 101 的 do while 結束後
如果中間完全沒直接跳到 LAB_00401113,老老實實的執行完迴圈
會因為 Line 102 的 code,進入 Line 105 ~ 106 輸出 Correct
否則就會輸出 Wrong
所以**如何才會輸出 Correct** 這個問題就變成在問
**要怎樣才能老老實實的執行完迴圈 ?**
Line 90 中, bVar2 的值其實是 \*pbVar3
所以 Line 90 其實在比對 pbVar3 跟 pbVar7 這兩個地址中的值
- 如果不一樣
- 若 bVar10 是 1 (true),最終 iVar4 會是 0 - 1 = -1
- 最終輸出 Wrong
- 若 bVar10 是 0 (false),最終 iVar4 會是 1 - 0 = 1
- 最終輸出 Wrong
說到底,進到這裡就表示最終輸出都是 Wrong
所以輸出 Correct 的條件之一是 **\*pbVar3 跟 \*pbVar7 需相同**
接著看到 Line 95,比對到 \*pbVar3 是 0 就結束比對,輸出 Correct
不是 0 的話,後面就得還要繼續比對
Line 96 ~ 98 根本是 Line 88 ~ 94 的翻版
只是比較對象是 \*pbVar3\[1\] 跟 \*pbVar7\[1\]
Line 99 ~ 100 將兩個地址 pbVar3 pbVar7 都直接加 2
---
總結這一個 Block (Line 87 的 do while ~ 最後) 的 code 在幹嘛
翻譯為人類聽得懂的話,就是
**比對 pbVar3 pbVar7 這兩個地址上的字串,一次比對兩個字
都一樣就輸出 Correct,反之輸出 Wrong**
---
繼續往上看
Line 85 ~ 86 寫
pbVar7 為 abStack176
pbVar3 為 abStack276
所以我說 abStack176 abStack276 這兩個地址上的字串是啥勒?
---
先來看看 FUN_scan 是啥好了 owo

call 了 FUN_scan("%s", ESP + 0x10)

FUN_scan(param1, param2)
又繼續 call 了 FUN_00401c8e(00408090、param1、¶m2)
也就是 FUN_00401c8e(00408090、"%s"、¶m2)
---
進到 FUN_00401c8e 我傻眼
781 行 code
不過還是耐住性子看一下
整個 code 被一個超大 do {} while(true) 包住

首先第一個 block 目光來到 Line 70 ~ 79
bVar1 在 Line 63 可以看到,是第二個參數,也就是 "%s"
~~在此我有不祥的預感,該不會這裡實作了一個簡單的 scanf 吧~~
如果 bVar1 等於 0,就會進這判斷式,不過因為我們ㄉ bVar1 不是 0
就先不看裡面做什麼了 ~~反正不會進去麻~~
接著第二個 block 看到 Line 80 ~ 90
比較 \*(int \*)0x408680 裡的值是否 < 2
切到 0x408680 看到是 0x00000001
所以一開始會執行 Line 81 ~ 82 的 code
this_02 在 Line 67 定義為 0x408468,而 \*0x408468 為 0x408472
而 bVar1 為 \*param_2,單位是 1 byte,所以這邊 bVar1 會是 0x25 也就是 `%` 的 ASCII code
從地址 (0x408472 + 0x25 \* 2) 取一個 byte 出來並且 and 8
這就是 uVar4 !! (好累 汗顏@@)
---
好到這邊 我已經不想靜態分析了 XD
直接動態看一下,搞不好還比較簡單
### 💩 Dynamically Analyze
雖然說是動態分析,但若在靜態分析過後大概知道關鍵點是啥會更好
另外一點就是,Decompiler 只能參考,反編譯回去時可能跟原意有些許差距
好,現在還不知道 FUN_print 跟 FUN_scan 的作用
先在他們頭上設斷點

來到 Call FUN_print 前

按一下 F8 步過

可以發現 Terminal 輸出文字ㄌ
所以大膽猜測,FUN_print 就是輸出 Stack 中第一個 pointer 所指向的字串
---
再來看看 FUN_scan
來到 Call FUN_scan 之前

按一下 F8 步過

發現 Terminal 可輸入東西
輸入進去後

很像 scanf(format, param1, param2 ...)
Stack 中第一個參數是 \"%s\"
第二個參數是 0x0018FE20
可以看到右下角 Stack
輸入字串被存到 0x0018FE20 了
---
那後面的邏輯是啥勒 ?
還是參考一下 Decompiler

剛剛下的斷點分別是 Line 46, 47
所以接下來會執行的 code 是從 Line 48 開始
首先看到 Line 52 ~ 57
就是在算 pcVar8 所指的字串的長度
長度會存到 \~uVar5
Line 58 的條件式形同 \~uVar5 > 1
再來 Line 59 ~ 74的 do while 中
第一個區塊是 Line 60 ~ 62 的 if
配合 Line 50、66,讓 iVar6 一直循環 0 1 2 0 1 2 ...
if 過了之後 call FUN_00401150
這個 function 不知道在幹嘛,就很適合下斷點看看
Line 49、64 定義 iVar4 從 0 開始每 loop 一次就 ++ 一次
Line 68 ~ 73 又在算一次字串長度
最後 Line 74 目的是要把字串整個 loop 過一遍
---
好,來下斷點
斷在要 call FUN_00401150 之前
不過下斷點時,發現了一件在 decompiler 沒發現的事

0x0040107A 對應 Line 60 ~ 62 的 if
但是,but,西咖喜
0x0040107E 的 movsx ecx ...
到
0x00401088 的 xor ecx, edx
好像沒在 decompiler 中有對應的 code ?!
那就先動態分析看看這邊在幹嘛
新設一個斷點在 0x0040107E 的 movsx ecx, ...
並且執行到它

esp + esi + C 此時等於 0x0018FE1C,值是 0x10
注意到這邊,esi 會循環 0 1 2 0 1 2 ...
所以 \[esp + esi + C\] 的值會循環 10 20 30 10 20 30 ...
在來 esp + ebp + 10 此時等於 0x0018FE20,也就是輸入字串
所以輸入的字串,會跟 0x10 0x20 0x30 這把 key xor
然後 call 進辣個神秘 function FUN_00401150
---
再來執行到 call FUN_00401150 之前

可以看到 Stack 中
第一跟第三個參數一樣,為0x0018FE84

那邊都是 0
第二個參數是一個像是 format 的東西 \"\%s\%02x\"
第四個參數是剛剛 xor 完的產物 0x71
按一下 F8 步過

歐歐歐歐歐

剛剛 xor 的東西存來這了,而且是把 0x71 轉成字串 "71" 存起來
---
我們看看這個 do while 進到第二圈
並且再度停在 call FUN_00401150 之前

Stack 中的參數第一二三個參數依舊沒變
第四個參數也是 0x61 (a 的 ascii code) 跟 0x20 xor 的結果 0x41
按一下 F8 步過

看看 0x0018FE84

把 xor 的產物 0x41 轉成字串 "41" 黏在字串後面了
---
整個 do while 都執行完
可以斷點斷在 0x004010B6

看看 0x0018FE84

的確就是輸入字串 aaaabbbbccccdddd 跟 0x10 0x20 0x30 依序下去 xor 的產物轉成字串
---
再來設斷點在輸入 serial

按下去 F8
輸入


最後可以看到是前一 part 有分析出來的比較字串的部分
兩個字串的頭分別是剛剛輸入的 serial
跟 0x0018FE84
### 🚩 Got it
到目前為止已經知道怎解了
題目的 serial 是 **5B134977135E7D13**
就只要依序 xor 回去就好了
## Easy_UnpackMe
### 🤔 Pack Unpack
~~完全不知道是啥的可以查一下 `UPX`~~
~~Pack 中文翻加殼~~
~~Unpack 為脫殼~~
~~後面加 er 就變加殼器/脫殼器~~
---
我們先講講 **流程** :
假設原本我們的程式是 A
Packer 會把程式給**壓縮**起來,變成 B
並且把程式一開始的進入點改成一段**解壓縮**的 code,這裡用 C 來表示
關於程式進入點
Windows exe 可以 google `PE Header`
Linux elf 則可以 google `ELF Header`
| 程式名稱 | Binary 實際結構 |
| -------- | -------- |
| 原本程式 | A |
| 加殼後的程式 | C, B |
運行時,會先執行**解壓縮**的code,將程式變回原來的樣子
| 程式名稱 | Binary 實際結構 |
| -------- | -------- |
| 執行完解壓縮 | C, A |
解壓縮完後,JMP 到原本程式 A 執行原本要做的事情
---
**為啥要 Pack ?**
1. 反防毒:
首先先問問 Anti-Virus 怎麼運作的 ? ~~雖然我也不是很了解,但我猜~~
有一種方式是看有沒有特定的 pattern 出現
比如說某些 pattern 就是開後門的 code 的長相
那 Packer 壓縮原本的程式後,整個 pattern 就被打散了,達到反防毒的目的
2. 反逆向:
好饒舌,反逆向
原本沒 Pack 的程式,裡面的 OPcode 形同裸體,有心的人必能事成將它的邏輯搞懂
Pack 後的程式,逆向攻城屍得先想辦法將它 Unpack,Unpack 不出來就連逆都不用逆
3. 壓縮:
Packer 不只形同加密般把原本程式變成亂碼,還可以將 size 壓縮變小
### 👀 So, how to unpack ?
- 第一種方法
看有沒有別人寫好的工具
不過你也得先知道對應的 Packer 是什麼,才能找 Unpacker
PEiD 就是個 Packer Detector 查殼器,自己去下載ㄅ
不過它也不是萬用的,有些殼是程式創作者自己寫的,這種 Custom Packer 就查不到了

像是這題就查不到
- 第二種方法:
用 x32/x64dbg 執行到真正的進入點
套用前章節的說法
> 運行時,會先執行**解壓縮**的code,將程式變回原來的樣子
| 程式名稱 | Binary 實際結構 |
| -------- | -------- |
| 執行完解壓縮 | C, A |
一直逐步執行直到真正的進入點
(所謂真正的進入點,就是進入 A 的點)
此時程式都解壓縮回真正的樣貌了
再把整個程式從 Memory dump 出來,並且將 Header 裡的進入點修改到真正的進入點
就脫殼完成
### 😐 Entry Point, but not OEP
這題呢,來示範第二個方法
首先用 Ghidra 打開程式,看看 PE Header,來實際算一次進入點在哪
參考 PE Header 架構圖

好像很長很可怕,但實際上要看的只有
- offset 0x28 的 Address Of Entry Point
- offset 0x34 的 Image Base
兩者相加,就是 Entry Point
看看 Ghidra 畫面

最前面這部分是 DOS Header
0x3c 的地方表示 Pointer to PE Header
看到 0x3c 上面的值是 0xd8
移駕到 0xd8 看一下
對著有地址的這個視窗按 G,並輸入 0x4000d8 就可以快速移動

OK 我們要看的地方是
- offset 0x28 的 Address Of Entry Point
- offset 0x34 的 Image Base
兩個地方分別加上 offset 0xd8
也就是 0x100 跟 0x10c


注意 Little-endian
所以這兩個值分別是
- 0x00a04b: Address Of Entry Point
- 0x400000: Image Base
相加就是 0x40a04b
看看 0x40a04b 的 code

Ghidra 很貼心的在上面用 entry(void) 命名這段區間的 code
### 🤘 OEP !!
不過剛剛算的那個不是真的 OEP(Original-Entry-Point)
蛤,你問我怎麼知道 ? ~~因為 0040a04b 不是 FLAG 啊~~
那那那麼,0x40a04b 這個地址的 code 在幹嘛???
動態分析看看好了,丟到 x32dbg

剛開起來就自動停在 0x40a04b 了
因為 x32dbg 會很貼心自動在 entry 下一個斷點
你可能會說: 欸欸乾 剛剛entry**算心酸**的喔
沒有啦~ 想說,帶你了解一下 entry 真正是怎麼算的麻~~
好,回正題,這邊的 code 我是沒有好好的 reverse 完
你可以好好的 reverse 這邊的 code 當練習
但我看到一堆 xor,我猜這邊就是在做前面說的**解壓縮**的步驟
只是如果只是 xor,那就不能說是**解壓縮**,而是**解密**
anyway,這一段 code
我猜就是將 Packed 程式 B 還原回原本程式 A 的過程
那麼,最後要一個 JMP to A 的指令吧 ?
把這段 code 一直往下拉,拉阿拉....

看來很可疑的 JMP,斷點設在辣邊,然後看看會跳到哪邊
噢噢跳到了這裡

(碼掉了,自己的 flag 自己找)
我們為了方便下面行文,辣個 OEP 我們就說是:
**004011XX**
基本上到這裡,這題就結束了,不過
你不覺得很好奇嗎
1. 那原本沒執行 C 時,**004011XX** 這邊長怎樣?
2. 前面方法 2 提到的
> 把整個程式從 Memory dump 出來,並且將 Header 裡的進入點修改到真正的進入點
怎麼做?
### 👀 The encrypted code at 004011XX
簡單一個截圖說明一切:

截自 Ghidra,還沒被還原時,長相長這樣
可以對比前章節已經還原後的 004011XX
OPcode 根本不一樣,表示說還原的步驟的確有對原程式動到手腳
### 😎 Dump the unpacked exe
這裡不得不說 x32dbg 真D好用
當 x32dbg 已跳到 OEP 時
從外掛程式(Plugin) -> Scylla

剛開起來長這樣
依序點 IAT Autosearch -> Get Imports -> Dump
假設輸出檔名為 Easy_UnpackMe_dump.exe
可以執行看看

此時還執行不起來
沒關係,再點 Fix Dump
選擇 Easy_UnpackMe_dump.exe
可以看到下面 Log 多了一條

再執行看看 Easy_UnpackMe_dump_SCY.exe
應該就是可以執行的了吧~~
再把它丟到 x32dbg
就可以發現一開始就直接進到 **004011XX**
你也可以自己用 PE Header 的欄位算算看是否是這個位置
這表示這支程式進入點是直接到 **004011XX**
這支程式已經是原本的程式,而非 Packed 程式
已經不需要經過**還原回原本程式**的 code 了
### Reference
[Dump the unpacked exe](https://goggleheadedhacker.com/blog/post/6)
[PE Header](https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg)
## Easy_ELF
### 👀 Analyze
就在 Linux 中動態分析
發現一些簡單的 xor 囉

斷點斷在 0x8048380
然後一直 follow follow follow
追到 \_\_libc\_start\_main+237 時
會跳到主要的 code

輸入 si 走進去看看

發現跳到 0x804851b
用 objdump 看一下這邊的 code

前面有三個連續 call
分別 call 了
- write@plt
輸出訊息
- 0x8048434
- 0x8048451
### 👀 0x8048434

只是一個 scanf
format 是 \"\%s\"
存到 0x804a020
### 👀 0x8048451

這個小多
看看能不能變出一個圖形化的東西來看
這邊用 radare2
```
r2 Easy_ELF
```
輸入 s 跳到這個地址
```
s 0x8048451
```
跑一下分析,等等才能產生圖
```
aaa
```
最後來看圖
```
VV
```

到這個畫面,可以用方向鍵來移動

目標是要進到右下角 mov eax, 1 的區塊 然後 return,表示成功驗證
到這邊之後,就很好解了
<!-- -->
<style>
/* fix mathjax rwd scroll
* #Research-direction > simple model
*/
ul > li > .mathjax {
overflow-x: scroll;
overflow-y: hidden;
overflow-wrap: break-word;
display: inline-block;
}
#doc > ul:nth-child(38) > li:nth-child(4) > ul > li > .mathjax {
width: 100%;
}
/* Dark mode */
/* <!-- todo: fix highlight.js blocks; some code blocks do not render correctly --> */
.navbar-default {
background-color: #091a22;
}
.navbar-default .navbar-brand,
.ui-infobar {
color: #ebebeb;
}
body {
background-color: #23272a !important;
}
.ui-view-area {
background: #23272a;
color: #ddd;
}
.ui-toc-dropdown {
background-color: #23272A;
border: 1px solid rgba(255,255,255,.15);
box-shadow: 0 6px 12px rgba(255,255,255,.175);
}
.ui-toc-dropdown .nav > li > a {
color: #ccc;
}
.ui-toc-dropdown .nav > .active:focus > a,
.ui-toc-dropdown .nav > .active:hover > a,
.ui-toc-dropdown .nav > .active > a {
color: #bbb;
}
.ui-toc .open .ui-toc-label {
color: #777;
}
table * {
background-color: #424242;
color: #c0c0c0
}
button,
a {
color: #64B5F6;
}
a:hover,
a:focus {
color: #2196F3;
}
a.disable,
a.disable:hover {
color: #EEEEEE;
}
/* Dark mode code block */
/* Imported from titangene/hackmd-dark-theme */
.markdown-body pre {
background-color: #1e1e1e;
border: 1px solid #555 !important;
color: #dfdfdf;
font-weight: 600;
}
.token.operator, .token.entity,
.token.url, .language-css .token.string,
.style .token.string {
background: unset;
}
/* Dark mode alert boxes */
.alert-info {
color: #f3fdff;
background: #40788A;
border-color: #2F7A95;
}
.alert-warning {
color: #fffaf2;
background: #936C36;
border-color: #AE8443;
}
.alert-danger {
color: #fff4f4;
background: #834040;
border-color: #8C2F2F
}
.alert-success {
color: #F4FFF2;
background-color: #436643;
border-color: #358A28;
}
/* Stylized alert boxes */
.alert-danger>p::before {
content: "❌ Dangerous\A";
}
.alert-warning>p::before {
content: "⚠ Warning\A";
}
.alert-info>p::before {
content: "ℹ Information\A";
}
.alert-warning>p::before,
.alert-danger>p::before,
.alert-info>p::before {
white-space: pre;
font-weight: bold;
}
</style>
<style>
/*
* Visual Studio 2015 dark style
* Author: Nicolas LLOBERA <nllobera@gmail.com>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #1E1E1E;
color: #DCDCDC;
}
.hljs-keyword,
.hljs-literal,
.hljs-symbol,
.hljs-name {
color: #569CD6;
}
.hljs-link {
color: #569CD6;
text-decoration: underline;
}
.hljs-built_in,
.hljs-type {
color: #4EC9B0;
}
.hljs-number,
.hljs-class {
color: #B8D7A3;
}
.hljs-string,
.hljs-meta-string {
color: #D69D85;
}
.hljs-regexp,
.hljs-template-tag {
color: #9A5334;
}
.hljs-subst,
.hljs-function,
.hljs-title,
.hljs-params,
.hljs-formula {
color: #DCDCDC;
}
.hljs-comment,
.hljs-quote {
color: #57A64A;
font-style: italic;
}
.hljs-doctag {
color: #608B4E;
}
.hljs-meta,
.hljs-meta-keyword,
.hljs-tag {
color: #9B9B9B;
}
.hljs-variable,
.hljs-template-variable {
color: #BD63C5;
}
.hljs-attr,
.hljs-attribute,
.hljs-builtin-name {
color: #9CDCFE;
}
.hljs-section {
color: gold;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
/*.hljs-code {
font-family:'Monospace';
}*/
.hljs-bullet,
.hljs-selector-tag,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #D7BA7D;
}
.hljs-addition {
background-color: #144212;
display: inline-block;
width: 100%;
}
.hljs-deletion {
background-color: #600;
display: inline-block;
width: 100%;
}
</style>