[UIUCTF2024] rev-Summarize

9자리 양의정수 6개 찾으면 끝남
sub_40137B를 먼저 보자

_BOOL8 __fastcall sub_40137B(
    unsigned int a1,
    unsigned int a2,
    unsigned int a3,
    unsigned int a4,
    unsigned int a5,
    unsigned int a6)
{
  unsigned int v7; // eax
  unsigned int v8; // ebx
  unsigned int v9; // eax
  unsigned int v10; // ebx
  unsigned int v11; // eax
  unsigned int v12; // eax
  unsigned int v18; // [rsp+20h] [rbp-30h]
  unsigned int v19; // [rsp+24h] [rbp-2Ch]
  unsigned int v20; // [rsp+28h] [rbp-28h]
  unsigned int v21; // [rsp+2Ch] [rbp-24h]
  unsigned int v22; // [rsp+30h] [rbp-20h]
  unsigned int v23; // [rsp+34h] [rbp-1Ch]
  unsigned int v24; // [rsp+38h] [rbp-18h]
  unsigned int v25; // [rsp+3Ch] [rbp-14h]

  if ( a1 <= 0x5F5E100 || a2 <= 0x5F5E100 || a3 <= 0x5F5E100 || a4 <= 0x5F5E100 || a5 <= 0x5F5E100 || a6 <= 0x5F5E100 )
    return 0LL;
  if ( a1 > 0x3B9AC9FF || a2 > 0x3B9AC9FF || a3 > 0x3B9AC9FF || a4 > 0x3B9AC9FF || a5 > 0x3B9AC9FF || a6 > 0x3B9AC9FF )
    return 0LL;
  v7 = sub_4016D8(a1, a2);
  v18 = (unsigned int)sub_40163D(v7, a3) % 0x10AE961;
  v19 = (unsigned int)sub_40163D(a1, a2) % 0x1093A1D;
  v8 = sub_4016FE(2LL, a2);
  v9 = sub_4016FE(3LL, a1);
  v10 = sub_4016D8(v9, v8);
  v20 = v10 % (unsigned int)sub_40174A(a1, a4);
  v11 = sub_40163D(a3, a1);
  v21 = (unsigned int)sub_4017A9(a2, v11) % 0x6E22;
  v22 = (unsigned int)sub_40163D(a2, a4) % a1;
  v12 = sub_40163D(a4, a6);
  v23 = (unsigned int)sub_40174A(a3, v12) % 0x1CE628;
  v24 = (unsigned int)sub_4016D8(a5, a6) % 0x1172502;
  v25 = (unsigned int)sub_40163D(a5, a6) % 0x2E16F83;
  return v18 == 4139449
      && v19 == 9166034
      && v20 == 556569677
      && v21 == 12734
      && v22 == 540591164
      && v23 == 1279714
      && v24 == 17026895
      && v25 == 23769303;
}

하 수식 개많네
음 일단 먼저 구할 수 있는 a’n’을 찾아야 함

Image

0x3b9ac9ff < a1 <= 0x5F5E100, 0x3b9ac9ff < a2 <= 0x5F5E100일 때 sub_40163d의 최대 최소값
200000000, 1999999998

v19=0x1093a1d*n+9166034일 때, n의 범위

Image

# Define the function based on the provided code
def sub_4016FE(a1, a2):
    v4 = 0
    v5 = 0
    while a1:
        v4 += (a1 & 1) * (a2 << v5)
        a1 >>= 1
        v5 += 1
    return v4

# Define the range for the inputs based on the problem statement
a1 = 3
a2_min = 0x3b9ac9ff  # 1,000,000,000
a2_max = 0x5F5E100   # 100,000,000

# Calculate the function output for the minimum and maximum a2 values
min_a2 = 0x3b9ac9ff
max_a2 = 0x5F5E100

# Calculate the minimum and maximum return values
min_return_value = sub_4016FE(a1, min_a2)
max_return_value = sub_4016FE(a1, max_a2)

