while ( 1 )
{ n = 0LL; printf(format); c = _isoc99_scanf("%ld %ld %ld", &x, &y, &n); if ( !x || !y || !n || n > 9 || c != 3 ) break; result[n] = y ^ x; argv = (const char **)result[n]; printf("Result: %ld\n", argv); } |
특정 위치에 x^y
의 값을 쓰는 작업을 할 수 있다. n
이 9 초과일 때만 필터링하고, 음수를 넣어 0x202248 이하의 범위의 영역에 값을 자유롭게 쓸 수 있다.
바이너리를 실행하고 프로세스의 메모리 매핑 상태를 살펴보니 0x0~0x1000 범위에 RWX 권한이 있다는 점이 눈에 띈다.
이유를 찾아보니 __do_global_ctors_aux
에서 특정 페이지에 대해 PROT_READ | PROT_WRITE | PROT_EXEC
권한을 주는 것을 볼 수 있었다.
int _do_global_ctors_aux()
{ _DWORD *addr; // [rsp+8h] [rbp-8h] for ( addr = (_DWORD *)((unsigned __int64)_do_global_ctors_aux & 0xFFFFFFFFFFFFF000LL); *addr != 0x464C457F; addr += 2 ) ; return mprotect(addr, 0x1000uLL, 7); } |
.text section에 write 권한이 있고 0x202248 이하 범위의 영역을 수정할 수 있기 때문에 xor 연산을 통해 dynamic code patch를 할 수 있다. 쉘코드를 직접 쓸 수도 있지만 win
함수가 주어졌고, call win
명령어 1개를 쓰는 게 더 편하고 쉽기 때문에 이 방법을 쓴다.
result 가 8바이트 단위의 배열이기 때문에, 주소가 8의 배수인 명령어를 찾아서 고쳐주어야 한다. 적절한 타겟은 main
함수에 있는 0xAC8의 call exit
명령어로, 해당 명령어를 call win
으로 바꾸면 win
함수를 실행할 수 있다. x86-64에서 PIE가 걸려있는 경우 RIP relative addressing 기법을 사용한다. (Ref: https://software.intel.com/en-us/articles/introduction-to-x64-assembly)
call exit
를 실행하는 시점에서 rip 레지스터의 값은 0xACD이고 win
의 주소는 0xA21 이다. 즉 call 0xFFFFFF54
를 실행시켜 주어야 하며 해당 명령어를 assemble 하면 E8 54 FF FF FF 이다. 이 위치에 값을 쓰려면 z=-262887
이 되어야 하고 x^y=0x000000FFFFFF54E8
을 만족하는 x
와 y
를 입력하면 플래그를 볼 수 있다.
Last update: 10/22/2020
'Wargame > pwnable.xyz' 카테고리의 다른 글
pwnable.xyz / Free Spirit (0) | 2020.01.13 |
---|---|
pwnable.xyz / two targets (0) | 2020.01.13 |
pwnable.xyz / note (0) | 2020.01.13 |
pwnable.xyz / GrownUp (0) | 2020.01.13 |
pwnable.xyz / misalignment (0) | 2020.01.13 |