Quiz: Computer Architecture
- rax = 0x0123456789abcdef 일 때, ax의 값은? : 0xcdef
- rax = 0x0123456789abcdef 일 때, ah의 값은? : 0xcd
- rax = 0x0123456789abcdef 일 때, eax의 값은? : 0x89abcdef
- rax = 0x0123456789abcdef 일 때, al의 값은? : 0xef
- rax에서 rbx를 뺐을 때, ZF가 설정되었다. rax와 rbx의 대소를 비교하시오. : ==
(ZF(Zero Flag)는 결과가 0일 때 설정된다.)
Quiz: Linux Memory Layout
#include <stdlib.h>
int a = 0xa;
const char b[] = "d_str";
int c;
int foo(int arg) {
int d = 0xd;
return 0;
}
int main()
{
int *e = malloc(sizeof(*e));
return 0;
}
- d가 위치하는 세그먼트 : 스택
- e는 어느 세그먼트의 데이터를 가리키는가? : 힙
- foo(함수)가 위치하는 세그먼트는 어디인가? : 코드
- a(전역 변수)가 위치하는 세그먼트는 어디인가? : 데이터
- b(const)가 위치하는 세그먼트는 어디인가? : 읽기 전용 데이터 (rodata),
ㄴ Rodata는 해석하면 Read only data로, 읽기전용인 data들이 들어간다. 주로 상수, 상수형 문자열, printf의 중괄호 부분이 들어가며, 오직 읽기 전용이다.
- c(초기화되지 않은 전역 변수)가 위치하는 세그먼트는 어디인가? : BSS
ㄴ BSS 영역은 초기화 하기 전의 전역변수를 저장하고, 데이터 영역은 초기화 한 전역 변수를 저장한다.
- "d_str"이 위치하는 세그먼트는 어디인가? : 읽기 전용 데이터 (rodata)
Quiz: x86 Assembly 2
[Register]
rcx = 0
rdx = 0
rsi = 0x400000
=======================
[Memory]
0x400000 | 0x67 0x55 0x5c 0x53 0x5f 0x5d 0x55 0x10
0x400008 | 0x44 0x5f 0x10 0x51 0x43 0x43 0x55 0x5d
0x400010 | 0x52 0x5c 0x49 0x10 0x47 0x5f 0x42 0x5c
0x400018 | 0x54 0x11 0x00 0x00 0x00 0x00 0x00 0x00
=======================
[code]
1: mov dl, BYTE PTR[rsi+rcx]
2: xor dl, 0x30
3: mov BYTE PTR[rsi+rcx], dl
4: inc rcx
5: cmp rcx, 0x19
6: jg end
7: jmp 1
- end로 점프하면 프로그램이 종료된다고 가정하자. 프로그램이 종료됐을 때, 0x400000 부터 0x400019까지의 데이터를 대응되는 아스키 문자로 변환하면 어느 문자열이 나오는가?
1) 첫번째 시도
[Register]
rcx = 0
rdx = 0
rsi = 0x400000
=======================
[Memory]
0x400000 | 0x67 0x55 0x5c 0x53 0x5f 0x5d 0x55 0x10
0x400008 | 0x44 0x5f 0x10 0x51 0x43 0x43 0x55 0x5d
0x400010 | 0x52 0x5c 0x49 0x10 0x47 0x5f 0x42 0x5c
0x400018 | 0x54 0x11 0x00 0x00 0x00 0x00 0x00 0x00
=======================
[code]
1: mov dl, BYTE PTR[rsi+rcx] ; dl = 0x67 => rsi+rcx = 0x400000 주소의 값
2: xor dl, 0x30 ; dl = 0x57 => dl 값과 0x30을 xor 한 값
3: mov BYTE PTR[rsi+rcx], dl ; BYTE PTR[0x400000] = 0x57 => 0x400000 주소의 값에 0x57 대입
4: inc rcx ; rcx = 0x1 => rcx 값 1 증가
5: cmp rcx, 0x19 ; rcx < 0x19 (false) => 플래그 설정
6: jg end ; 직전에 비교한 두 피연산자 중 전자가 더 크면 end로 점프
7: jmp 1 ; 1로 되돌아감
- 이런 식으로 반복하면 0x400019까지의 각 주소에 있는 값에 0x30을 xor한 값이 들어가며, 그 결과는 다음과 같다.
[Memory]
0x400000 | 0x57 0x65 0x6c 0x63 0x6f 0x6d 0x65 0x20
0x400008 | 0x74 0x6f 0x20 0x61 0x73 0x73 0x65 0x6d
0x400010 | 0x62 0x6c 0x79 0x20 0x77 0x6f 0x72 0x6c
0x400018 | 0x64 0x21 0x00 0x00 0x00 0x00 0x00 0x00
Quiz: x86 Assembly 3
[Code]
main:
push rbp // rbp를 스택 상단에 넣는다
mov rbp, rsp // rbp에 rsp 값을 대입
mov esi, 0xf // esi = 0xf
mov rdi, 0x400500 // rdi = 0x400500
call 0x400497 <write_n> // write_n 함수를 실행
mov eax, 0x0 // eax = 0x0
pop rbp // 스택 상단값 제외
ret // 반환
write_n:
push rbp // rbp를 스택 상단에 넣는다
mov rbp, rsp // rbp = rsp
mov QWORD PTR [rbp-0x8],rdi // QWORD PTR [rbp-0x8] = 0x400500
mov DWORD PTR [rbp-0xc],esi // DWORD PTR [rbp-0xc] = 0xf
xor rdx, rdx // rdx = 0x0
mov edx, DWORD PTR [rbp-0xc] // edx = 0xf
mov rsi,QWORD PTR [rbp-0x8] // rsi = 0x400500
mov rdi, 0x1 // rdi = 0x1
mov rax, 0x1 // rax = 0x1
syscall // system call
pop rbp // 스택 상단값 제외
ret // 반환
==================================
[Memory]
0x400500 | 0x3037207964343372
0x400508 | 0x003f367562336420
- 다음 어셈블리 코드를 실행했을 때 출력되는 결과로 올바른 것은?
1) rax에 0x1을 넣고 System Call을 하는 것으로 write System Call임을 알 수 있다.
2) 시스템 콜 테이블은 아래 표를 참조.
3) rdi = 0x1, rsi = 0x400500, rdx = 0x0 후 edx = 0xf (edx는 rdx의 하위 32비트이다.)
4) write(0x1(stdout), 0x400500(출력할 데이터의 주소값), 0xf(문자열의 길이))
5) x86 아키텍처는 리틀 엔디안 방식(낮은 주소에 데이터의 끝 부분을 저장하는 방식)이므로,
72 33 34 64 79 20 37 30 / 20 64 33 62 75 36 3f를 해석하면 답이 나온다.
Quiz: Calling Convention
// Name: callconv_quiz.c
// Compile: gcc -o callconv_quiz callconv_quiz.c -m32
int __attribute__((cdecl)) sum(int a1, int a2, int a3){
return a1 + a2 + a3;
}
void main(){
int total = 0;
total = sum(1, 2, 3);
}
main:
0x080483ed <+0>: push ebp
0x080483ee <+1>: mov ebp,esp
0x080483f0 <+3>: sub esp,0x10
0x080483f3 <+6>: mov DWORD PTR [ebp-0x4],0x0 // total 변수 할당(esp 값만큼 공간 할당 - 스택 특성상 위로 자람(주소 값을 - 함))
0x080483fa <+13>: (a) // push 0x3
0x080483fc <+15>: (b) // push 0x2
0x080483fe <+17>: (c) // push 0x1
0x08048400 <+19>: call 0x80483db <sum> // sum 함수 호출
0x08048405 <+24>: (d) // add esp, 0xc => sum 함수 호출 이후 스택 공간 정리 - esp 값에 할당한 공간 크기(0xc - 12 Byte)만큼 더해 스택 공간 정리
0x08048408 <+27>: mov DWORD PTR [ebp-0x4],eax
0x0804840b <+30>: nop
0x0804840c <+31>: leave
0x0804840d <+32>: ret
- 호출 규약으로 cdecl을 사용함을 확인할 수 있다. 즉, 이는 x86 바이너리를 컴파일한 것이다.
- ebp : 현재 스택 프레임의 베이스 주소(최하단 주소)를 저장하고 있는 레지스터
- esp : 현재 스택의 최상단 주소를 저장하고 있는 레지스터
- 함수의 인자들은 스택에 거꾸로 push 된다.
- x86 아키텍처의 cdecl에서는 호출자가 스택 프레임을 정리한다.
// Name: callconv_quiz2
// Compile: gcc -o callconv_quiz2 callconv_quiz2.c
int sum(int a1, int a2, int a3){
return a1 + a2 + a3;
}
void main(){
int total = 0;
total = sum(1, 2, 3);
}
main:
0x00000000004004f2 <+0>: push rbp
0x00000000004004f3 <+1>: mov rbp,rsp
0x00000000004004f6 <+4>: sub rsp,0x10
0x00000000004004fa <+8>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400501 <+15>: (a) // mov edx, 0x3
0x0000000000400506 <+20>: (b) // mov esi, 0x2
0x000000000040050b <+25>: (c) // mov edi, 0x1
0x0000000000400510 <+30>: call 0x4004d6 <sum>
0x0000000000400515 <+35>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400518 <+38>: nop
0x0000000000400519 <+39>: leave
0x000000000040051a <+40>: ret
- 문제에서 SYSV를 적용하여 컴파일했다고 했으므로, 해당 바이너리는 x86-64 바이너리를 컴파일한 것이다.
- 여기서는 함수 인자를 rdi, rsi, rdx, rcx, r8, r9 에 차례로 저장하고 나머지 인자들은 스택에 push한다.
- 따라서 인자는 거꾸로 edx, esi, edi 순으로 push 된다. 인자의 크기가 작기 때문에 rdx, rsi, rdi의 하위 비트에 저장된다.
Quiz: Static Link vs. Dynamic Link
- runtime resolve를 하기 위한 코드는 plt에 들어 있다.
- PLT에서 GOT를 참조하여 실행 흐름을 옮기는 과정에는 어떠한 보호 기법도 존재하지 않는다.
- 정적 링크된 바이너리에서 처음 함수 func를 호출하면, runtime resolve를 거칠 필요가 없다. (모든 함수 코드가 이미 실행 파일에 포함되어 있다.)
- 동적 링크된 바이너리는 정적 링크된 바이너리에 비해 바이너리의 크기가 작다는 장점이 있다.
Quiz: RELRO
- No RELRO는 어떠한 RELRO 보호기법도 적용되지 않는 상태를 의미한다.
- Full RELRO에서는 .got 영역을 조작하여 실행 흐름을 조작할 수 없다.
- RELRO는 RELocation Read-Only의 줄임말이다.
- Partial RELRO에서는 .fini_array를 조작하여 실행 흐름을 조작할 수 없다. (Partial RELRO의 경우, .init_array와 .fini_array에 대한 쓰기 권한이 제거되어 두 영역을 덮어쓰는 공격을 하기 어려워진다. 하지만 .got.plt 영역에는 쓰기 권한이 존재한다.)
Quiz: PIE
- Shared Object는 실행할 수 있다.
- PIE가 적용된 바이너리는 무엇인가?
/bin/ls: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2f15ad836be3339dec0e2e6a3c637e08e48aacbd, for GNU/Linux 3.2.0, stripped
- PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재된다. 반대로, ASLR이 적용되지 않은 시스템에서는 PIE가 적용된 바이너리더라도 무작위 주소에 적재되지 않는다.
Quiz: Out of Bounds
#include <stdio.h>
int main() {
int buf[0x10];
unsigned int index;
scanf("%d", &index);
[A] // if (index >= 0x10) {exit(-1);} : 인덱스가 0x10을 넘어가지 않도록 함
printf("%d\n", buf[index]);
return 0;
}
- OOB 취약점을 방어하기 위해 [A] 위치에 들어갈 올바른 검증 코드는?
Quiz: ptmalloc2
- smallbin의 청크를 재할당할 때, unlink과정을 수행해야 한다.
- 청크를 재할당 할 때, bin를 먼저 비운 뒤 tcache에서 청크를 꺼낸다. - X
- largebin에 보관할 수 있는 청크의 갯수는 10개로 제한된다. - X
- 메모리 파편화를 막기위해 연속된 fastbin의 청크는 병합된다. - X, smallbin
Quiz: Double Free Bug
- 다음 중 double free가 발생한 것으로 가장 의심되는 free list를 고르시오.
=> 0x55c2efc412c0 ← 0x55c2efc41200 ← 0x55c2efc41250 ← 0x55c2efc412c0 (중복된 주소가 free list에 들어가 있음)
Quiz: SECCOMP
- Strict Mode : read, write, exit, sigreturn 시스템 콜의 호출만을 허용한다.
- SECCOMP 의 ALLOW LIST
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
- BPF(Berkeley Packet Filter) :
BPF_LD, BPF_JMP, BPF_JEQ, BPF_RET, BPF_STMT, BPF_JUMP
Quiz: Master Canary
- TLS : Thread Local Storage
- TLS의 0x28 오프셋에 마스터 카나리가 저장된다.
- gdb에서 FS에 주소를 참조하기 위한 키워드는? : $fs_base
- FS를 초기화하기 위해 사용하는 시스템 콜 : arch_prctl
Quiz: _rtld_global
- 프로그램이 종료될 때 호출되는 함수 : __GI_exit, _dl_fini, __rtld_lock_lock_recursive
- _rtld_global 구조체가 위치하는 영역 : /lib/x86_64-linux-gnu/ld-2.27.so
Quiz: SigReturn-Oriented Programming
- 다음 중 올바르지 않은 시그널 종류는? : SIGEXIT
include/linux/signal.h 일부
* +--------------------+------------------+
* | POSIX signal | default action |
* +--------------------+------------------+
* | SIGHUP | terminate |
* | SIGINT | terminate |
* | SIGQUIT | coredump |
* | SIGILL | coredump |
* | SIGTRAP | coredump |
* | SIGABRT/SIGIOT | coredump |
* | SIGBUS | coredump |
* | SIGFPE | coredump |
* | SIGKILL | terminate(+) |
* | SIGUSR1 | terminate |
* | SIGSEGV | coredump |
* | SIGUSR2 | terminate |
* | SIGPIPE | terminate |
* | SIGALRM | terminate |
* | SIGTERM | terminate |
* | SIGCHLD | ignore |
* | SIGCONT | ignore(*) |
* | SIGSTOP | stop(*)(+) |
* | SIGTSTP | stop(*) |
* | SIGTTIN | stop(*) |
* | SIGTTOU | stop(*) |
* | SIGURG | ignore |
* | SIGXCPU | coredump |
* | SIGXFSZ | coredump |
* | SIGVTALRM | terminate |
* | SIGPROF | terminate |
* | SIGPOLL/SIGIO | terminate |
* | SIGSYS/SIGUNUSED | coredump |
* | SIGSTKFLT | terminate |
* | SIGWINCH | ignore |
* | SIGPWR | terminate |
* | SIGRTMIN-SIGRTMAX | terminate |
* +--------------------+------------------+
* | non-POSIX signal | default action |
* +--------------------+------------------+
* | SIGEMT | coredump |
* +--------------------+------------------+
- 컨텍스트 스위칭이 일어날 때 커널 코드의 실행을 마친 후, 기억한 정보를 되돌려 복귀할 때 사용하는 syscall 이름은 무엇인가? : sigreturn
- restore_sigcontext 함수에서 복귀할 때 컨텍스트를 복구하기 위해 사용되는 구조체는 무엇인가? : sigcontext
Quiz: _IO_FILE
- 다음 중 _IO_jump_t 구조체의 함수 테이블 멤버 함수가 아닌 것은? : _IO_flush_t
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};
- 리눅스 시스템의 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체 : _IO_FILE
- _IO_FILE 구조체에서 _flags 필드 중 _IO_MAGIC 의 값은 무엇인가? : 0xFBAD0000
'프로그래밍 > 해킹' 카테고리의 다른 글
간단한 해킹 문제 모음 (2) | 2024.09.04 |
---|---|
리버싱 퀴즈 모음 (0) | 2024.08.31 |
웹 해킹 퀴즈 모음 (0) | 2024.08.30 |
Format String Bug (0) | 2024.08.18 |
PLT / GOT / libc database (0) | 2024.08.15 |