-
TLSv00 - pwnable.xyzWrite-ups/pwnable.xyz 2020. 2. 12. 03:53
Prob Info
Prob Info Checksec int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { signed int v3; // eax signed int v4; // ST0C_4 setup(*(_QWORD *)&argc, argv, envp); puts("Muahaha you thought I would never make a crypto chal?"); generate_key(0x3F); while ( 1 ) { while ( 1 ) { while ( 1 ) { print_menu(); printf("> "); v3 = read_int32(); if ( v3 != 2 ) break; load_flag(); } if ( v3 > 2 ) break; if ( v3 != 1 ) goto LABEL_12; printf("key len: "); v4 = read_int32(); generate_key(v4); } if ( v3 == 3 ) { print_flag(); } else if ( v3 != 4 ) { LABEL_12: puts("Invalid"); } } }
크립토 문제라고 한다. 일단 키 생성 부분부터 확인하면 아래와 같다.
unsigned __int64 __fastcall generate_key(signed int a1) { 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 ( a1 > 0 && (unsigned int)a1 <= 0x40 ) { memset(s, 0, 0x48uLL); 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 ) { while ( !s[i] ) read(fd, &s[i], 1uLL); } strcpy(key, s); close(fd); } else { puts("Invalid key size"); } return __readfsqword(0x28u) ^ v5; }
인자로 받은 키 길이만큼 /dev/urandom에서 읽어들이고 key에 strcpy로 복사한다. 키값을 읽어올 때 널바이트는 무시하도록 만들어서 길이만큼 복사는 되지만 문자열 함수이므로 맨 뒤에 널바이트가 붙게 된다.
__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() == 121 ) do_comment = (__int64 (*)(void))f_do_comment; result = do_comment(); } return result; }
플래그 출력 함수 부분이다. 그런데 기능이 구현되지 않아 대신 설문조사 함수를 do_comment에 저장하고 호출해준다. 그런데 이 함수의 주소를 보면 아래와 같은데, 실제 플래그를 출력해주는 함수와 근접하다.
.text:0000000000000B1F ; __unwind { .text:0000000000000B1F push rbp .text:0000000000000B20 mov rbp, rsp .text:0000000000000B23 sub rsp, 40h .text:0000000000000B27 mov rax, fs:28h .text:0000000000000B30 mov [rbp+var_8], rax .text:0000000000000B34 xor eax, eax .text:0000000000000B36 lea rdi, aEnterComment ; "Enter comment: "
.text:0000000000000B00 real_print_flag proc near .text:0000000000000B00 ; __unwind { .text:0000000000000B00 push rbp .text:0000000000000B01 mov rbp, rsp .text:0000000000000B04 lea rsi, flag .text:0000000000000B0B lea rdi, format ; "%s" .text:0000000000000B12 mov eax, 0 .text:0000000000000B17 call printf .text:0000000000000B1C nop .text:0000000000000B1D pop rbp .text:0000000000000B1E retn .text:0000000000000B1E ; } // starts at B00 .text:0000000000000B1E real_print_flag endp
게다가 끝 주소도 00으로 끝나서 PIE가 걸려있어도 주소 끝부분만 널바이트로 덮어주면 3번 메뉴로 실제 플래그 출력 함수를 호출할 수 있다. 여기서 앞서 설명한 generate_key함수의 strcpy가 off-by-one을 가능하게 해주는 것을 알 수 있다. 먼저 3번 메뉴를 한번 호출해서 do_comment에 f_do_comment 함수의 주소를 적고 다시 generate_key를 호출하면 off-by-one을 통해 do_comment가 가리키는 함수를 실제 플래그 함수로 바꿀 수 있다.
여기까지 진행하면 실제 플래그를 출력할 수 있는데, 이 플래그는 xor 암호화가 되어있다. 플래그 형식이 "FLAG{}" 이므로 이런 형식이 나오도록 하면 된다. 키 길이를 1로 하면 (0은 불가) 키가 아래와 같이 될 것이다.
key : | ?? | 00 | ?? | ?? | ... | ?? |
그렇다면 1번째 인덱스의 L은 "L" ^ 0 을 진행해 그대로 L이 될 것이다. 이런 방식으로 1번째부터 플래그 길이만큼 하나씩 brute-forcing으로 구하면 된다. 플래그를 %s로 출력해주기 때문에 암호화를 하다 널바이트가 나오면 플래그가 끊길 수 있으니 범위를 나눠서 구하면 된다.
'Write-ups > pwnable.xyz' 카테고리의 다른 글
l33t-ness - pwnable.xyz (0) 2020.02.12 Jmp table - pwnable.xyz (0) 2020.02.12 Free Spirit - pwnable.xyz (0) 2020.02.12 two targets - pwnable.xyz (0) 2020.02.12 xor - pwnable.xyz (0) 2020.02.11