0. 사담

옛날에는 어셈블리코드를 한땀한땀 분석하는 맛이 있었는데 IDA를 맛본 후로는 그 고생했을 때의 재미는 알고 있지만 하기가 싫어진다. 그냥 딸깍 하고 싶다.


1. rev-basic-0

바로 IDA를 통해 디컴파일 해봤다.

  • s1 : 입력값
  • s2 : 비교값
  • v5 : 스택 카나리

s1 변수는 40바이트 크기의 배열로 scanf 함수를 통해 입력값을 받는다.

s2 변수는 아직까지는 어떤 값이 들어있는지 모른다.

v5는 스택 카나리라는 건데 컴파일러가 버퍼 오버플로우 보호용 코드로 삽입해놓은 코드이다. 보통 버퍼 오버플로우는 스택을 이용한다. 스택에서 base point, return address 값을 변경하여 공격자가 원하는 함수로 이동시킨다. 두 값을 변경하기 위해서는 공격 가능한 스택의 부분부터 아래에 있는 base point, return address 부분까지 값을 넣어서 덮어 씌워야 한다. 스택 카나리는 스택의 중간(보통 base point 바로 위로 알고 있음)에서 버퍼 오버플로우 시도의 검증값의 역할로써 그 값이 달라지면 오버플로우가 발생했다는 것을 알려준다.

 

9번 줄을 보면 strcmp 함수를 통해서 s1과 s2를 비교하는데 같으면 "Success!" 문자열을, 다르면 "Hmm.." 문자열을 출력한다.

 

그럼 s2에 어떤 값이 들어있는지 알고 우리가 그 값을 동일하게 입력만 한다면 문제를 풀 수 있을 것이다. 사실 s2에 있는 문자가 문제의 답일 것이다. 왜냐? 그걸 똑같이 입력해야 성공이라고 뜨니까

 

이렇게 더블클릭하면 변수의 실제 바이너리의 주소를 보여준다.

어떤 의미냐면 변수형은 char 포인터이고 s2에는 _data 행에 있는 "K0{very_easy_man~}"의 주소가 들어있는 것이다. 

s2에 문자열이 들어가 있는게 아니라 문자열의 주소가 들어가 있는 것이다.(그거나 그거나다 이 문제를 풀 때는)

 

2. 정답 확인

리눅스에서 실행시켜본다.

 

이렇게 틀린 값을 넣으면 "Hmm.." 문자열이 뜨고...

 

 

"K0{very_easy_man~}" 값을 넣으면 "Success!"문자열이 뜬다!


99. 후기

원래는 rev-basic-0 문제가 너무 간단해서 0부터 3까지 하나의 글로 작성하려고 했는데 빨리 자야해서 문제 별로 글을 써야 겠다.

0. 사담

회사에서 혼날때마다 뭔가 공부해야겠다는 맘이 샘솟지만 이상하게 집에만 오면 싹 가라앉는다. 뭔가 블로그에 적고는 싶은데 너무 귀찮긴 하다. 리버싱 스터디때 풀고 풀이를 블로그에 다 적어야지라고 맘 먹었지만 안적어서 많이 밀린 문제 풀이를 적어보려고 한다. 여기는 리버싱이 아니라 IDA랑 코딩만 할 줄 알아도(사실 코딩 없어도 눈치로라도 맞출 수 있는) 그런 문제들 풀이다.

 

그리고 IDA 사용법도 좀 익숙해지거나 다른 디스어셈블러 하나에 정착할 필요를 조금 느끼긴 했다. 기드라나 IDA 둘 중에 하나 골라서 사용법을 좀 정리해 보려고 한다. (기드라로 할 가능성 80%)


1. Handray - 1

입력값에 따른 실행 결과를 예측하여 flag를 제출하라!

입력값 : 65741

 

IDA 사용법을 대충 적으면서 넘어가겠다. 일단 IDA를 키고 분석할 파일을 IDA로 드래그앤 드롭을 하면 이런 창이 나오는데 그냥 아무것도 만지지말고 OK만 눌러주면 된다.

 

 

OK를 누르면 해당 어셈블리어가 나열된 뭔가 뭔가를 볼 수 있다. 맨 위 네모에서 두가지로 나뉘는데 조건 분기를 표현한 것이다. 사실 전에 올리디버그를 사용해서 이런 친절함이 익숙하지 않다. 여기서 F5를 눌러보자

 

 

 

얼마나 놀라운 기능인가! 어셈블리어를 보기좋은 코드로 바꿔주는 기술! 전에 올리디버그만 알았던 시절이였다면 그저 하나하나 해석해가면서 "이런 동작을 하는 프로그램이다!"라고 했겠지만 IDA를 알고있는 지금은 그저 딸깍이다. 그리고 elf 파일이라서 올리디버그로 보지도 못했을거 같기도 한데 그건 잘 모르겠다.

 

위에 코드를 딱 보면 printf로 "odd"랑 "even"을 조건에 따라 출력하는 모습을 볼 수 있다. 눈치가 조금이라도 있으면 홀짝을 구분하는 프로그램인걸 알 수 있다. 그래서 정답은 "odd"라고 유추할 수 있지만 문제에 장난질을 쳤을 수도 있기에 조건문을 살짝 살펴봤다.

 