print(f"Minimum return value: {min_return_value}")
print(f"Maximum return value: {max_return_value}")

200000000 < v8 <= 1999999998
300000000 < v9 <= 2999999997

나보고 어드카라고 안된다고

hint가 9자리 수인듯 hex값이라 몰랐는데 0x5f5e101이 100000001
라업에서는 z3이라는 파이썬 lib로 사용

아.. 쟤네 함수가 간단한 사칙연산이었음
매개변수들끼리의… 오…

from z3 import *

def add(param_1, param_2):
    return param_1 + param_2

def sub(param_1, param_2):
    return param_1 - param_2

def mul(param_1, param_2):
    return param_1 * param_2

def xor(param_1, param_2):
    return param_1 ^ param_2

def and_(param_1, param_2):
    return param_1 & param_2

a, b, c, d, e, f = BitVecs('a b c d e f', 32)

s = Solver()

s.add(a > 100000001)
s.add(b > 100000001)
s.add(c > 100000001)
s.add(d > 100000001)
s.add(e > 100000001)
s.add(f > 100000001)

s.add(a < 1000000000)
s.add(b < 1000000000)
s.add(c < 1000000000)
s.add(d < 1000000000)
s.add(e < 1000000000)
s.add(f < 1000000000)

uVar1 = sub(a, b)
uVar2 = add(uVar1, c)
uVar3 = add(a, b)
uVar1 = mul(2, b)
uVar4 = mul(3, a)
uVar5 = sub(uVar4, uVar1)
uVar6 = xor(a, d)
uVar1 = add(c, a)
uVar7 = and_(b, uVar1)
uVar11 = add(b, d)
uVar1 = add(d, f)
uVar8 = xor(c, uVar1)
uVar9 = sub(e, f)
uVar10 = add(e, f)

s.add(uVar2 % 0x10ae961 == 0x3f29b9)
s.add(uVar3 % 0x1093a1d == 0x8bdcd2)
s.add(uVar5 % uVar6 == 0x212c944d)
s.add(uVar7 % 0x6e22 == 0x31be)
s.add(uVar11 % a == 0x2038c43c)
s.add(uVar8 % 0x1ce628 == 0x1386e2)
s.add(uVar9 % 0x1172502 == 0x103cf4f)
s.add(uVar10 % 0x2e16f83 == 0x16ab0d7)

if s.check() == sat:
    m = s.model()
    print(f'a = {m[a]}\nb = {m[b]}\nc = {m[c]}\nd = {m[d]}\ne = {m[e]}\nf = {m[f]}')
else:
    print("No solution found")

[HITCON 2024] rivisual

Deobfuscate Tool

Image 1 Image 2

너무 많아서 일단 접어놨음
아래서부터 함수 분석해보자..

