처음에 Immunity Debugger로 문제 파일을 연 후 F8키를 눌러보면서 한줄씩 실행시켜보았다.
위와 같은 어셈블리 코드와 메세지창을 볼 수 있었고 CD-ROM이 맞다는 메세지 창이 뜨도록 만드는 것이 이 문제의 목표였기 때문에 아래 그림의 코드 부분을 유심히 보았다.
일단 00401026 주소에 있는 JE 명령어는 어셈블리의 분기 명령어 중 하나로 Jump if equal 즉, CMP로 비교한 결과 값이 같다면 참이 되고, 이때 해당 주소로 점프하는 기능을 가진 명령어이다.
수정하기 전의 코드에서 CMP 명령어를 처리해줄때 EAX 레지스터에는 00000001 값이 들어있고 ESI에는 00401003 값이 들어있기 때문에 두 결과값은 같지 않으므로 0040103D라는 주소로 넘어가지 않고 그냥 그 다음주소(00401028)로 넘어간다.
위와 같이 옆에 주석 부분에서 볼 수 있듯이 Ok, I really think that your HD is a CD-ROM이라는 메세지가 있는 MessageBox가 실행되도록 하려면 CMP를 해준 값이 같도록 하여 JE를 만났을때 해당 주소(0040103D)로 넘어갈 수 있게 만들어주면 될 것이라고 생각했다.
그래서 위와 같이 MOV EAX, ESI를 해주어 두 값이 같도록 수정했더니 제대로 메세지 창이 뜨는 것을 확인할 수 있었다.
MOV 명령어는 operend1값에 operend2 값을 넣어준다는 의미이다.
또 다른 풀이법으로는 JE 명령어를 JMP 명령어로 바꿔주는 방식이 있다.
JE 명령어는 CMP해준 두 값이 같아야만 점프를 하는 반면 JMP 명령어는 무조건 해당 주소값으로 점프를 한다.
그렇기 때문에 위와 같이 JE를 JMP로 수정해주면 해당 주소로 잘 넘어가고 원하는 메세지 창이 뜨게 할 수 있다.
이외에도 레지스터의 값을 증가시키거나 감소시키는 코드로 수정하여 EAX와 ESI의 값을 같게 만들어주는 방법도 있을 것이다.
리눅스 시스템에서 구동 중인 프로세스 목록은 프로세스를 관리하는 태스크 디스크립터의 연결 리스트(init_task.tasks)에 접근하여 등록된 프로세스를 출력한다.
1) 프로세스를 식별하는 필드
→ comm은 TASK_COMM_LEN 크기의 배열이며 프로세스 이름을 저장한다.
→ 프로세스 이름들은 태스크 디스크립터를 나타내는 task_struct 구조체의 comm 필드에 접근해서 출력한다.
ex) 디버깅용 코드
현재 실행 중인 프로세스의 태스크 디스크립터 구조체 task_struct 구조체에 접근하는 current 매크로 + 프로세스 이름을 저장하는 comm 필드 -> 다양한 디버깅용 코드 작성 가능
task_struct *p가 깨우려는 프로세스의 태스크 디스크립터
프로세스 이름이 kthreadd인 경우 dump_stack( ) 함수를 호출해 함수 호출 흐름을 커널 로그로 출력
▶ pid : 프로세스를 식별하는 정수형 값
- 프로세스 아이디 : pid_t pid;
- 스레드 그룹 아이디 : pid_t tgid;
2) 프로세스 상태 저장
▶ 태스크 디스크립터에서 프로세스 상태를 관리하는 두 가지 필드
① state : 프로세스 실행 상태
- TASK_RUNNING : CPU 에서 실행 중이거나 런큐에서 대기 상태에 있음 - TASK_INTERRUPTIBLE : 휴면 상태 - TASK_UNINTERRUPTIBLE : 특정 조건에서 깨어나기 위해 휴면 상태로 진입한 상태
② flags : 프로세스 세부 동작 상태와 속성 정보
→ PF_*로 시작하는 매크로 필드를 OR 연산한 결과를 저장
- PF_IDLE : 아이들 프로세스 - PF_EXITING: 프로세스가 종료 중인 상태 - PF_EXITPIDONE : 프로세스가 종료를 마무리한 상태 - PF_WQ_WORKER : 프로세스가 워커 스레드인 경우 - PF_KTHREAD : 프로세스가 커널 스레드인 경우
▶ exit_state : 프로세스 종료 상태를 저장
- EXIT_DEAD
- EXIT_ZOMBIE
- EXIT_TRACE
▶ exit_code : 프로세스의 종료 코드를 저장
3) 프로세스 간의 관계
▶ 프로세스 간의 관계를 나타내는 필드
① struct task_struct *real_parent : 자신을 생성한 부모 프로세스의 태스크 디스크립터 주소 저장
② struct task_struct *parent : 부모 프로세스의 태스크 디스크립터 주소 저장
※ *real_parent와 *parent의 차이점
→ 부모 프로세스가 종료되지 않고 실행 중이면 같음
→ 프로세스 계층 구조에서 지정한 부모 프로세스가 없을 경우 init 프로세스를 부모 프로세스로 변경하면 다름
위와 같이 부모 프로세스가 종료되면 do_exit( ) 함수에서 화살표 방향으로 함수 호출
forget_original_parent( )와 find_new_reaper( )에서 새로운 부모 프로세스 지정
③ struct list_head children : 부모 프로세스가 자식 프로세스를 생성할 때 children 연결 리스트에 자식 프로세스 등록
④ struct list_head sibling : 같은 부모 프로세스로 생성된 프로세스의 연결 리스트 주소 저장
※ children과 sibling 필드의 연결 방식
▶ ps axjf : 부모와 자식 프로세스 관계 확인
rcu_gp, rcu_par_gp, kworker/0, mm_percpu_wq, ksoftirqd/0 프로세스들의 부몸 프로세스는 kthreadd임을 알 수 있다.
※ 부모 프로세스인 kthreadd 입장에서의 태스크 디스크립터
- kthreadd 프로세스 태스크 디스크립터의 children 필드는 연결리스트
- 연결리스트 헤드에 자식 프로세스의 task_struct 구조체의 sibling 필드 주소를 저장
- kworker/0:0H의 입장에서 mm_percpu_wq와 ksoftirpd/0 프로세스는 자신의 sibling 연결 리스트로 이어져 있음
4) 프로세스 연결 리스트
→ task_struct 구조체의 tasks 필드는 list_head 구조체로서 연결 리스트 타입
※ tasks 필드는 언제 init 프로세스의 태스크 디스크립터 필드 중 연결 리스트 타입이 tasks 필드에 등록될까?
▶ copy_process( ) 함수 : 프로세스를 생성할 때 호출
12번째 줄 실행 시 커널로부터 태스크 디스크립터 할당받음
13번째 줄 실ㄹ행 시 init_task.tasks 연결 리스트의 마지막 노드에 현재 프로세스의 task_struct 구조체의 tasks 주소 등록
▶ tasks 필드를 TRACE32로 확인해보기
6번째 줄에서 가리키는 주소의 의미는?
→ init_task.tasks 연결 리스트에 추가된 다음 프로세스의 task_struct 구조체의 tasks 필드 주소
즉, 0xA1618310 주소는 연결 리스트에 등록된 다음 프로세스 태스크 디스크립터의 next 필드 주소를 의미
→ 이 방식으로 커널에서 구동 중인 모든 프로세스의 태스크 디스크립터 주소를 알 수 있음
5. 프로세스 실행 시각 정보
▶ 태스크 디스크립터에서 프로세스의 실행 시각 정보를 알 수 있는 필드
① u64 utime : 유저 모드에서 프로세스가 실행한 시각
② u64 stime : 커널 모드에서 프로세스가 실행한 시각
③ struct sched_info sched_info.last_arrival : 프로세스 스케줄링 정보 저장
※ sched_info.last_arrival 필드는 언제 변경될까?
→ sched_info_arrive( ) 함수의 9번째 줄에서 업데이트됨
※ sched_info_arrive( ) 함수는 언제 호출될까?
→ context_switch( ) 함수 내에서 컨텍스트 스위칭을 수행하기 직전에 prepare_task_switch( ) 함수를 호출하는데, 이 함수를 따라가다 보면 호출됨