if((v4&1) != 0)

이것은 무엇을 하는 코드일까? v4를 1과 and 연산하여 0이 아니면 odd를 출력, 반대로 0이면 even을 출력하는 것이다.

홀짝은 숫자의 마지막 비트가 1이면 홀수이고 0이면 짝수이니 1과 어떤 숫자를 and 했을 떄 1이 나올려면 어떤 숫자의 마지막 비트는 1이여야 할 것이다. 이것으로 볼때 문제에 장난질을 하지 않았다는 사실을 알 수 있다.

 

 

__isoc99_scanf(a1: &unk_402004, a2: &v4, a3: envp);

 

추가적으로 바로 위에있는 함수도 확인해 보았다. 함수명에서부터 알 수 있듯 이것은 scanf인 것이다. scanf()는 첫 번쨰 인자로 문자열을 받는 서식을 받는 것이고, 두번째 인자로 해당 서식을 적용한 값이 두번째 인자로 저장되는 함수이다. 숫자를 입력 받을라면 "%d"라는 서식으로 지정해야 하고, v4가 저장될 위치인 것이다. 그래서 &unk_402004를 더블클릭 해보면 "%d"문자열이 저장되어 있는 것을 볼 수 있다.

Hex-View에서도 볼 수 있음

 

뭐 이러쿵 저러쿵 한 말이 많아졌지만 결국 답은 "odd"라는 것이다.

 

 

극적인 마무리를 위해서 linux에서 실행시켜 보았다. odd가 출력되는 것을 볼 수 있다.


2. Handray - 2

2번부터는 빠르게 간다.

입력값에 따른 실행 결과를 예측해 flag로 제출해주세요!

입력값 : 12314831

어떤 값을 입력 받으면 2부터 입력받은 숫자까지 반복문을 도는데 입력받은 숫자랑 돌고있는 반복횟수를 나눠서 나머지가 0이면 false를 출력 전부 돌았는데도 나머지가 0인 수가 없으면 true를 출력하는 것이다.

 

대충 봐도 소수인지 아닌지 판별하는 프로그램인 것이다. (왜 v4/2 이후의 값을 도는지는 모르겠지만..)

계산이 매우 귀찮으니 소인수분해 계산기를 통해서 또는 소수 판별기를 통해 소수인지 알아낸다. 소수가 아니란다. 

정답은 False


3. Handray - 3

입력값에 따른 실행 결과를 예측해 flag로 제출해주세요!

입력값 : GoodMorning!

보면 문자열을 입력받아 15번 반복문을 도는데 각 문자가 97, 101, 105, 111, 117 이면 v6에 카운트를 하나씩 증가시켜 몇개가 나왔는지 마지막에 v6를 출력하는 프로그램이다. 

 

97, 101, 105, 111, 117은 무엇이냐? 소문자 영모음이다. a, e, i, o, u가 몇개인지 보는건데 입력값에는 4개가 있는것을 볼 수 있으니 정답은 4!


99. 후기

자고싶기도 하고 IDA 딸깍하는 문제라서 너무 쉽기도 해서 2, 3번은 너무 대충한 감이 없지않아 있다. 혼나도 집에만 오면 공부할 맘이 싹 가라앉는데 회사에서 안혼나면 공부를 어떻게 하나 싶다. 자주자주 혼나야겠다.

0. 사담

스터디 모임을 하고 있는데 문제가 나와서 그냥 풀기엔 내 머리속에서 휘발될거 같아서 좀 끄적여 보려고 한다. 답이랑 풀이과정이 틀릴수 있으니 언제나 반박해 줬으면 좋겠다.


1. 실행 후 eax의 값은?

0x0000000000001131 <main+8>:	mov    DWORD PTR [rbp-0x10],0x1
0x0000000000001138 <main+15>:	mov    DWORD PTR [rbp-0xc],0x3
0x000000000000113f <main+22>:	mov    DWORD PTR [rbp-0x8],0x5
0x0000000000001146 <main+29>:	mov    edx,DWORD PTR [rbp-0x10]
0x0000000000001149 <main+32>:	mov    eax,DWORD PTR [rbp-0xc]
0x000000000000114c <main+35>:	add    edx,eax
0x000000000000114e <main+37>:	mov    eax,DWORD PTR [rbp-0x8]
0x0000000000001151 <main+40>:	add    eax,edx

 

 

풀이 방법으로 선택한 것은 문제의 어셈블리 코드를 C코드로 변환하는 방법이다.

int a = 1; //[rbp-0x10]
int b = 3; //[rbp-0xc]
int c = 5; //[rbp-0x8]

int result = c + (a + b)

 

eax가 rpb-0x10과 같이 저장공간을 따로 받지는 않았지만 보기 좋게 result로 표현을 해봤다.

그래서 답은 9

 

2.  실행 후 DWORD PTR [rbp-0x10] 의 값은?