function _0x52fd86(_0x1f947c) {
    const _0x52daff = {
      'sMjKe': _0xfbe68(0x306) + _0xfbe68(0x268) + _0xfbe68(0x343) + '7c',
      'eyMvd': _0xfbe68(0x371) + _0xfbe68(0x1cd) + _0xfbe68(0x347) + '601473cb42' + _0xfbe68(0x387) + _0xfbe68(0x249) + _0xfbe68(0x313) + _0xfbe68(0x2f9) + _0xfbe68(0x2dc) + '37b04dcef8' + _0xfbe68(0x2b4) + _0xfbe68(0x394) + 'ef873ce857' + _0xfbe68(0x2b0) + _0xfbe68(0x31d) + _0xfbe68(0x288) + _0xfbe68(0x2ce) + _0xfbe68(0x2fd) + _0xfbe68(0x322) + _0xfbe68(0x3ab) + _0xfbe68(0x3b0) + _0xfbe68(0x241) + _0xfbe68(0x229)
    };
    let _0x4a7c67 = CryptoJS.enc[_0xfbe68(0x3a7)][_0xfbe68(0x25e)](CryptoJS[_0xfbe68(0x3af)](_0x1f947c)[_0xfbe68(0x37d)](CryptoJS[_0xfbe68(0x349)][_0xfbe68(0x3a7)]));
    let _0x1f1504 = CryptoJS[_0xfbe68(0x349)][_0xfbe68(0x3a7)][_0xfbe68(0x25e)](_0x52daff[_0xfbe68(0x2d4)]);
    let _0x289540 = CryptoJS.enc[_0xfbe68(0x3a7)][_0xfbe68(0x25e)](_0x52daff.eyMvd);
    let _0x21f6cd = CryptoJS[_0xfbe68(0x22a)].decrypt({
      'ciphertext': _0x289540
    }, _0x4a7c67, {
      'iv': _0x1f1504,
      'padding': CryptoJS[_0xfbe68(0x242)][_0xfbe68(0x231)],
      'mode': CryptoJS[_0xfbe68(0x363)].CBC,
      'hasher': CryptoJS.algo[_0xfbe68(0x3af)]
    });
    return _0x21f6cd[_0xfbe68(0x37d)](CryptoJS[_0xfbe68(0x349)][_0xfbe68(0x1eb)]);
}

복호화 함수
상수 지정해주는데 _0xfbe68이 자꾸 쓰인다.
이건 사용자 정의 function이 아니다
풀어줘야 한다. 이외에도..

Image 3

background가 red이면 lose이고, green되어야 함..?

Obfuscator Tool
문제 제작한 사람이 사용한 obfuscator이다.
여기 deobfuscator을 더 찾아보다가…

GitHub Repository
해당 깃허브 발견

Image 4

i.addEventListener("mousemove", a => {
    if (!p) {
      return;
    }
    if (k && m) {
      m.setAttribute("x2", a.clientX - i.getBoundingClientRect().left);
      m.setAttribute("y2", a.clientY - i.getBoundingClientRect().top);
    }
});
i.addEventListener("mouseup", a => {
    if (k && m) {
      m.setAttribute("x2", k.offsetLeft + k.offsetWidth / 2);
      m.setAttribute("y2", k.offsetTop + k.offsetHeight / 2);
      k.classList.add("selected");
      k = null;
      let a = f(q.map(a => parseInt(a.dataset.number)));
      if (a !== null) {
        q.forEach(a => {
          a.classList.add("win");
        });
        let b = document.getElementById("flag");
        b.innerText = a;
      } else {
        q.forEach(a => {
          a.classList.add("lose");
        });
      }
    }
    p = false;
});

이부분이 좀 중요한 거 같은데..
조건문 1. k && m이 0이 아님
조건문 2. f의 값이 null이 아님

마우스 이벤트 정리하자면
mousedown: 누른 시점
mouseover: 움직인 시점인데 아마 밖에서 해당 별로 왔을 때 말하는듯
mousemove: 움직인 시점, 이건 over과 반대인듯
mouseup: 뗀 시점

let a = f(q.map(a => parseInt(a.dataset.number)));
여기서 q.map이 아래 함수의 a로 감

