hint를 보자
처음 보는 것들이 나왔다.
sd_set? FD_ZERO, FD_SET, select, FD_ISSET??
검색해 보자.
blog.naver.com/tipsware/220810795410
여기 형님이 아주 제대로 정리해 주셨다👍
FD_ZERO(&fds)
- fds 스트럭쳐(32bytes == 1024bits)를 0으로 초기화.
- fds 스트럭쳐는 1024칸의 배열이라고 생각하면 편하다.
FD_SET(STDIN_FILENO,&fds)
- STDIN_FILENO 가 뭐지?
- 다음 내용을 보며 좀 더 알아보자
흔히 유닉스 시스템에서 모든 것은 파일이라고 한다. 일반적인 정규파일(Regular File)에서부터 디렉토리(Directory), 소켓(Socket), 파이프(PIPE), 블록 디바이스, 캐릭터 디바이스 등등 모든 객체들은 파일로써 관리된다. 유닉스 시스템에서 프로세스가 이 파일들을 접근할 때에 파일 디스크립터(File Descriptor)라는 개념을 이용한다.
파일 디스크립터는 '0이 아닌 정수', 'Non-negative Integer' 값이다. 즉, 음수가 아닌 0과 양수인 정수 값을 갖는다. (unsigned int 값이라고 보면 된다.) 프로세스가 실행 중에 파일을 Open 하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해 준다. 그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근 할 때, FD 값을 이용해 파일을 지칭 할 수 있다.
프로그램이 프로세스로 메모리에서 실행을 시작 할 때, 기본적으로 할당되는 파일 디스크립터들이 있다. 바로 표준 입력(Standard Input), 표준 출력(Standard Output), 표준 에러(Standard Error)이다. 이 들에게 각각 0, 1, 2 라는 정수가 할당되며, POSIX 표준에서는 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO로 참조된다. 이 매크로는 <unistd.h> 헤더 파일에서 찾아 볼 수 있다.
3가지 파일 디스크립터가 언제나 열린채로 동작 한다.
-------위의 블로그 발췌-------
쉽게 말하면
File Descripter 는 열려있는 파일들의 고유 식별값 이고
프로세스가 실행 될 때 기본적으로 표준입력파일, 표준출력파일, 표준에러파일이 열러있다는 뜻이다.
FD_SET(STDIN_FILENO,&fds) 함수는 표준입력파일에 해당하는 FD를 1로 SET 하겠다는 뜻이다.
실제로 FD 스트럭쳐인 fds 의 1024개의 bit 중에서 STDIN_FILENO == 0 에 해당하는 맨 앞 bit가 1로 세팅된다.
1로 세팅하는게 무슨의미인가?
- FD_SET 은 여러번 할 수 있다.
- FD_SET(3,&fds);
- FD_SET(5,&fds);
- FD_SET(7,&fds); 이런식으로.
- 이처럼 1로 세팅된 여러 FD들 중에 데이터의 변화가 생긴 것을 select 함수를 통해 알아낼 수 있다.
if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1)
원형 int select(int mafdl, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_SETSIZE(1024) 미만 중 FD_SET 한 FD 의 이벤트(데이터의 변화)가 발생했을 때
이벤트가 발생한 FD의 갯수를 반환한다.
우리는 0만 SET 했기 때문에 그거 하나만 체크한다.
두번째, 세번째, 네번째 인자
readfds: '읽기'가 가능한지 감시. (read() 작업이 가능한지)
writefds: '쓰기'가 가능한지 감시. (write() 작업이 가능한지)
exceptfds: 예외가 발생했거나 대역을 넘어서는 데이터(소켓)가 존재하는지 감시.
즉 우리는 "읽을 수 있는 데이터가 생겼다!" 라는 이벤트를 기다리는 것이다!
5번째 인자인 struct timeval 이 NULL 이면 FD신호가 생길 때까지 무한정 대기한다.
select() 함수의 특징으로는 0, 3, 5, 7 FD를 SET 했을 때
0에서 이벤트가 발생했다면 0만 1로 남기도 나머지는 모두 0으로 바꾼다는 것이다.
select로 이벤트가 발생되었다는 것을 알아냈으면
이제 어떤 FD에서 발생했는지를 알아야 한다.
FD_ISSET(fileno(stdin),&fds) 는
fds 스트럭쳐에 있는 stdin(0) 이 1로 셋팅되어 있니? == 0에서 이벤트가 발생 했니?
이다.(fileno(fileptr) 함수 : File Descripter 반환)
read( fd, buffer, count ) : fd가 가리키는 파일에서 최대 count 바이트를 읽어 buffer에 저장.
read(fileno(stdin),&x,1);
- 표준입력파일에서 최대 1byte를 읽어서 x에 저장해~
이제 어느정도 함수들에 대해 이해를 했으니 문제의 코드를 해석하면 이런느낌이지 않을까 싶다.
.
.
.
else
{
FD_ZERO(&fds); //1024bit인 fds 스트럭쳐를 0으로 초기화 해
FD_SET(STDIN_FILENO,&fds); //FD가 STDIN_FILENO(0) 에 해당하는 파일(표준입력파일)에서
데이터가 변하는지(이벤트가 발생하는지) 체크할거니까 1로 세팅해놔
if(select(FD_SETSIZE, &fds, NULL, NULL, NULL) >= 1) //fds 스트럭쳐에서
FD_SETSIZE(1024) 미만(0~1023 모든 FD)에 대해서 1로 세팅되어있는
애들중에 '읽기' 이벤트가 발생한 애들 수를 반환해
이벤트가 발생할 때까지 계속 기다려.
{
if(FD_ISSET(fileno(stdin),&fds)) //fds 스트럭쳐에 있는 stdin(0) 이 1로 셋팅되어 있니?
== 0에서 이벤트가 발생 했니?
{
read(fileno(stdin),&x,1); //표준입력파일에서 최대 1byte를 읽어서 x에 저장해~
switch(x)
{
.
.
.
fflush(stdout);
- 혹시 남아있을지 모를 출력버퍼를 비워준다.
이해가 잘 안된다면 아래 블로그 참고
여기까지 오는데 오래 걸렸는데
결론적으로 초초 요약하면 뭐다?
"문자를 입력받는다" 이다............
참 힘들게 하네
이제 다시 전체 코드를 보자.
check가 0xdeadbeef 일때 level19의 유저권한으로 shell을 실행하는 shellout 함수가 실행된다.
그리고 문자가 입력되면 1byte 씩 x에 저장하고 switch 문으로 들어간다.
주목해야 하는 것은 case 0x08: 과 default: 이다.
0x08 일 때 count가 -1 된다. (참고로 0x08는 아스키코드표를 보면 BackSpace 이다.)
그리고 default에서 string의 인덱스로 count가 들어가고 그곳에 x를 입력한다.
그러다는 것은 string[-n] 과 같이 만들어서 string이 아닌 메모리에 덮어 쓸 수 있다는 뜻이다.
그리고 그곳이 check의 메모리 공간이고 거기에 0xdeadbeef를 덮어씌우면?
끝.
자 그러면 각 변수들의 스텍영역 메모리구조를 알아보기 위해 gdb를 해보자.
set disassembly-flavor intel 코드가 뭔지 궁금한 사람
------------------------------------------------------------------------------------------------
set disassembly-flavor intel 은 어셈블리코드를 intel문법으로 바꿔주는 명령어이다.
어셈블리코드를 작성하는 문법으로 at&t, intel 두가지가 있다.
일반적으로 gdb를 하고서 보이는 코드는 at&t 문법이다.
상황에 따라 intel 문법으로 보는게 더 편할때 사용하고 있다.
------------------------------------------------------------------------------------------------
너무 길어서 필요한 부분만 짤라서 보겠다.
<main+3>을 보면 esp에서 0x100를 뺐으므로 256byte 만큼 메모리를 할당했다는 뜻이다.
c코드를 보면 x와 count를 0으로 초기화 했다.<main+12>, <main+19> 를 보면 [ebp-108], [ebp-112]에 0을 넣었다.그러다는 건
[ebp-108] == x, [ebp-112] == count 라는 것이다.
<main+91>에서 [ebp-104] 를 0xdeadbeef 와 비교하므로
[ebp-104] == check 이다.
이 부분은 string에 접근하는 default: 부분이다.
첫번째 줄에서 [ebp-100]의 값을 eax로 가져온다.
[ebp-100]가 바로 string의 시작주소이다.
그 밑으로 보면 [ebp-112]에서 count값을 가져오는 것,[ebp-108]에서 x값을 가져오는 것을 볼 수 있다.
그리고 inc [ebp-112]를 통해 [ebp-112]가 count라는 것을 확실하게 알 수 있다.
메모리구조를 대략적으로 그려보면 다음과 같다.
다 왔다!
이제 우리가 해야 할 것은 0x08을 4번 주어서 count를 -4 로 만든다음에
string[count] 로 접근해서 check에 0xdeadbeef 를 덮어씌우면 된다!
예에에에~~~~~ 무사히 level19의 유저권한을 얻고 pw를 알아냈다!
도움이 되었다면 아래에 하트 눌러주기~
참고사이트
www.joinc.co.kr/w/Site/system_programing/File/select
'C, HackerSchool FTZ' 카테고리의 다른 글
해커스쿨 FTZ level20 (HackerSchool) 풀이 (2) | 2021.11.18 |
---|---|
해커스쿨 FTZ level19 (HackerSchool) 풀이 (0) | 2021.03.29 |
해커스쿨 FTZ level17 (HackerSchool) 풀이 (0) | 2020.12.28 |
해커스쿨 FTZ level16 (HackerSchool) 풀이 (0) | 2020.12.28 |
댓글