0x0000000000001131 <main+8>:	mov    DWORD PTR [rbp-0x10],0x0
0x0000000000001138 <main+15>:	mov    DWORD PTR [rbp-0xc],0x2
0x000000000000113f <main+22>:	mov    DWORD PTR [rbp-0x8],0x3
0x0000000000001146 <main+29>:	mov    eax,DWORD PTR [rbp-0xc]
0x0000000000001149 <main+32>:	imul   eax,DWORD PTR [rbp-0x8]
0x000000000000114d <main+36>:	mov    DWORD PTR [rbp-0x4],eax
0x0000000000001150 <main+39>:	cmp    DWORD PTR [rbp-0x4],0x6
0x0000000000001154 <main+43>:	jne    0x115f <main+54>
0x0000000000001156 <main+45>:	mov    DWORD PTR [rbp-0x10],0xff
0x000000000000115d <main+52>:	jmp    0x1166 <main+61>
0x000000000000115f <main+54>:	mov    DWORD PTR [rbp-0x10],0x0
0x0000000000001166 <main+61>:	mov    eax,DWORD PTR [rbp-0x4]
0x0000000000001169 <main+64>:	and    DWORD PTR [rbp-0x10],eax

 

변환!

int a = 0 // [rbp-0x10]
int b = 2 // [rbp-0xc]
int c = 3 // [rbp-0x8]


int d = b * c // [rbp-0x4]

if(d != 6){
	a = 0xff
} else {
	a = 0
}

a = d & a

 

이러면 되지 않을까? 

그래서 답은 6

 

3. 실행 후 DWORD PTR [rbp-0x38]의 값은?

0x0000000000001164 <main+27>:	mov    DWORD PTR [rbp-0x30],0x1
0x000000000000116b <main+34>:	mov    DWORD PTR [rbp-0x2c],0x2
0x0000000000001172 <main+41>:	mov    DWORD PTR [rbp-0x28],0x3
0x0000000000001179 <main+48>:	mov    DWORD PTR [rbp-0x24],0x4
0x0000000000001180 <main+55>:	mov    DWORD PTR [rbp-0x20],0x5
0x0000000000001187 <main+62>:	mov    DWORD PTR [rbp-0x1c],0x6
0x000000000000118e <main+69>:	mov    DWORD PTR [rbp-0x18],0x7
0x0000000000001195 <main+76>:	mov    DWORD PTR [rbp-0x14],0x8
0x000000000000119c <main+83>:	mov    DWORD PTR [rbp-0x10],0x9
0x00000000000011a3 <main+90>:	mov    DWORD PTR [rbp-0xc],0xa
0x00000000000011aa <main+97>:	mov    DWORD PTR [rbp-0x38],0x0
0x00000000000011b1 <main+104>:	mov    DWORD PTR [rbp-0x34],0x0
0x00000000000011b8 <main+111>:	jmp    0x11ca <main+129>
0x00000000000011ba <main+113>:	mov    eax,DWORD PTR [rbp-0x34]
0x00000000000011bd <main+116>:	cdqe
0x00000000000011bf <main+118>:	mov    eax,DWORD PTR [rbp+rax*4-0x30]
0x00000000000011c3 <main+122>:	add    DWORD PTR [rbp-0x38],eax
0x00000000000011c6 <main+125>:	inc    DWORD PTR [rbp-0x34]
0x00000000000011ca <main+129>:	cmp    DWORD PTR [rbp-0x34],0x9
0x00000000000011ce <main+133>:	jle    0x11ba <main+113>

 

 

변환!

int a[10] = [1,2,3,4,5,6,7,8,9,10]; // [rbp-0x30 ~ rbp-0xc]
int b = 0; // [rbp-0x38]
int c = 0; // [rbp-0x34]

while(c <= 9){
    b = b + a[c];
    c++;
}

 

일단 main+118에서 일정한 간격마다 값을 참조하기 떄문에 배열로 표현을 해봤다.

코드 또한 1부터 10까지 더하는 것이니

답은 55

 

4. 실행 후 rbp-0x10에 해당하는 값은?