function f(a) {
  // 다양한 b.wtf 연산의 결과를 변수 c부터 y에 저장
  let c = b.wtf(a[19], a[3], a[5]) * 25;
  let e = b.wtf(a[7], a[20], a[18]) * 25;
  let f = b.wtf(a[11], a[22], a[18]) * 25;
  let h = b.wtf(a[5], a[17], a[2]) * 25;
  let i = b.wtf(a[20], a[13], a[5]) * 25;
  let j = b.wtf(a[11], a[1], a[21]) * 25;
  let k = b.wtf(a[8], a[11], a[1]) * 25;
  let l = b.wtf(a[9], a[5], a[4]) * 25;
  let m = b.wtf(a[17], a[9], a[21]) * 25;
  let n = b.wtf(a[23], a[9], a[20]) * 25;
  let o = b.wtf(a[16], a[5], a[4]) * 25;
  let p = b.wtf(a[16], a[14], a[13]) * 25;
  let q = b.wtf(a[5], a[6], a[10]) * 25;
  let r = b.wtf(a[2], a[11], a[5]) * 25;
  let t = b.wtf(a[11], a[3], a[1]) * 25;
  let u = b.wtf(a[12], a[3], a[10]) * 25;
  let v = b.wtf(a[14], a[1], a[9]) * 25;
  let w = b.wtf(a[18], a[11], a[17]) * 25;
  let x = b.wtf(a[12], a[15], a[2]) * 25;
  let y = b.wtf(a[22], a[0], a[19]) * 25;

  // 변수 z 초기화
  let z = 0;

  // 변수 z에 다양한 b.gtfo 연산의 결과를 누적
  z += d(0.3837876686390533 - b.gtfo(j, v, m, 16, 21));
  z += d(0.21054889940828397 - b.gtfo(t, j, k, 8, 2));
  z += d(0.475323349112426 - b.gtfo(j, w, q, 0, 20));
  z += d(0.6338370887573964 - b.gtfo(h, e, q, 8, 4));
  z += d(0.4111607928994082 - b.gtfo(f, t, u, 23, 1));
  z += d(0.7707577751479291 - b.gtfo(w, h, p, 20, 6));
  z += d(0.7743081420118344 - b.gtfo(n, r, h, 9, 10));
  z += d(0.36471487573964495 - b.gtfo(m, c, i, 18, 8));
  z += d(0.312678449704142 - b.gtfo(u, n, w, 0, 17));
  z += d(0.9502808165680473 - b.gtfo(x, n, h, 22, 10));
  z += d(0.5869052899408282 - b.gtfo(q, l, f, 14, 10));
  z += d(0.9323389467455623 - b.gtfo(w, f, q, 12, 7));
  z += d(0.4587118106508875 - b.gtfo(k, r, f, 4, 21));
  z += d(0.14484472189349107 - b.gtfo(u, n, t, 7, 15));
  z += d(0.7255550059171598 - b.gtfo(j, w, x, 9, 23));
  z += d(0.5031261301775147 - b.gtfo(h, f, t, 7, 1));
  z += d(0.1417352189349112 - b.gtfo(k, t, m, 16, 14));
  z += d(0.5579334437869822 - b.gtfo(t, f, x, 19, 11));
  z += d(0.48502262721893485 - b.gtfo(o, i, l, 23, 18));
  z += d(0.5920916568047336 - b.gtfo(l, m, e, 19, 6));
  z += d(0.7222713017751479 - b.gtfo(v, f, i, 8, 16));
  z += d(0.12367382248520711 - b.gtfo(o, u, q, 9, 5));
  z += d(0.4558028402366864 - b.gtfo(p, o, f, 10, 2));
  z += d(0.8537692426035504 - b.gtfo(w, n, r, 4, 11));
  z += d(0.9618170650887574 - b.gtfo(q, x, w, 15, 2));
  z += d(0.22088933727810647 - b.gtfo(c, l, v, 10, 5));
  z += d(0.4302783550295858 - b.gtfo(v, p, j, 14, 2));
  z += d(0.6262803313609467 - b.gtfo(y, t, f, 17, 22));

  // z가 특정 값을 초과하면 null 반환
  if (z > 0.00001) {
    return null;
  }

  // 조건을 만족하면 문자열 s 생성
  let s = "";
  s += Math.round(b.wtf(a[4], a[2], a[22]) * 100000).toString();
  s += Math.round(b.wtf(a[17], a[9], a[14]) * 100000).toString();
  s += Math.round(b.wtf(a[4], a[13], a[7]) * 100000).toString();
  s += Math.round(b.wtf(a[4], a[20], a[23]) * 100000).toString();
  s += Math.round(b.wtf(a[5], a[7], a[12]) * 100000).toString();
  s += Math.round(b.wtf(a[20], a[19], a[4]) * 100000).toString();
  s += Math.round(b.wtf(a[17], a[6], a[19]) * 100000).toString();
  s += Math.round(b.wtf(a[6], a[21], a[18]) * 100000).toString();
  s += Math.round(b.wtf(a[4], a[3], a[8]) * 100000).toString();
  s += Math.round(b.wtf(a[11], a[7], a[14]) * 100000).toString();
  s += Math.round(b.wtf(a[9], a[2], a[13]) * 100000).toString();
  s += Math.round(b.wtf(a[22], a[10], a[3]) * 100000).toString();
  s += Math.round(b.wtf(a[15], a[22], a[13]) * 100000).toString();
  s += Math.round(b.wtf(a[16], a[12], a[9]) * 100000).toString();
  s += Math.round(b.wtf(a[14], a[8], a[17]) * 100000).toString();
  s += Math.round(b.wtf(a[1], a[18], a[6]) * 100000).toString();
  s += Math.round(b.wtf(a[10], a[11], a[3]) * 100000).toString();
  s += Math.round(b.wtf(a[8], a[12], a[5]) * 100000).toString();
  s += Math.round(b.wtf(a[1], a[3], a[12]) * 100000).toString();
  s += Math.round(b.wtf(a[9], a[13], a[7]) * 100000).toString();
}

