Challenge_^平台不支持上传start.sh,bzImage等组件,想要通过运行测试的可以跟我要链接
ko文件直接放ida_int64 xor_kmod_init(){fentry();proc_create(“xor_check”, 438, 0, xor_proc_fops);printk(unk_1F0);return 0;}__int64 __fastcall xor_proc_write(__int64 a1, __int64 a2){unsigned __int64 n17_1; // rdx__int64 n17; // rbx__int64 n17_2; // raxBYTE s1[49]; // [rsp-31h] [rbp-31h] BYREFfentry_(a1, a2);n17 17;*(QWORD *)s1[17] _readgsqword(0x28u);if ( n17_1 0x11 )n17 n17_1;check_object_size(input_buffer, n17, 0);if ( copy_from_user(input_buffer, a2, n17) )return -14;if ( n17 input_buffer[n17 - 1] ! 10 n17 17 ){for ( n17_2 0; n17_2 ! 17; n17_2 )s1[n17_2] input_buffer[n17_2] ^ 0x62;if ( !memcmp(s1, TARGET, 0x11u) ){printk(unk_190);return 17;}else{printk(unk_1C8);return -22;}}else{printk(unk_168);return -22;}}__int64 xor_kmod_exit(){remove_proc_entry(“xor_check”, 0);return printk(unk_234);}关键还是在于异或for ( n17_2 0; n17_2 ! 17; n17_2 )s1_[n17_2] input_buffer[n17_2] ^ 0x62;if ( !memcmp(s1_, TARGET, 0x11u) )取target与62进行16进制异或11 06 12 01 19 10 07 12 15 0C 12 15 0C 1Fsdpc{repwnpwn}学几个函数proc_create(“xor_check”, 438, 0, xor_proc_fops);它做了以下几件事proc_create()这是一个内核函数别人已经写好的工具函数作用是在 /proc 目录下创建一个文件。/proc 是什么Linux 系统里有一个特殊的目录叫 /proc它里面的文件不是存在硬盘上的而是内核虚拟出来的。你读/写这些文件实际上是在和内核里的代码交互。比如你 cat /proc/cpuinfo 就能看到 CPU 信息。参数1 “xor_check”文件的名字。所以这个文件就叫 /proc/xor_check参数2 438这个数字是权限用8进制看就是 0666。6 表示 “读写”三个数字分别对应 “文件主人/同组人/其他人” 都能读写。这里所有人都可以读写这个文件。参数3 0父目录设为空意味着放在 /proc 根目录下参数4 xor_proc_fops最关键的一个参数。xor_proc_fops 是一个函数表fops file operations这张表里记录了当有人读这个文件时调用哪个函数、“当有人写这个文件时调用哪个函数”。 表示取这个表的地址告诉内核这张表在哪。在这个表里写操作指向的就是下面的 xor_proc_write 函数。_check_object_size(input_buffer, n17, 0);_check_object_size()又一个内核安全检查函数检查 input_buffer存用户数据的缓冲区有没有至少 n17 字节那么大防止越界访问参数3 的 0 表示这是一个读操作检查if ( copy_from_user(input_buffer, a2, n17) )return -14;copy_from_user()把数据从用户空间复制到内核空间为什么需要这步系统里有两块地盘用户空间普通程序待的地方和内核空间内核代码待的地方。程序不能直接访问内核的地盘需要通过 copy_from_user 这个海关来传输数据。参数1 input_buffer数据要存到内核的哪个位置参数2 a2从用户空间的哪个地址取数据参数3 n17复制多少个字节debugmeida打开int __fastcall main(int argc, const char **argv, const char **envp){char Str[8]; // [rsp20h] [rbp-30h] BYREF__int64 v5; // [rsp28h] [rbp-28h]__int64 v6; // [rsp30h] [rbp-20h]__int64 v7; // [rsp38h] [rbp-18h]char v8; // [rsp40h] [rbp-10h]_main();*(_QWORD *)Str 0;v5 0;v6 0;v7 0;v8 0;puts(“Please input the flag: “);scanf(”%32s”, Str);if ( strlen(Str) 32 ){decode_flag(flag);if ( !memcmp(Str, flag, 0x20u) )puts(Right! );elseputs(Wrong! );}else{puts(Length wrong! );}return 0;}在比较处下断点然后debug输入任意32个字符回车image-20260702143252120lea rcx, flag ; rcx flag (flag 缓冲区的地址)call decode_flag ; 调用 RC4 解密函数把加密数据解密到 flag 缓冲区; 执行完这行后flag 地址处就是明文 flag 了lea rax, [rbpStr] ; rax 用户输入的字符串地址mov r8d, 20h ; r8 32 (第三个参数比较长度 32 字节)lea rdx, flag ; rdx flag 地址 (第二个参数解密后的 flag)mov rcx, rax ; rcx 用户输入地址 (第一个参数)call memcmp ; 调用 memcmp(用户输入, 解密后flag, 32)SDPCSEC{debug_to_do_1s_e53y}ezida直接打开然后ctrlF就能搜到依旧学习代码Stream __iob_func();__iob_func()获取标准输入/输出/错误流的信息。返回一个结构体数组其中包含 stdin键盘输入、stdout屏幕输出等。Stream 指向 stdin。fgets(Buffer, 100, Stream);fgets “file get string”从文件读取一行文字。第一个参数 Buffer → 把读到的内容存入 Buffer 数组。第二个参数 100 → 最多读 99 个字符留一个给字符串结尾标记 \0。第三个参数 Stream → 从键盘stdin读。效果你输入一行字按回车这行字被存进 Buffer。去掉换行符Buffer[strcspn(Buffer, “\n”)] 0;strcspn(Buffer, “\n”) “string complement span”找到 Buffer 里第一个 “\n”换行符的位置。因为你按了回车fgets 会把换行符也保留在 Buffer 末尾。例如你输入 abc\n这个函数返回 3\n 在第 3 个位置从 0 开始数。Buffer[位置] 0把那个位置的换行符替换成 0也就是字符串结束符 \0相当于剪掉换行符。检查长度if ( strlen(Buffer) correct_len )strlen(Buffer) “string length”数一下 Buffer 里有多少个字符遇到 \0 停止。correct_len一个事先定义好的全局变量值是 0x1f 31。if (…)如果括号里的条件成立就执行它下面的代码块否则跳到 else。这句话在说如果你输入的密码长度不等于 31就去报错。WCPVPhgaLL#L rjL$#Lu}wn转16进制放随波逐流异或40 57 43 50 40 56 50 68 60 67 61 4c 22 60 4c 26 23 4c 20 72 26 6a 4c 24 23 4c 75 22 7d 77 6edata b’WCPVPhgaLL#L rjL$#Lu}wn’print(bytes([b ^ 19 for b in data]).decode())go^go?解压后只有三个关键文件GD1.exeGD1.console.exeGD1.pck这里已经基本能判断是 Godot 导出的 Windows 游戏。原因很简单exe .pck 是 Godot 很典型的发布结构。调用工具反编译E:\retools\gdre\gdre_tools.exe --headless–recoverE:\cod_ctf_nxgodot\NXgodot\GD1.pck–outputE:\cod_ctf_nxgodot\gdre_recover或者直接打开E:\retools\gdre\gdre_tools.exe把附件拖进去查看重点在main.gdcextends Nodeexport var mob_scene: PackedScenevar scorevar a “000010000010000001101001000010000001000001100110000010000010000001101000000001100110000010010000000001101001000000011001000000011000000000100000000001100100000001100111000001110001000000100011000000010010000001110001000000100010000000100010000000011000000000010010000000100001000000011000000001100111000001100100000000010010000001100100000000010110000000010110000000010111000000010010000000010110000000011000000001101001000000100101000000100010000001101001000001100100000000011000000001101000000000100011000000010111000000010111000010010010\n”var xor_key 33func _ready():passfunc _process(delta: float) - void :passfunc game_over():$ScoreTimer.stop()$MobTimer.stop()$HUD.show_game_over()func new_game():score 0Player.start(Player.start(Player.start(StartPosition.position)$StartTimer.start()$HUD.update_score(score)$HUD.show_message(“Get Ready”)get_tree().call_group(“mobs”, “queue_free”)func _on_mob_timer_timeout():var mob mob_scene.instantiate()var mob_spawn_location $MobPath / MobSpawnLocationmob_spawn_location.progress_ratio randf()var direction mob_spawn_location.rotation PI / 2mob.position mob_spawn_location.positiondirection randf_range( - PI / 4, PI / 4)mob.rotation directionvar velocity Vector2(randf_range(150.0, 250.0), 0.0)mob.linear_velocity velocity.rotated(direction)add_child(mob)func _on_score_timer_timeout():score 1$HUD.update_score(score)if score 177906: var result for i in range(0, a.length(), 12): var bin_chunk a.substr(i, 12) if bin_chunk.length() 12: break var hundreds bin_chunk.substr(0, 4).bin_to_int() var tens bin_chunk.substr(4, 4).bin_to_int() var units bin_chunk.substr(8, 4).bin_to_int() var mixed_val hundreds * 100 tens * 10 units var decrypted_val mixed_val ^ xor_key result String.chr(decrypted_val) $HUD.show_message(result)func _on_start_timer_timeout():$MobTimer.start()$ScoreTimer.start()