0x000000000040110e <+8>:     mov    DWORD PTR [rbp-0x40],0x1
0x0000000000401115 <+15>:    mov    DWORD PTR [rbp-0x3c],0x3
0x000000000040111c <+22>:    mov    DWORD PTR [rbp-0x38],0x7
0x0000000000401123 <+29>:    mov    DWORD PTR [rbp-0x34],0xd
0x000000000040112a <+36>:    mov    DWORD PTR [rbp-0x30],0x15
0x0000000000401131 <+43>:    mov    DWORD PTR [rbp-0x2c],0x1
0x0000000000401138 <+50>:    mov    DWORD PTR [rbp-0x28],0xd
0x000000000040113f <+57>:    mov    DWORD PTR [rbp-0x24],0x1b
0x0000000000401146 <+64>:    mov    DWORD PTR [rbp-0x20],0xd
0x000000000040114d <+71>:    mov    DWORD PTR [rbp-0x1c],0x1
0x0000000000401154 <+78>:    mov    DWORD PTR [rbp-0x4],0x1e
0x000000000040115b <+85>:    mov    DWORD PTR [rbp-0x8],0x0
0x0000000000401162 <+92>:    mov    DWORD PTR [rbp-0xc],0x0
0x0000000000401169 <+99>:    jmp    0x4011a3 <main+157>
0x000000000040116b <+101>:   mov    eax,DWORD PTR [rbp-0xc]
0x000000000040116e <+104>:   cdqe
0x0000000000401170 <+106>:   mov    eax,DWORD PTR [rbp+rax*4-0x40]
0x0000000000401174 <+110>:   cmp    DWORD PTR [rbp-0x4],eax
0x0000000000401177 <+113>:   jle    0x401185 <main+127>
0x0000000000401179 <+115>:   mov    eax,DWORD PTR [rbp-0xc]
0x000000000040117c <+118>:   cdqe
0x000000000040117e <+120>:   mov    eax,DWORD PTR [rbp+rax*4-0x40]
0x0000000000401182 <+124>:   mov    DWORD PTR [rbp-0x4],eax
0x0000000000401185 <+127>:   mov    eax,DWORD PTR [rbp-0xc]
0x0000000000401188 <+130>:   cdqe
0x000000000040118a <+132>:   mov    eax,DWORD PTR [rbp+rax*4-0x40]
0x000000000040118e <+136>:   cmp    DWORD PTR [rbp-0x8],eax
0x0000000000401191 <+139>:   jge    0x40119f <main+153>
0x0000000000401193 <+141>:   mov    eax,DWORD PTR [rbp-0xc]
0x0000000000401196 <+144>:   cdqe
0x0000000000401198 <+146>:   mov    eax,DWORD PTR [rbp+rax*4-0x40]
0x000000000040119c <+150>:   mov    DWORD PTR [rbp-0x8],eax
0x000000000040119f <+153>:   add    DWORD PTR [rbp-0xc],0x1
0x00000000004011a3 <+157>:   cmp    DWORD PTR [rbp-0xc],0x9
0x00000000004011a7 <+161>:   jle    0x40116b <main+101>
0x00000000004011a9 <+163>:   mov    edx,DWORD PTR [rbp-0x4]
0x00000000004011ac <+166>:   mov    eax,DWORD PTR [rbp-0x8]
0x00000000004011af <+169>:   add    eax,edx
0x00000000004011b1 <+171>:   mov    DWORD PTR [rbp-0x10],e

 

변환하기에 앞서 풀이를 했던 흐름을 생각해 보면 반복문 관련된 문제를 앞에서 풀기도 했고 공간만들고 값을 넣는 모습을 보니 역시 반복문이 있겠구나 생각했고 그 반복문 안에 조건문도 두개정도 있다고 파악하고 시작을 했다. (점프 관련 코드가 두개가 있었기 떄문)

 

처음 조건식은 rbp-0x4가 작을 때 뭔갈 하고 두번쨰 조건식은 rbp-0x8이 클 때 뭔갈 하는거 보니 정렬관련된 건가? 해서 몇바퀴 돌렸더니

 

rbp-0x4보다 작은 값을 찾으면 rbp-0x4에 저장하고 rbp-0x8보다 큰 값을 찾으면 rbp-0x8에 저장하는 그런 것이였다. 

 

이것의 의미는 가장 큰 값과 가장 작은 값을 찾는다는 것이고. 

 

마지막에 이 두개를 더하니 가장 큰 값과 가장 작은 값을 더한게 답일 것이다.

 

변환!

int a[10] = {1, 3, 7, 13, 21, 1, 13, 27, 13, 1}; // [rbp-0x40 ~ rbp-0x1c]

int min = 30 // [rbp-0x4]
int max = 3 // [rbpj-0x8]

int i = 0 // [rbp-0xc]
int result // [rbp-0x10]

while(i <= 9){
    if (min > a[i]){
    	min = a[i];
    }
    
    if (max < a[i]){
    	max = a[i];
    }
    
    i++;
}

result = min + max;

 

그래서 답은 28

 


99. 후기

오랜만에 하는 어셈블리라 나름 재밌었다.

'어셈블리' 카테고리의 다른 글

외부 모듈  (0) 2021.10.19
모듈 프로그래밍  (0) 2021.10.12
반복  (0) 2021.10.11
논리 연산  (0) 2021.10.10
사칙 연산  (0) 2021.10.09

0. 사담

1년 좀 넘게 보안관제를 하면서 여유가 조금 생기기도 했고, 자동화를 위해서 분석 내용을 작성할 명확한 근거?가 필요하기도 하고, 공부도 할 겸, 죽어있던 블로그도 살릴겸(올해 목표 중 하나..) 이제부터 보는 모든 공격에 대해서 따로 분석한 내용을 정리해 보려고 한다. 틀린 내용이 다수 있으니 재미로만 봐주길 바란다.

 

 



1. WAF란 무엇인가?

웹의 비정상 트래픽을 탐지하고 차단하기 위한 방화벽이다.

방화벽과 WAF의 차이점은 방화벽은 TCP/IP 레벨에 포함된 정보들을 기반(ip, 포트, 프로토콜, TCP 플래그 등)으로 차단룰을 설정하는 것이고, WAF는 HTTP 정보를 기반(HTTP 헤더들)으로 차단룰을 설정하는 것이다.

2. AWS WAF란 무엇인가?

AWS(Amazon Web Services)가 운영하는 클라우드 보안 서비스이다. 즉 AWS에서 제공해주는 WAF이다.


그럼 AWS서비스를 이용중인 서버만 AWS WAF 서비스를 제공받을 수 있나? 아니다. 실제 서버가 AWS 밖에 있어도 CloudFront를 경유시켜 서버에 오게끔 할 수 있고, 경유시킬떄 WAF 서비스를 받을 수 있게 된다.