너무 길어서 이것보다 더 많은데 암튼..
위쪽에 string 처리 된 거 코드 풀어야 했음

attribute vec3 position;
uniform   mat4 mvpMatrix;
varying   vec2 vPosition;

void main(void) {
	gl_Position = mvpMatrix * vec4(position, 1.0);
	vPosition = position.xy;
}
#ifdef GL_ES
precision mediump float;
#endif 

uniform float u_time;
uniform vec2 u_resolution;
varying vec2 vPosition;

vec3 hash(vec2 seed){
	vec3 p3 = fract(float(seed.x + seed.y*86.) * vec3(.1051, .1020, .0983));
	
	p3 += dot(p3, p3.yzx + 33.33);
	return fract(p3);
}

vec3 layer(float scale, vec2 uv, float time){
	// uv coord in cell
	vec2 scaled_uv = uv * scale - 0.5;
	vec2 uv0 = fract(scaled_uv) - 0.5;
	// cell id
	vec2 cell_id = scaled_uv - fract(scaled_uv);
	vec3 col = vec3(0);
	float speed = 1.5;
	// distance to a spinning random point in the cell (also surrounding cells)
	vec3 seed = hash(cell_id);
	
	float radiance = seed.x + time * seed.y;
	vec2 center_of_star = vec2(sin(radiance), cos(radiance)) * 0.3;
	
	// radial distort effect for star shine
	vec2 v_to_star = uv0 - center_of_star;
	float star_radiance = atan(v_to_star.x/v_to_star.y);
	float star_spark_1 = sin(star_radiance*14.+radiance*6.);
	float star_spark_2 = sin(star_radiance*8.-radiance*2.);
	float stars = length(v_to_star) * (5.+star_spark_1+star_spark_2) * 0.03;
	col += smoothstep(length(seed) * 0.01, 0., stars);
	return col;
}
void main() {
	// center global uv from -1 to 1
	vec2 virtual_resolution = vec2(2.0, 2.0);
	vec2 uv = (vPosition * 2. - virtual_resolution.xy) / virtual_resolution.y;
	vec3 col = vec3(0.);//vColor.xyz;
	
	const float layer_count = 6.5;
	for(float i = 0.0; i < layer_count; i+=1.){
		float rotate_speed = u_time*0.4;
		float scale = mod(i - rotate_speed, layer_count)*1.5;
		vec2 offseted_uv = uv + vec2(sin(rotate_speed), cos(rotate_speed));
		vec3 layer_col = layer(scale, offseted_uv, u_time + i*1.5);
		
		// we want the star to smoothly show uㅔ
		float max_scale = layer_count * 1.5;
		float color_amp = smoothstep(0., 1., smoothstep(max_scale, 0., scale));
		col += layer_col * color_amp;
	}
	// blue background
	col += vec3(0., 0., -0.15) * (uv.y - 0.7) * pow(length(uv), 0.5);
	gl_FragColor = vec4(col, 1.);
}
attribute vec3 position;
varying   float owO;

