jadx로 같은 파일을 확인하면, class의 이름이 Base64Coder에서 pabd63bb로 바뀐 것을 알 수 있다.
class renaming은 class의 이름를 "Base64Coder"처럼 식별할 수 있는 문자대신, "pabd63bb"와 같이 무슨 의미인지 알 수 없게 바꾸어준다. 따라서 역공학으로 해당 파일을 보더라도 무슨 역할을 하는 지 알아차리기 힘들어 역공학을 방해한다. [1]
처음에 메소드의 끝을 가리키는 goto가 있고, 메소드 끝에 메소드의 처음을 가리키는 goto가 추가되었다.
백신탐지 비교
(왼쪽 : 기존앱, 오른쪽 : Goto옵션 적용)
- Reorder
Reorder는 역공학하여 분석하기 어렵도록, 메소드의 명령어 흐름을 복잡하게 한다. 왼쪽의 그림이 Reorder된 app_3의 BandManager.smali이다. [2] 오른쪽의 기존과 method와 다르게 추가된 내용도 생기고 흐름도 goto문이 생기는 등 복잡하게 바뀐 것을 알 수 있다.
왼쪽의 Nop 옵션이 적용된 app_2/smali/c/ca/a.smali 파일을 보면 오른쪽의 기존 파일과 다르게 nop 명령어들이 추가된 것을 알 수 있다.
백신탐지 비교
(왼쪽 : 기존앱, 오른쪽 : nop 옵션 적용)
- ArithmeticBranch
ArithmetiBranch 옵션은 의미 없는 코드를 삽입하여 명령어 흐름을 복잡하게 만들다. 따라서 역공학시 분석을 하기 어렵게 한다. [2] 왼쪽의 ArithmeticBranch 옵션이 적용된 app_4/smali/com/example/eroplayer/MainActivity을 보면 오른쪽의 기존 파일과 다르게 junk code가 삽입된 것을 알 수 있다.
이번 과제를 하면서, 개발자의 자산을 보호하는 것도 중요하지만, 악성코드를 탐지를 위해 난독화를 탐지하는 기술도 중요하다고 느꼈다.
참고논문
[1] 난독화에 강인한 안드로이드 앱 버스마킹 기법 김 동 진 , 조 성 제° , 정 영 기* , 우 진 운**, 고 정 욱***, 양 수 미**** Android App Birthmarking Technique Resilient to Code Obfuscation Dongjin Kim , Seong-je Cho° , Youngki Chung* , Jinwoon Woo**, Jeonguk Ko***, Soo-mi Yang****
[2] 안드로이드 어플리케이션 역공학 보호기법 하 동 수*, 이 강 효*, 오 희 국*
[4] Android Code Protection via Obfuscation Techniques: Past, Present and Future Directions Parvez Faruki, Malaviya National Institute of Technology Jaipur, India Hossein Fereidooni, University of Padua, Italy Vijay Laxmi, Malaviya National Institute of Technology Jaipur, India Mauro Conti, University of Padua, Italy Manoj Gaur, Malaviya National Institute of Technology Jaipur, India
CREATE FUNCTION f_mgr (e_name varchar(10))
RETURNS varchar(10)
BEGIN
declare manager varchar(10);
select m.ename into manager
from emp e, emp m
where e.mgr=m.empno and e.ename = e_name;
RETURN manager;
END
(2) 부서번호를 입력하면 부서의 위치를 출력하는 함수를 작성하시오 (f_loc)
다음과 같이 함수를 테스트한 결과를 보이시오.
select empno, ename, job, f_loc(deptno) as loc
from emp
CREATE FUNCTION f_loc (d_no int)
RETURNS varchar(10)
BEGIN
declare d_loc varchar(10);
select loc into d_loc
from dept
where deptno = d_no;
RETURN d_loc;
END
1. 사원번호를 매개변수로 입력 받아 사원번호, 이름, 담당업무, 연봉, 소속부서명을 보여주는 stored procedure 를 작성하시오 (p_emp_sel_1)
CREATE PROCEDURE p_emp_sel_1(id int)
BEGIN
select empno, ename, job, sal, dname from emp, dept
where emp.deptno=dept.deptno and empno = id;
END
2. 부서번호, 부서명, 위치를 매개변수로 입력 받아 새로운 부서 정보를 생성하는 stored procedure 를 작성하시오 (p_dept_insert_1)
CREATE PROCEDURE p_dept_insert_1(d_num int , d_name varchar(10), d_loc varchar(10))
BEGIN
insert into dept values(d_num, d_name, d_loc);
END
3. 사원번호, 사원 이름을 매개변수로 입력 받아 이름을 수정하는 stored procedure 를 작성하시오 (p_emp_update_1)
CREATE PROCEDURE p_emp_update_1 (e_num int, e_name varchar(10))
BEGIN
update emp set ename=e_name where empno = e_num;
END
1. 사원번호를 매개변수로 입력 받아 사원의 담당업무가 ‘CLERK’ 이면 급여를 20% 올리고, 아닌 경우는 10%를 올리는 stored procedure 를 작성하시오 (p_emp_update_2)
CREATE PROCEDURE p_emp_update_2 (e_num int)
BEGIN
declare e_job varchar(10);
select job into e_job
from emp
where empno=e_num;
if(e_job = 'clerk') then
update emp
set sal=sal*1.2
where empno=e_num;
end if;
if(e_job != 'clerk') then
update emp
set sal=sal*1.1
where empno=e_num;
end if;
END
2. 사원번호를 매개변수로 입력 받은 후에 그 사원이 속한 부서 사람들의 연봉 합계를 구하여 출력하는 stored procedure 를 작성하시오 (p_emp_sel_2)
CREATE PROCEDURE p_emp_sel_2 (e_num int)
BEGIN
select sum(sal)
from emp
where deptno = (select deptno from emp where empno = e_num);
END
3. 사원번호를 매개변수로 입력 받은 후에 사원의 급여가 평균급여 이상이면 해당 사원의 근무지를 보이고, 그렇지 않으면 사원의 직무를 보이는 stored procedure 를 작성 하시오. (p_emp_sel_3)
CREATE PROCEDURE p_emp_sel_3 (e_num int)
BEGIN
declare e_sal decimal(10,4);
declare avg_sal decimal(10,4);
select sal into e_sal
from emp
where empno=e_num;
select avg(sal) into avg_sal
from emp;
if(avg_sal <= e_sal) then
select loc from empd where empno=e_num;
else
select job from emp where empno=e_num;
end if;
END
=> 어플리케이션마다 복수의 SQL문 기술할 필요 없이 만들어진 저장 프로시저를 사용하면 다른 어플리케이션을 수정하여 컴파일 할 필요 없음.
=> 저장 프로시저는 만들어지는 순간에 구문이 검사됨, 따라서 DBA의 에러를 감소시킬 수 있음(실행전 검사)
=> SQL문을 직접 실행시킬 수 없는 사용자들도 저장 프로시저만 실행시킬 수 있는 권한을 가지게 할 수 있다.
단점
=> 접하기 어렵다.
=> DBMS 제품마다 문법이 다르다(비표준화)
=> 저장프로시저를 남발하는 경우 유지보수가 어렵다.
함수와의 차이점
저장프로시저 : 일반적으로 return 값이 없는 프로그램, CALL에 의해서 호출
함수 : return 값이 있는 프로그램, MAX(), min()과 같이 SQL문 안에서 사용됨
- 저장 프로시저 생성
CREATE PROCEDURE 저장프로시저이름()
BEGIN
SQL문 1;
SQL문 2;
END
BEGIN ~ END 사이에 원하는 SQL문들을 작성하면 된다.
** 구분문자 문제
위와 같이 작성시 BEGIN ~ END안에서 SQL문이 작성이 덜 되었더라도 ;를 만나게되면 CREATE PROCEDURE 을 실행하게되어 완성되지 않은 상태로 저장프로시저가 생성된다.
CREATE PROCEDURE 저장프로시저이름(인수이름 자료형)
BEGIN
SQL문 1;
SQL문 2
END
와 같이 될 경우 MySQL 콘솔창은 ;이 입력되면 ;이전단계까지의 명령문을 실행하게 되기때문이다.
따라서
저장 프로시저에서 END를 입력하고난 뒤 CREATE PROCEFURE 명령이 실행되게 해야한다.
DELIMITER를 사용하여 구분문자를 ;대신 다른 문자로 변경한다.
EX) DELIMITER // 혹은 DELIMITER =
와 같이원하는 문자를 적으면 된다.
DELIMITER // =>구분 문자를 ;에서 //로 변경
CREATE PROCEDURE pr1()
BEGIN
SELECT * FROM tb;
SELECT * FROM tb1;
END
// =>//구분문자를 만났으므로 이전까지 입력된 명령을 수행
DELIMITER ; => 구분문자를 다시 ;로 변경
- 저장 프로시저 실행
CALL 저장프로시저이름;
ex) CALL pr1();를 실행하면
자동으로 SELECT * FROM tb;와 SELECT * FROM tb1;가 실행된다.
- 저장 프로시저 인수 사용
PROCEDURE 저장프로시저이름(인수이름 자료형);
예를 들어 sales가 200 이상인 값을 보려고 할때
select * from tb where sales>=d;와 같이 설정하면
DELIMITER //
CREATE PROCEDURE pr1(d INT)
BEGIN
SELECT * FROM tb WHERE sales>=d;
END
//
DELIMITER ;
d를 인자로 받아들여 pr1(200)을 실행하면
select * from tb where sales>=200;이 된다.
- 작성된 저장 프로시저 내용 표시
SHOW CREATE PROCEDURE 저장프로시저이름;
ex) SHOW CREATE PROCEDURE pr1;
pr1의 프로시저 내용을 볼 수 있게 된다.
- 저장 프로시저 삭제
DROP PROCEDURE 저장프로시저이름;
- 저장 함수
저장 프로시저와 유사하지만, 실행했을 때 값을 반환한다.
CREATE FUNCTION 저장함수이름(인수이름 자료형) RETURNS 반환값자료형
BEGIN
SQL문 ....
RETURN 반환값식
END
* DECLARE
저장 함수에서 변수를 사용하려면 DECLARE로 정의해야한다.
DECLARE 변수이름 자료형;
예시를 보면
CRETAE FUNCTION func() RETURNS DOUBLE => 함수의 반환형은 double
BEGIN
DECLARE r DOUBLE; => 변수 r 선언
SELECT AVG(sales) INTO r FROM tb; => AVG(sales)값을 r에 저장
RETURN r; => r을 반환
END
- 저장함수 삭제
DROP FUNCTION 저장함수이름;
- 저장함수 내용 표시
SHOW CREATE FUNCTION 저장함수이름;
저장 함수의 내용을 볼 수 있게된다.
- 트리거
테이블에 대해 어떤 처리를 실행하면 이에 반응하여 설정한 명령이 자동으로 실행되는 구조
INSERT, UPDATE, DELETE 등 명령이 실행될 때, 트리거로 설정한 명령이 자동으로 실행되게 할 수 있다.
EX) 테이블의 레코드 변경시, 변경한 내용을 다른 테이블에 기록하도록 트리거를 만들 수 있음
따라서 트리거는 처리를 기록하거나, 처리가 실패할 경우를 대비하여 만들어 놓으면 좋다.
트리거는 INSERT, UPDATE, DELETE 등 명령이 실행되기 직전(BEFORE) 또는 직후(AFTER)에 호출되어 실행된다.
즉, 어떤 데이터를 처리하기 전에 호출되거나 어떤 데이터를 처리한 후에 호출된다.
또한 테이블에서 어떤 데이터를 처리하기 전의 값은 OLD.칼럼이름.
처리한 후의 값은 NEW.칼럼이름으로 얻을 수 있다.(추출할 수 있다.)
명령에 따라서 칼럼 값을 추출할 수도 있고 없을 수도 있다.
명령
실행전(old.칼럼이름)
실행후(new.칼럼이름)
insert
old.칼럼이름 추출 불가
가능
delete
가능
new.칼럼이름 추출불가
update
가능
가능
- 트리거 생성
CREATE TRIGGER 트리거이름 BEFORE(또는 AFTER) DELETE(UPDATE,INSERT 등과 같은 명령)
ON 테이블 이름 FOR EACH ROW
BEGIN
변경전(OLD.칼럼이름)을 이용한 처리 ====> 또는 변경후(NEW.칼럼이름)
END
EX) tb1에서 삭제한 레코드를 tb1m에 삽입하는 트리거 작성
DELIMITER //
CREATE TRIGGER tr1 BEFORE DELETE ON tb1 FOR EACH ROW
===> delete에 반응, 삭제하기 직전의 값을 넣으므로 before, for each row 각 행에 대해 수행
BEGIN
INSERT INTO tb1m VALUES(OLD.number, OLD.name, OLD.age);
END
//
DELIMIER ;
tb1에서 레코드가 삭제될때, tb1m에 삭제된 레코드가 입력되게 된다.
DELETE FROM tb1;을 하여 모든 레코드를 삭제하더라도 tb1m에 삭제된 레코드들이 저장되게 된다.
따라서 INSERT INTO tb1 SELECT * FROM tb1m;과 같이 다시 복원을 할 수도 있다.
* de-obfuscator : 난독화된 악성코드를 분석시 사용, 난독화를 해제시켜주는 도구
분석 지연
난독화
클래스, 메소드, 변수명 등을 의미 없는 문자나 식별할 수 없는 문자로 치환, 제어흐름 난독화
암호화
문자열, 리소스 등 암호화
코드 분리
핵심 로직이 담긴 코드를 분리하고 실행 중에 동적으로 적재
환경 탐지
디버거 탐지
프로세스 ID 확인, 디버깅 탐지 API 결과 값 반환
에뮬레이터 탐지
단말 ID, 전화번호, 빌드 값, IP 조사
플랫폼 해킹 탐지
OS 해킹 여부 확인
앱 변조 탐지
앱 위변조 여부, 앱 서명 값 확인
코드 난독화 기법 분류
- 레이아웃 난독화
예시) 식별자 난독화 (identifier renaming)
package iAmDriving{
public class LetsNavigate{...}
}
pacakge iAmConnecting {
public class MyBluetoothHandle{...}
}
pacakge userIsActive{
public class MainActivity{...}
}
|
V
package a{
public class a{...}
}
pacakge b {
public class b{...}
}
pacakge c{
public class c{...}
}
먼저 데이터를 X_train/y_train, X_test/y_test, X_val, y_val로 나누어 학습을 진행하였습니다.
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten
from keras.layers.convolutional import Convolution2D
from keras.layers.convolutional import MaxPooling2D
from keras.utils import np_utils
import numpy as np
import matplotlib.pyplot as plt
import os
import cv2
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator
from tensorflow.python.keras.preprocessing.image import load_img, img_to_array
import numpy as np
from pathlib import Path
import glob
import pandas as pd
from skimage.io import imread # reading image as data
data_dir = Path('chest_xray')
train_dir = data_dir / 'train'
val_dir = data_dir / 'val'
test_dir = data_dir / 'test'
normal_cases_dir = train_dir / 'NORMAL'
pneumonia_cases_dir = train_dir / 'PNEUMONIA'
normal_cases_t = normal_cases_dir.glob('*.jpeg')
pneumonia_cases_t = pneumonia_cases_dir.glob('*.jpeg')
# Training data as a list
X_train = []
y_train = []
# Normal cases
for img in normal_cases_t:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
# Convert grayscale image
if img.shape[2] ==1:
img = np.dstack([img, img, img])
# CV2 uses BGR format, so we need to convert it to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Normalizing the pixel values by dividing by its maximum
img = img.astype(np.float32)/255.
X_train.append(img)
y_train.append(0)
# Pneumonia cases
for img in pneumonia_cases_t:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
if img.shape[2] ==1:
img = np.dstack([img, img, img])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img.astype(np.float32)/255.
X_train.append(img)
y_train.append(1)
X_train = np.array(X_train)
y_train = np.array(y_train)
normal_cases_dir = val_dir / 'NORMAL'
pneumonia_cases_dir = val_dir / 'PNEUMONIA'
normal_cases_v = normal_cases_dir.glob('*.jpeg')
pneumonia_cases_v = pneumonia_cases_dir.glob('*.jpeg')
# Training data as a list
X_val = []
y_val = []
# Normal cases
for img in normal_cases_v:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
# Convert grayscale image
if img.shape[2] ==1:
img = np.dstack([img, img, img])
# CV2 uses BGR format, so we need to convert it to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Normalizing the pixel values by dividing by its maximum
img = img.astype(np.float32)/255.
X_val.append(img)
y_val.append(0)
# Pneumonia cases
for img in pneumonia_cases_v:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
if img.shape[2] ==1:
img = np.dstack([img, img, img])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img.astype(np.float32)/255.
X_val.append(img)
y_val.append(1)
X_val = np.array(X_val)
y_val = np.array(y_val)
normal_cases_dir = test_dir / 'NORMAL'
pneumonia_cases_dir = test_dir / 'PNEUMONIA'
normal_cases_test = normal_cases_dir.glob('*.jpeg')
pneumonia_cases_test = pneumonia_cases_dir.glob('*.jpeg')
# Training data as a list
X_test = []
y_test = []
# Normal cases
for img in normal_cases_test:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
# Convert grayscale image
if img.shape[2] ==1:
img = np.dstack([img, img, img])
# CV2 uses BGR format, so we need to convert it to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# Normalizing the pixel values by dividing by its maximum
img = img.astype(np.float32)/255.
X_test.append(img)
y_test.append(0)
# Pneumonia cases
for img in pneumonia_cases_test:
img = cv2.imread(str(img))
img = cv2.resize(img, (224,224))
if img.shape[2] ==1:
img = np.dstack([img, img, img])
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = img.astype(np.float32)/255.
X_test.append(img)
y_test.append(1)
X_test = np.array(X_test)
y_test = np.array(y_test)
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten
from keras.layers.convolutional import Convolution2D
from keras.layers.convolutional import MaxPooling2D
seed = 100
np.random.seed(seed)
model=Sequential()
a=3
model.add(Convolution2D(32, kernel_size=(a, a), padding='same', strides=(1, 1), input_shape=(image_size, image_size, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Convolution2D(64, kernel_size=(a, a), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Convolution2D(128, kernel_size=(a, a), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Convolution2D(256, kernel_size=(a, a), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(256,activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1,activation='sigmoid'))
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])
# Final evaluation of the model
scores = model.evaluate(X_test, y_test, verbose=0)
print("loss: %.2f" % scores[0])
print("acc: %.2f" % scores[1])
'''
loss: 2.00
acc: 0.75
'''
관련 자료를 찾던 중 ImageDataGenerator을 알게되었습니다.
CNN은 영상의 2차원 변환인 회전(Rotation), 크기(Scale), 밀림(Shearing), 반사(Reflection), 이동(Translation)와 같은 2차원 변환인 Affine Transform에 취약합니다.
즉, Affine Tranform으로 변환된 영상은 다른 영상으로 인식하므로, Data Generator를 사용하여 이미지에 변화를 주면서 컴퓨터의 학습자료로 이용하여 더욱 효과적이고 과적합을 방지하는 방식인, ImageDataGenerator으로 학습하도록 하였습니다.
Problem 3: 주어진 희소행열을 표현하기 위한 적합한 자료구조를 기술하고 행열 내 모든 값의 합을 구하는 독립된 함수를 작성하시오.
1. 정수값을 입력 받아 희소행렬의 크기를 결정
2. 주어진 행렬내 원소의 약 10% 이하가 0이 아닌 값으로 임의 수를 생성 하여 대입
//h.h
int** insertRand(int** Matrix, int m);
int* NoDupRand(int* rand_num, int n);
int** createMatrix(int m, int n);
void printMatrix(int** Matrix, int m);
void showPercent(int** matrix, int m1);
void SumMatrix(int** matrix, int SparesMatrix_row);
//f.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include"h.h"
void SumMatrix(int** matrix, int SparesMatrix_row) {
int m1, sum, size;
sum = 0;
size = SparesMatrix_row;
//matrix[] 갯수
for (m1 = 0; m1 < size; m1++) {
sum += matrix[m1][2];
}
printf("==================\n");
printf("sum is %d\n", sum);
printf("==================\n");
}
void showPercent(int** matrix, int matrix_size) {
int spares_size = matrix_size;
spares_size -= matrix[matrix_size - 1][3];
matrix_size /= 10;
float percent;
percent =((float)spares_size / (float)matrix_size);
//행렬안에 값이 0인 경우를 빼줌
printf("Percent : %0.3f%%\n", percent);
printf("==================\n");
}
void printMatrix(int** Matrix, int m) {
int m1, n1;
for (m1 = 0; m1 < m; m1++) {
for (n1 = 0; n1 < 3; n1++) {
if (n1 == 2) {
printf("|%4d|\n", Matrix[m1][n1]);
}
else
printf("|%4d ", Matrix[m1][n1]);
}
}
}
//중복되지 않는 랜덤값을 가져오기위해
int* NoDupRand(int* rand_num, int n) {
int index1, index2, i, temp;
srand(time(NULL));
//n번
for (i = 0; i < n; i++) {
rand_num[i] = i;
}
//섞기 n/2번
for (i = 0; i < (n / 2); i++) {
index1 = rand() % n;
index2 = i;
temp = rand_num[index1];
rand_num[index1] = rand_num[index2];
rand_num[index2] = temp;
}
return rand_num;
}
//(m x n) matrix
int** insertRand(int** Matrix, int m) {
int m1, n1;
int count;
int* NoDupRand_m;
int* NoDupRand_n;
NoDupRand_m = (int*)malloc(sizeof(int) * m);
NoDupRand_n = (int*)malloc(sizeof(int) * m);
NoDupRand_m = NoDupRand(NoDupRand_m, m);
sleep(1);
NoDupRand_n = NoDupRand(NoDupRand_n, m);
//원소를 넣을 (행,열)위치 생성
for (m1 = 0; m1 < m; m1++) {
//행(0~m-1) 열(0~n-1) 위치 저장
//랜덤값 중복체크를 하면 시간, 공간복잡도가 늘어남으로
//행과 열이 겹치는 경우
// 행 열 값 행 열값
//ex) [1][1][] [1][1][]
Matrix[m1][0] = NoDupRand_m[m1];
Matrix[m1][1] = NoDupRand_n[m1];
}
/* 0 1 3
0 [1][1][값1]
1 [1][1][값2]
.
.
n [n][m][ ]
위와 같은 경우 발생가능
*/
//배열에 n까지 값을 저장하고 섞음으로 랜덤값 중복체크보다 시간 줄임
//0이 아닌 원소가 10%라고 가정하고 만든행렬이므로
count = 0;
for (m1 = 0; m1 < m; m1++) {
Matrix[m1][2] = rand() % 10;
//printf("[%d][3] = %d\n", m1, Matrix[m1][2]);
if (Matrix[m1][2] == 0) {
count++;
}
}
//행열값
//[][][0]
//0값이 들어간 경우가 발생하는경우를 만들어줫으므로
//값이 0인 (행,열)의 갯수를 셈
Matrix[m-1][3] = count;
return Matrix;
}
int** createMatrix(int m, int n){
int** matrix;
int m1;
//10%이하여야하므로
m1 = (m * n) / 10;
matrix = (int**)malloc(sizeof(int*) * m1);
for (int m2 = 0; m2 < (m1 - 1); m2++) {
//행, 열, 값 3개
matrix[m2] = (int*)malloc(sizeof(int) * 3);
}
//0갯수 저장
matrix[(m1-1)] = (int*)malloc(sizeof(int) * 4);
return matrix;
}
//m.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<unistd.h>
#include"h.h"
void main() {
//mxn 행렬
int m, n;
int Msize;
int** spares_matrix;
float percent;
printf("희소 행렬 크기 입력(m, n값): ");
scanf("%d %d", &m, &n);
printf("\n");
Msize = (m * n / 10);
//희소행렬의 0이 아닌값만 저장할 행렬
//10%이하 여야함
spares_matrix = createMatrix(m, n);
spares_matrix = insertRand(spares_matrix, Msize);
printf(" 행 열 값\n");
printf("------------------\n");
printMatrix(spares_matrix, Msize);
//희소 행열의 덧셈 결과 비교 출력, 0이 아닌 비율 출력
SumMatrix(spares_matrix, Msize);
showPercent(spares_matrix, Msize);
printf("program End!\n(by32153682이창민)\n");
printf("==================\n");
}