3. Web ACL은 무엇인가?

AWS WAF에서의 가장 중요한 설정 묶음을 말한다. AWS WAF는 Web ACL에 적힌 규칙에 따라서 검사를 한다고 보면된다.

4. ALB는 무엇인가?

로드 밸런서다. 서버가 여러개면 하나의 서버만 힘들지 않게 알아서 잘 나눠주는 것이다.

 

5. EC2는 무엇인가?

AWS에서 빌려서 사용하는 서버(공간) 이라고 보면 된다.


6. 왜 AWS WAF는 ALB 정보가 중요한 것인가?

(1) 일반적인 AWS WAF의 구성도는 [사용자 - AWS WAF - ALB - EC2] 이다. WAF에 연결된 직접적인 보호 대상이 ALB이기 떄문이다. 그리고 어떤 EC2까지 요청이 전달되었는지도 확정하기 어렵다.
(2) 1번 이유의 연장선이긴한데 그래서 로그에 "httpSourceName": "ALB" 라고 찍힌다. 보호 대상이 ALB라는 의미이다.
(3) 목적지 ip를 nslookup 했을 떄 나온 EC2 인스턴스가 해당 요청을 직접 받았다고 단정 지을 수 없다. 그 이유는 nslookup은 ip의 dns 이름을 보여주는 것이기 떄문에 해당 EC2 인스턴스가 직접 요청을 받았다는 직접적인 증거가 되지 않는다.
(4) 애초에 목적지 ip가 alb를 가리키는 것일 수 있다. alb는 받은 요청을 전달만 해주면 ec2가 처리해서 다시 돌려주는 방식일 수 있다는 점?



로그 분석(필드의 의미)

여태까지는 각 필드의 정확한 의미를 생각하지 않고 업무를 했었는데 이참에 좀 정리를 할까 한다.

아래 사이트가 정확하다. 
https://docs.aws.amazon.com/waf/latest/developerguide/logging-fields.html

 

Log fields for protection pack (web ACL) traffic - AWS WAF, AWS Firewall Manager, AWS Shield Advanced, and AWS Shield network se

clientAsn is logged in AWS WAF logs only when an ASN match statement is used. This field is not logged otherwise.

docs.aws.amazon.com

 

 

timestamp

말 그대로 요청을 기록한 시간이다. UTC 기준이라 한국보다 9시간 느리다.

 

formatVersion

AWS WAF의 로그 형식 버전이라고 보면된다. 딱히 의미 있는 값은 아니고 각 버전에 따라 로그의 형식이 조금씩 달라질 수 있다~~ 이런 의미이다.

 

webaclId

요청을 검사한 Web ACL의 식별자이다. 일반적으로 [arn:aws:wafv2:<리전>:<계정 ID>:<범위>/<종류>/<이름>/<고유 ID>] 해당 형식을 따른다.

 

terminatingRuleId

요청에 대한 WAF 평가를 최종적으로 종료한 규칙의 ID이다. 어떠한 규칙도 평가를 종료하지 않았다면 "Default_Action"이라는 값이 기록된다. 
특이한 점으로 규칙에 일치하더라도 규칙의 동작이 COUNT라면 평가를 종료하지 않고 넘어갈 수도 있다.(종료를 안시키는 규칙도 있다)

 

terminatingRuleType

평가를 종료한 규칙의 유형이다.
AWS에서 정의하는 유형이 4개가 있다.
REGULAR : 일반 규칙이 종료했거나, "Default_Action"이 최종 적용된 경우
RATE_BASED : 일정 시간 동안 특정 기준의 요청 횟수가 한도를 넘어서 Rate-based rule이 요청을 종료한 경우
GROUP : 사용자가 만든 규칙 결과가 요청 평가를 종료한 경우
MANAGED_RULE_GROUP : AWS 관리형 규칙 그룹이나 AWS Marketplace 관리형 규칙 그룹에서 종료한 경우

 

action

AWS WAF가 이 요청에 대해 최종적으로 적용한 종료 동작이다.
이것도 AWS에서 정의한 종료 동작이 있다.
ALLOW : 요청을 백엔드로 전달 (안전한 요청으로 판정되었다는 의미는 아님)
BLOCK : 요청을 차단 (기본 응답은 403, 하지만 사용자 정의 응답 구성 가능)
CAPTCHA : CAPTCHA 검증 실패·미완료로 종료 (사람이 수동으로 하는거)
CHALLENGE : Challenge 검증 실패·미완료로 종료 (브라우저가 자동으로 하는거)

 

terminatingRuleMatchDetails

최종 종료 규칙이 구체적으로 어떤 데이터와 일치했는지 기록하는 배열이다. terminatingRuleId가 "Default_Action" 이라면 종료한 규칙이 없다는 것이라서 배열이 비어 있을 수 있다.
비어있지 않다면 내부에 다음 필드가 들어갈 수 있다.
conditionType : "SQL_INJECTION", "XSS" 등 탐지 유형
location : "BODY", "QUERY_STRING", "HEADER" 등 검사 위치
matchedData : 탐지에 사용된 문자열 조각
matchedFieldName : 일치한 필드 이름
sensitivityLevel : SQLi 검사 민감도 등

 