void main(){
	gl_Position = vec4(position.xy, 0.0, 1.0);
	owO = position.z;
}

#ifdef GL_ES
precision highp float;
#endif            
varying float owO;
#define OvO 255.0
#define Ovo 128.0
#define OVO 23.0

float OwO (float Owo, float OWO, float owO) { 
	OWO = floor(OWO + 0.5); owO = floor(owO + 0.5); 
	return mod(floor((floor(Owo) + 0.5) / exp2(OWO)), floor(1.0*exp2(owO - OWO) + 0.5)); 
}
vec4 oWo (float Ow0) { 
	if (Ow0 == 0.0) return vec4(0.0); 
	float Owo = Ow0 > 0.0 ? 0.0 : 1.0; 
	Ow0 = abs(Ow0); 
	float OWO = floor(log2(Ow0)); 
	float oWo = OWO + OvO - Ovo; 
	OWO = ((Ow0 / exp2(OWO)) - 1.0) * pow(2.0, OVO);
	float owO = oWo / 2.0; 
	oWo = fract(owO) + fract(owO); 
	float oWO = floor(owO); 
	owO = OwO(OWO, 0.0, 8.0) / OvO; 
	Ow0 = OwO(OWO, 8.0, 16.0) / OvO; 
	OWO = (oWo * Ovo + OwO(OWO, 16.0, OVO)) / OvO; 
	Owo = (Owo * Ovo + oWO) / OvO; 
	return vec4(owO, Ow0, OWO, Owo);
}
void main()
{
	gl_FragColor = oWo(owO);
}

어… 어렵다

[DownUnderCTF] REV-number mashing

if문들만 피해주면 되는 간단한 문제

v4=v4*v5가 되는 v4와 v5를 찾아주면 된다
이건 항등식인데 v4는 0이 안되고 v5는 0이나 1이 되면 안된다.
아마 int의 최대최소 제한을 이용해서 푸는 문제 같다.
대충 int의 최대최소 간의 거리를 v5번 순회해서 v4로 돌아오는 값 생각해주면 될 거 같은데
지금 노트가 없어서 집 가서 푸는 걸로 하고…

처음 생각한 식은 0부터 거리가 a일 때 4a=2^32(int의 자료형 크기)라 생각하는 a=v4와 5=v5였다
근데 이게 INT_MAX에서 넘어가는 부분에서 내가 계산을 잘못한 건지 안되었다..

signed라 헷갈리나 싶어서 unsigned로 먼저 생각해보았다.

결론적으로 안됐다..^^
뭔가 또 식을 잘못 세운 거 같다.

다시 식을 세워서 챗지피티한테 물어봤다.

# Constants
TARGET_PRODUCT = -2_147_483_648
CONSTANT_SUM = 2_147_483_648

# Function to find valid (a, n, m) triplets
def find_triplets(target_product, constant_sum):
    for a in range(-2_147_483_648, 0):
        if target_product % a == 0:
            n = target_product // a
            if n > 0:  # n must be a positive integer
                m = (constant_sum - a) // (a - 1)
                if m > 0 and (constant_sum - a) % a == 0:  # m must be a positive integer
                    return a, n, m
    return None

result = find_triplets(TARGET_PRODUCT, CONSTANT_SUM)
print(result)

이렇게 나왔는데 v5는 1이 불가능하다.. if에 걸림

라이트업 참고했더니 1 대신 -1을 넣으면 되는 문제였다..