httpSourceName

AWS WAF에 요청을 전달한 보호 대상 AWS 서비스 유형이다. 그니까 어떤걸 보호중인지 나타내는 필드이다.

 

httpSourceId

httpSourceName의 ID라고 보면 될 것 같다. 
ALB의 경우 "<AWS 계정 ID>-유형/로드밸런서 이름/로드밸런서 고유 ID" 형식이다.

 

ruleGroupList

이 요청에 대해 평가된 규칙 그룹별 결과 목록이다. 이 AWS WAF를 통과하는 요청은 이 규칙 리스트를 전부 검사한다고 보면된다. 물론 중간에 규칙이 일치하고 COUNT가 아니라면 일치하는 것까지만 표시된다.

 

rateBasedRuleList

요청에 대해 평가된 Rate-based rule 목록이다. 단일 요청의 내용만 보는 것이 아니라 일정 평가 구간 동안 동일한 집계 기준에서 발생한 요청 수를 계산하여 평가한다.

 

nonTerminatingMatchingRules

이거 ruleGroupList 속에도 있어서 좀 헷갈렸는데 최상위 필드에 있는 nonTerminatingMatchingRules 는 사용자가 직접 Web ACL에 작성한 규칙을 기준으로 일치했지만 평가를 종료하지 않은 규칙이 있으면 표시해주는 목록인 것이다.

 

requestHeadersInserted

AWS WAF가 사용자 정의 요청 처리 설정을 통해 백엔드 요청에 추가한 HTTP 헤더 목록이다. 추가한 헤더가 있냐 없냐를 나타내는 필드이다

 

responseCodeSent

AWS WAF가 사용자 정의 응답을 통해 클라이언트에게 직접 보낸 HTTP 상태 코드를 말한다. 이게 null이라고 해서 응답 코드가 전송되지 않았다는게 아니고, 기록된게 없다는 뜻이다.

ja3Fingerprint & ja4Fingerprint

TLS 'Client Hello' 특성을 기반으로 계산한 JA3 지문이다.
ja3는 무엇이냐? https로 접속한 프로그램의 통신 특성을 나타내는 값이다.
https 접속이 시작될때 브라우저나 프로그램은 서버에게 암호화 방식, TLS 버전, 여러 기능을 사용할 수 있다는 정보를 주는데 이걸 나타내는 ID이다.
따라서 http 헤더랑 ja3 값의 나타내는 정보가 일치하지 않는다면 위장 가능성을 의심해 볼 수 있는 그런 정보이다.


99. 후기

각 필드의 의미를 한번에 외울순 없지만 어차피 자주 보는거라 지금 정리한걸로 계속 봐볼 생각이다. ALB가 뭔지 EC2가 뭔지 오늘에서야 찾아봤다는게 스스로에게 좀 충격이긴 하다. 공부를 너무 안한거 같기도 하고.. 사실 각 필드의 의미를 보니까 생각보다 별거 없었던거 같기도 하고..  어쨌든 이걸 기반으로 분석내용의 기준을 세울 때 활용해 보겠다. 

화면에서 칸을 나눌 때 아주 요긴하게 써먹을 수 있다.

 

Layout 객체 생성

from rich.layout import Layout

layout = Layout()

 

 

Layout 영역 분할

split 메서드를 사용해서 수직이나 수평으로 분할 가능

from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel

layout = Layout()

layout.split(
    Layout(name="header", size=3),   # 고정 크기 (3행)
    Layout(name="body", ratio=2),   # 비율적 크기
    Layout(name="footer", ratio=1),  # 비율적 크기
)

console = Console()
console.print(layout)

 

결과는 이렇다.

아주 아름답다.

레이아웃이 비어있으면 레이아웃 정보를 이렇게 출력해준다. 원래는 테두리도 안보이는듯 하다.

 

레이아웃을 가로로 배치하고 싶으면 split을 split_row로 바꿔주면 된다.

from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel
layout = Layout()

layout.split_row(
    Layout(name="header", size=3),   # 고정 크기 (3행)
    Layout(name="body", ratio=2),   # 비율적 크기
    Layout(name="footer", ratio=1),  # 비율적 크기
)


console = Console()
console.print(layout)

가로 배치

header 레이아웃의 크기가 너무 작아서 정보가 출력이 안됐다.

 

Layout 중첩

레이아웃을 중첩시켜 복잡하게 배치할 수 있다.

from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel
layout = Layout(name="main")

layout.split(
    Layout(name="header", size=3),   # 고정 크기 (3행)
    Layout(name="body", ratio=2),   # 비율적 크기
    Layout(name="footer", ratio=1),  # 비율적 크기
)

layout["header"].split_row(
    Layout(name="header_left", ratio=1),   # 비율적 크기
    Layout(name="header_center", ratio=2),   # 비율적 크기
    Layout(name="header_right", ratio=1),  # 비율적 크기
)

layout["body"].split_row(
    Layout(name="left", ratio=1),   # 비율적 크기
    Layout(name="right", ratio=1),  # 비율적 크기
)

layout["footer"].split_row(
    Layout(name="one", ratio=1),   # 비율적 크기
    Layout(name="two", ratio=1),   # 비율적 크기
    Layout(name="three", ratio=1),  # 비율적 크기
    Layout(name="four", ratio=1),  # 비율적 크기
)



console = Console()
console.print(layout)

 

중첩시킬 때 레이아웃 이름을 전부 다르게 해줘야 한다. 중복되는 이름이 있으면 마지막에 선언했던 레이아웃의 이름으로 덮여진다.

 

 

Layout 콘텐츠 추가

각 레이아웃에 rich에서 제공하는 랜더러블 객체(Panel, Table, Syntax 등)을 추가할 수 있다.

from rich.console import Console
from rich.layout import Layout
from rich.panel import Panel
layout = Layout(name="main")

layout.split(
    Layout(name="header", size=3),   # 고정 크기 (3행)
    Layout(name="body", ratio=2),   # 비율적 크기
    Layout(name="footer", ratio=1),  # 비율적 크기
)


layout["header"].update(Panel("Header"))
layout["body"].update(Panel("Main content goes here"))
layout["footer"].update(Panel("Footer"))



console = Console()
console.print(layout)

 

결과

'창고' 카테고리의 다른 글

Python Rich  (0) 2025.03.06
[Python] C에게 문자열 받아오기  (0) 2022.04.19
IAT(Import Address Table)  (0) 2022.01.06
PE File Format - RVA to RAW  (0) 2021.12.30
PE File Format - Section Headers  (0) 2021.12.13

Python Rich 소개

터미널에서 글자들을 이쁘게 꾸밀려면 복잡한 출력을 필요로 하는데 글자들을 꾸미는데 필요한 복잡한 출력을 쉽게 만들 수 있게 해주는 라이브러리이다.

즉, 터미널을 꾸미는 라이브러리다.

이쁜 터미널은 가독성이 좋아진다.

 

Rich 설치 방법(Windows)

일단 파이썬이 설치되어 있어야 한다.

cmd 창을 열고 아래 명령어를 입력하면 설치된다.

python -m pip install rich

 

Rich 사용 예시

아래 코드를 사용하면

from rich.console import Console
from rich.panel import Panel
from rich.text import Text

console = Console()

text = Text("Welcome to Rich!")
text.stylize("bold magenta")

panel = Panel(text, title="Rich Demo", border_style="green")
console.print(panel)

console.print("[bold blue]This is a blue text[/bold blue]")
console.print("[on red]This has a red background[/on red]")

 

이런 결과가 나온다.

그냥 그런데?

 

잘 사용하면 이렇게 멋진 구성도 가능하다.

 

 

Rich 사용 시 참고사항

글꼴의 영향이 있다.

위에 찍은 예시는 NSimSun 글꼴이다. 다른 글꼴로 하면 초록색 사각형이 깨질 수 있다.

터미널에서 제공하는 SimSun-ExtB 글꼴이다.

 

돋움채 같은 경우는 초록색 사각형의 오른쪽, 왼쪽 둘 다 표현이 제대로 안되는데, 터미널 창의 크기를 살짝 조절해 주면 오른쪽은 보이게 된다. (대충 이런 상황이 있다는 것을 알려주고 싶었다.)

돋움체

 

그래서 Rich 라이브러리의 모든 기능을 활용하려면, 유니코드를 지원하고 다양한 스타일을 잘 표현할 수 있는 현대적인 터미널 글꼴을 선택하는 것이 좋다.

예를 들면 

  • Fira Code
  • Cascadia Code
  • JetBrains Mono

등이 있다곤 하는데, 나는 일단 터미널 글꼴로 해볼 것이다. 하다가 표현이 이상하게 된다면 글꼴 설치하고 적용하고 바뀌는 점을 다시 블로그에 올려보겠다.

'창고' 카테고리의 다른 글

Python Rich - Layout  (0) 2025.03.07
[Python] C에게 문자열 받아오기  (0) 2022.04.19
IAT(Import Address Table)  (0) 2022.01.06
PE File Format - RVA to RAW  (0) 2021.12.30
PE File Format - Section Headers  (0) 2021.12.13

0. 후기

그닥 어렵지 않았다. level11과 level12를 풀어서 level13도 어렵지 않게 풀이를 생각해 냈다. 어느 정도 풀어놓고 level11부터 풀이를 쓰느라 잊고 있었는데 level13을 풀면서 main함수가 끝나고 복귀하는 부분을 조작하는걸 이 때 이해했다.

 

1. 문제 파악하기

바로 힌트부터 봤다. 

 

2. 코드 해석

#include<stdlib.h>

main(int argc, char *argv[])
{
    long i=0x1234567;
    char buf[1024];
    
    setreuid(3094,3094);
    // 프로그램을 실행할 떄 넣어준 인자의 수가 2개 이상이면 buf에 첫번째 인자를 넣어준다.
    if(argc>1)
    strcpy(buf,argv[1]);
    
    // 오버플로우가 발생하면 프로그램을 바로 죽임
    if(i != 0x1234567) {
    	printf("Warnning: Buffer OverFlow !!! \n");
        kill(0,11);
   }
}

 

level11과 같은 함수를 이용해서 프로그램의 인자를 복사하는건 같지만 오버플로우 발생 여부를 검증하는 코드가 추가됐다. 그러면 추가로 0x1234567까지 신경쓰면서 값을 덮어 써주면 된다.

 