자료형 오버플로우 관련한 문제는 생각보다 많이 나오는 거 같다.
아이디어 생각한 건 좋았는데 언제나 그랬듯이 마지막을 제대로 풀지 못해서 solve 못하는 경우가 많은데…
조금 양치기 하면 괜찮아지겠지 생각중이다.

Solidity Lesson 2: 좀비가 희생물을 공격하다

  1. 매핑과 주소
    address: 각 계정마다 가주고 있는 고유 식별자
    주소는 특정 유저(혹은 스마트 컨트랙트)가 소유한다 ???
    mapping: 키-값 저장소
// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다: 
mapping (address => uint) public accountBalance;
// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다 
mapping (uint => string) userIdToName;
  1. msg.sender
    솔라디티에 있는 특정 전역 변수 중 하나
    함수를 호출한 유저(혹은 스마트 컨트랙트)의 주소를 가리키는 변수 msg.sender

  2. require
    특정 조건이 참이 아니면 에러 메세지 출력 후 실행 중단

function sayHiToVitalik(string _name) public returns (string) {
  // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다
  // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 
  // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 참이면 함수 실행을 진행한다:
  return "Hi!";
}
  1. 상속
    그냥 우리가 아는 그 상속…
contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}
  1. import
    다른 파일을 import…
import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

./는 동일한 폴더(알죠?)

  1. Storage vs. Memory
    변수를 저장할 수 있는 공간으로 위 두 개가 있음
    Storage는 영구적임 -> 보통 상태 변수
    Memory는 임시적임 -> 보통 함수 내 선언한 변수
    컴퓨터의 하드 디스크와 RAM으로 생각하면 굳

자동 선언되는데 구조체와 배열의 경우에 지정(명시)

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 꽤 간단해 보이나, 솔리디티는 여기서 
    // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. 
    // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다.
    // 그리고 
    mySandwich.status = "Eaten!";
    // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. 

    // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: 
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. 
    // 그리고 
    anotherSandwich.status = "Eaten!";
    // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 
    // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: 
    sandwiches[_index + 1] = anotherSandwich;
    // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
  }
}
  1. 함수 접근 제어자
    private는 상속하는 컨트랙트가 접근할 수 없음
    internal로 수정~
    internal: private에서 상속 컨트랙트에서도 접근이 가능하다는 점만 다름
    external: 컨트랙트 바깥에서만 호출될 수 있는 public
contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // eat 함수가 internal로 선언되었기 때문에 여기서 호출이 가능하다 
    eat();
  }
}
  1. 인터페이스
    우리가 소유하지 않은 컨트랙트와 상호작용하려면 인터페이스 정의
contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

여기서 getNum 함수를 이용하여 해당 컨트랙트의 데이터를 읽고자하는 external 함수..
LuckyNumber이라는 인터페이스 먼저 정의

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

컨트랙트 뼈대 느낌..
다른 컨트랙트에서

contract MyContract {
  address NumberInterfaceAddress = 0xab38...
  // ^ 이더리움상의 FavoriteNumber 컨트랙트 주소이다
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // 이제 `numberContract`는 다른 컨트랙트를 가리키고 있다.

  function someFunction() public {
    // 이제 `numberContract`가 가리키고 있는 컨트랙트에서 `getNum` 함수를 호출할 수 있다:
    uint num = numberContract.getNum(msg.sender);
    // ...그리고 여기서 `num`으로 무언가를 할 수 있다
  }
}

위와 같이 활용, 이때 활용하는 함수는 public 또는 external로 선언되어야 함

  1. 다수의 반환값
function multipleReturns() internal returns (uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 다음과 같이 다수 값을 할당한다:
  (a, b, c) = multipleReturns();
}

// 혹은 단 하나의 값에만 관심이 있을 경우: 
function getLastReturnValue() external {
  uint c;
  // 다른 필드는 빈칸으로 놓기만 하면 된다: 
  (,,c) = multipleReturns();
}

저기 저 쉼표로 반환값만큼 체크해주면 됨