3. gdb 확인

1048바이트 만큼 스택을 확보한 모습을 볼 수 있고, ebp-12가 i인 것도 확인할 수 있다. 변수 별 스택 크기를 예상해 보면 

buf : 1024 + 12(dummy)

i : 4 + 8(dummy)

이렇게 예상할 수 있다.

 

 

확인을 위해 프로그램 인자를 복사하고 오버플로우를 확인하는 부분(main+69)에 bp를 걸고 인자로 A문자 1024개 넣고 실행을 하면 

 

 

내가 예상한 것과 같이 쓸데없는 값이 들어가 있는 것을 확인 할 수 있다. 그리고 빨간거 두개가 앞에는 ebp이고 뒤에가 main 함수의 복귀 주소이다.

 

스택 상황을 정리해 보면 이렇다.

 

4. 귀환할 주소 찾기

level11에서 했던 것처럼 argv에서 쉘코드를 실행할 거기 때문에 argv위치를 찾아봤다. 찾기 위해서 내가 입력할 페이로드만큼 A를 넣어야 한다.

그래서 1056(1048 + 4 + 4)만큼 A 문자를 넣어줬다. 그리고 중간에 0x1234567이 잘 들어가는지도 확인해 보기 위해 넣어 봤다. 

잘 들어간다.

 

5. 페이로드 작성

위에 귀환할 주소를 토대로 페이로드를 작성했다. 

"\X90"을 1011개 넣은 이유는 1036바이크 만큼의 크기에서 25바이트는 쉘코드를 넣어야 하기 때문이다.

그 뒤에 쉴코드를 넣고, 바로 0x1234567도 넣어준다.

그리고 아무 문자 12개 넣어주고 복귀할 주소를 넣어줬다.

 

6. 정답

'문제 풀이 > Hackerschool FTZ' 카테고리의 다른 글

FTZ - level12  (1) 2024.09.25
FTZ - level10  (0) 2024.08.03
FTZ - level9  (0) 2024.08.02
FTZ - level8  (0) 2024.08.02
FTZ - level7  (0) 2024.07.31

0. 후기

이건 한참을 붙잡고 있어도 푸는 방법이 안떠올라서 결국 다른사람들의 풀이를 봐버렸다. 떠올리지 못할 방법이긴 했다. 환경 변수를 통해서 풀었는데 이 방법에 대해서 더 찾아봐야 겠다.

 

1. 문제 파악하기

일단 힌트부터 확인했다. 코드를 딱 봤을 때 level11과 다른점은 프로그램 안에서 입력값을 받는다는 점만 다르고 나머지는 같다고 생각했다.

 

2. gdb 확인하기

일단 level11에서 했던것처럼 main 함수가 끝나고 복귀할 주소를 덮어써서 쉘코드를 실행하려고 스택의 구조를 파악하였다. level11와 같이 264만큼의 크기를 확보하고 있는 모습이다.

 

3. 문제 발생

여기쯤에서 문제가 발생했는데, 쉘코드를 입력한 값이 스택에 저장되는데 이 스택 주소값이 계속 바뀌었다. 그래서 찾아보니가 메모리 보호기법 중에 ASLR이라고 있는데 스택의 주소를 프로그램이 실행될 때 매번 다르게 바꿔주는 기법이라고 있었다. 그래서 스택의 주소가 계속 바뀌나보다 싶어서 ASLR을 푸는 방법이 뭐가 있나 찾아보고 해봤는데 생각해보니까 level11에서도 스택의 메모리가 계속 변하는거 보니 이 방법이 아닌것 같다고 생각했다.

 

4. 환경 변수 생성

그래서 고민을 계속하다가 포기하고 다른 사람들 문제를 보았다. 환경 변수를 통해 풀던데 이 방법은 level11을 풀고 나서 다른 사람들 풀이를 볼 때 봤던 거긴 했는데 뭔가 방법이 맘에 안들어서 따로 더 안찾아봤던 방법이었다. 여러개를 찾아봤지만 다들 이 방법을 사용하길래 나도 사용했다.

 

환경 변수에 쉘코드를 넣어줬다. \x90을 30개나 넣어준 이유는 약간의 오차가 생기더라도 NOP slide를 통해서 실핼될 수 있도록 넣어줬다.

 

그리고 환경변수의 주소를 출력하는 코드를 만들어 주고.

 

프로그램 실행을 통해 환경 변수의 주소를 알아냈다.

 

5. 페이로드 작성

페이로드는 268바이트(264+ebp)만큼 대충 채워주고, 그 뒤에 4바이트에 환경변수의 주소를 넣어줬다. 프로그램이 실행되는 동안 입력값을 받는 경우에 어떻게 python을 통해 값을 넣어 주는지도 알게 되었다. 

 

6. 정답

 

'문제 풀이 > Hackerschool FTZ' 카테고리의 다른 글

FTZ - level13  (0) 2024.09.26
FTZ - level10  (0) 2024.08.03
FTZ - level9  (0) 2024.08.02
FTZ - level8  (0) 2024.08.02
FTZ - level7  (0) 2024.07.31

+ Recent posts