RSA

RSA Common Modulus Attack은 RSA에 대한 공격 방법 중 하나로, 다음의 조건에서 평문을 알아내는 공격이 가능하다.

  • (이름 그대로) 동일한 Modulus를 반복 사용(public exponent는 상이)
  • public exponent들이 서로 소 (gcd(e1, e2) = 1)
  • 동일한 메세지에 대한 암호문이 알려져 있고, n에 대해 서로 소 (gcd(c1, n) = 1, gcd(c2, n) = 1)

위의 조건을 만족하는 두 Key Pair (n, e1), (n, e2)와 이를 이용하여 메세지 m을 암호화 한 c1, c2가 각각 존재한다고 하자.

  • c1 = me1 mod n
  • c2 = me2 mod n

확장 유클리드 알고리즘(extended euclidean algorithm)을 이용하면 아래를 만족하는 r, s를 각각 구할 수 있다.

  • re1 + se2 = 1

그러면 다음과 같이 평문 m을 계산할 수 있다.

  • c1r · c2s = (me1)r · (me2)s = me1r · me2s = mre1+se2 = m (mod n)

그런데, e1e2는 양의 정수이므로 re1 + se2 = 1를 만족하기 위해서는 rs 중 하나가 음수이어야 한다. 여기서는 r이 음수라고 가정하자.

r이 음수일 때, 유한체(finite field)에서는 제곱근 계산이 어려우므로, gcd(c1, n) = 1 이라는 전제 하에 c1r을 다음과 같이 계산한다.

  • r < 0 일 때, t = -r이라고 하면 t는 양의 정수가 된다.
  • c1r = c1-t = c1-1 · t = (c1-1)t = (c1-1)-r (mod n)

종합하여, 평문 m은 아래와 같이 구할 수 있다.

  • (c1-1)-r · c2s = c1r · c2s = m (mod n)

처음의 조건에서 두 암호문 모두 n에 대해 서로 소여야 한다고 했는데,
사실 r이 음수인 경우 c1만, s가 음수인 경우 c2만 n과 서로 소이면 된다.

ctf Pwnable 문제를 풀다 보면 seccomp가 적용된 바이너리를 자주 만날 수 있다. 보통은 소스코드 없이 바이너리만 주어지기 때문에 decompile 하여 코드를 보게 되는데, argument들이 정수로 표시되어 있어 해석이 불편할 때가 많아 따로 정리해두려고 한다.

  • Intro

seccomp는 SECure COMPuting의 약자로, 커널이 제공하는 수많은 system call 중에 사용되지 않는 것들을 필터링 함으로써 attack surface를 줄이는 역할을 한다. seccomp 필터링을 사용하려면 process가 자신이 사용할 system call들을 필터로 정의해야 하는데, 이 필터는 BPF(Berkeley Packet Filter) 프로그램 형식으로 정의된다. (BPF는 일종의 socket 필터로, seccomp에서 사용될 때는 system call 번호와 argument들에 대해 필터링을 수행한다: 하단 참조) seccomp 필터를 위반하는 system call 호출이 일어나면 커널은 SIGKILL 시그널을 발생시켜서 프로세스를 종료한다.

seccomp 필터는 한번 적용되면 제거가 불가능하도록 설계되어 있는데, seccomp 필터를 사용한다는 것 자체가 이후에 실행되는 코드들에 대한 불신을 의미하기 때문이다.

seccomp는 prctl()seccomp()의 두 가지 인터페이스를 통해 사용할 수 있다.

  • prctl()

prctl()은 프로세스에 대한 여러가지 파라미터들을 변경하거나 확인하기 위한 인터페이스이다. 다양한 기능을 제공하지만, 여기서는 seccomp를 설정하기 위한 부분만 다룬다.

int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

prctl()을 이용해서 seccomp를 설정하기 위해서는 PR_SET_SECCOMP 옵션을 이용해야 한다. arg2를 통해서 SECCOMP_MODE_STRICTSECCOMP_MODE_FILTER의 두 가지 모드를 지정할 수 있다.

SECCOMP_MODE_STRICT 모드는 read, write, exit, sigreturn을 제외한 모든 system call을 제약한다.

SECCOMP_MODE_FILTER 모드는 허용되는 system call을 지정할 수 있는데, 이 때 arg3는 sock_fprog 구조체에 대한 포인터이다.

   struct sock_fprog {
       unsigned short      len;    /* Number of BPF instructions */
       struct sock_filter *filter; /* Pointer to array of BPF instructions */
   };

   struct sock_filter {            /* Filter block */
       __u16 code;                 /* Actual filter code */
       __u8  jt;                   /* Jump true */
       __u8  jf;                   /* Jump false */
       __u32 k;                    /* Generic multiuse field */
   };

instruction들이 수행될 때 BPF 프로그램은 아래와 같이 구성된 호출 정보를 제공받는다.

   struct seccomp_data {
       int   nr;                   /* System call number */
       __u32 arch;                 /* AUDIT_ARCH_* value
                                      (see <linux/audit.h>) */
       __u64 instruction_pointer;  /* CPU instruction pointer */
       __u64 args[6];              /* Up to 6 system call arguments */
   };

seccomp 필터가 fork를 허용하는 경우, fork를 통해 생성된 자식 프로세스는 부모의 seccomp 모드를 상속한다. execve가 허용된 경우에도 생성된 프로세스에 seccomp 모드가 유지된다.

  • seccomp()

seccomp() 인터페이스는 prctl() 인터페이스의 seccomp 관련 기능에 대한 확장 인터페이스이다. 주로 thread 관련 기능이 추가되었다.

int seccomp(unsigned int operation, unsigned int flags, void *args);

operation 인자를 통해서 SECCOMP_SET_MODE_STRICTSECCOMP_SET_MODE_FILTER의 두 가지 모드를 지정할 수 있다.

SECCOMP_SET_MODE_STRICT 모드는 prctl()의 strict 모드와 마찬가지로 read, write, exit, sigreturn을 제외한 모든 system call을 제약한다. 이 때, flag와 args는 각각 0과 NULL이어야 한다. seccomp(SECCOMP_SET_MODE_STRICT, 0, NULL)은 prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)와 동일한 기능을 제공한다.

SECCOMP_SET_MODE_FILTER 모드는 arg 인자를 통해 허용되는 system call들의 목록을 정의한다.

flags에는 아래의 값들을 사용할 수 있다.

  • SECCOMP_FILTER_FLAG_TSYNC: 해당 프로세스 내의 모든 thread에 동일한 seccomp filter를 적용한다.
  • SECCOMP_FILTER_FLAG_LOG: SECCOMP_RET_ALLOW를 제외한 모든 필터의 return action이 logging 된다.(/proc/sys/kernel/seccomp/actions_logged)
  • SECCOMP_FILTER_FLAG_SPEC_ALLOW: Spectre와 Meltdown 방어를 위한 mitigation을 disable한다.

flag가 0인 경우, prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args)와 동일하게 동작한다. 즉, seccomp() 인터페이스는 위의 flag들을 사용하기 위해 개발되었다.

seccomp() filter mode example (from here)

static int user_trap_syscall(int nr, unsigned int flags)
{
       struct sock_filter filter[] = {
               BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
                       offsetof(struct seccomp_data, nr)),
               BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1),
               BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF),
               BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
       };

       struct sock_fprog prog = {
               .len = (unsigned short)ARRAY_SIZE(filter),
               .filter = filter,
       };

       return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog);
}

seccomp() 대신 scmp_filter_ctx, seccomp_rule_add(), seccomp_load()를 이용해서 seccomp를 구성할 수도 있다. (참조)

  • Constants

  • linux/include/uapi/linux/seccomp.h
/* Valid values for seccomp.mode and prctl(PR_SET_SECCOMP, <mode>) */
#define SECCOMP_MODE_DISABLED   0 /* seccomp is not in use. */
#define SECCOMP_MODE_STRICT 1 /* uses hard-coded filter. */
#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */

/* Valid operations for seccomp syscall. */
#define SECCOMP_SET_MODE_STRICT     0
#define SECCOMP_SET_MODE_FILTER     1
#define SECCOMP_GET_ACTION_AVAIL    2
#define SECCOMP_GET_NOTIF_SIZES     3

/* Valid flags for SECCOMP_SET_MODE_FILTER */
#define SECCOMP_FILTER_FLAG_TSYNC       (1UL << 0)
#define SECCOMP_FILTER_FLAG_LOG         (1UL << 1)
#define SECCOMP_FILTER_FLAG_SPEC_ALLOW      (1UL << 2)
#define SECCOMP_FILTER_FLAG_NEW_LISTENER    (1UL << 3)
  • linux/include/uapi/linux/prctl.h
/* Get/set process seccomp mode */
#define PR_GET_SECCOMP  21
#define PR_SET_SECCOMP  22
  • 사족: BPF

BPF는 네트워크 패킷 모니터링 도구인 tcpdump 프로그램에서 파생되었다. tcpdump에서 처리해야 할 패킷들이 너무 많아지자 모든 패킷을 user space까지 올리는 것이 비효율적이 되었고, (user space에서는 관심 있는 패킷만 처리할 수 있도록) 필터링 작업을 kernel space에서 수행하기 위해 만들어진 것이 BPF이다.

seccomp 개발자들이 seccomp에 대한 요구사항을 정리하고 설계를 진행하다 보니 BPF와 매우 비슷한 task가 필요한 것으로 판단되어서 BPF를 system call 필터링에도 사용할 수 있도록 확장하였다.

BPF 프로그램은 커널 안에 존재하는 작은 virtual machine에서 처리된다고 생각하면 된다. BPF 프로그램은 최대 4096개의 instruction을 가질 수 있으며, kernel에 로딩될 때 verification 과정을 거친다. 이 VM에서 동작하는 코드는 몇가지 제약 사항이 있는데, 대표적으로 branch는 허용 되지만 이전의 instruction으로의 jump는 허용하지 않기 때문에 loop의 구현이 불가능하다는 점이 있다. 또, 모든 branch가 return으로 종료되어야 하기 때문에 BPF 프로그램은 항상 종료되며 이 때 정확한 return 값이 반환된다는 것이 보장된다.

BPF는 하나의 accumulator register와 data 영역을 갖는데, seccomp 필터링 시에 data 영역에는 system call에 대한 정보가 저장된다. 모든 instruction은 64bit 크기인데, 16bit opcode와 8bit 크기의 두 개의 jump 주소, 그리고 opcode에 따라 용도가 정해지는 32bit 크기의 field로 구성된다. jump 주소 두개를 갖고 있을 수 있기 때문에 conditional jump를 간단히 구성할 수 있다. jump 주소는 offset이기 때문에 0은 no jump를 의미하게 된다.

BPF를 확장한 eBPF(extended BPF)도 개발되었는데, 이는 BPF의 필터링 대상을 tracepoints, raw sockets, perf event 등으로 확장한 것이다. 참고로, Spectre와 Meltdown 취약점에서 커널에 공격 코드를 올리기 위해 사용했던 것이 eBPF이다.


Nmap은 IP scan과 Port scan을 위한 훌륭한 도구이다. (게다가 오픈소스!)

하지만 왜인지 WSL 환경과 내 윈도우 PC에서는 NMap이 정상적으로 잘 작동하지 않았다. 열심히 분석해서 이유를 알아내서 해결하면 좋겠지만, 알아낸다고 해결이 안될 수도 있고 다른 방법이 없는 것도 아니니 그냥 편한 방법을 사용하기로 했다.

  • IP Scanning

우선 IP scanning 방법을 살펴보자. 인터넷에서 조금 검색해 봤더니 Free IP Scanner라는 것을 찾을 수 있었다.

이 Tool은 scan 하고 싶은 IP 범위를 적어주면 그 범위 내에 살아있는 ip 들을 찾아주는 역할만 딱 한다. 별다른 기능은 없는 것 같지만, 그만큼 가볍고 빠르게 동작한다.

GUI 프로그램으로, 별다른 설정이나 사전 지식이 필요치 않다.

위 사진 한장으로 설명 끝.

  • Port Scanning

NMap이 잘 동작하면 좋은데, 커맨드 라인에서는 잘 안된다.. 왜지 ㅜㅠ 할 수 없이 Port Scanner 프로그램을 찾아보다가, NMap의 GUI wrapper인 zenmap을 알게 되었다. Windwos에서는 NMap 설치하면 같이 설치된다(좋다..).

Target와 Profile을 적어주면 알아서 스캔한다. 내가 선택한 Target와 Profile에 따라 Command에 자동으로 명령어가 작성되는데, 이 부분을 클릭해서 세부적으로 수정할 수도 있다.

Description

Category: Crypto

Source: Codegate 2018 Quals.

Points: 300

Description:

XD

source : Codegate 2018 Quals.

Write-up

주어진 파일을 unzip으로 풀어보면, RSAbaby.py 파일과 이를 수행한 결과인 Result.txt 파일을 확인할 수 있다. Result.txt에 있는 값들을 이용하여 암호화 된 flag를 푸는 문제임을 쉽게 짐작할 수 있다.

문제로 부터 얻어낼 수 있는 값은 n(modulus), e, h, g, encrypted flag이다.

임의의 a에 대하여, 이므로,

임을 알 수 있다.

형태를 보면 아래의 페르마의 소정리(Fermat's little theorem)와 닮은 것 같다.

페르마의 소정리를 이용할 수 있도록 수식을 확장해 나가보자.

은 p의 배수임을 알 수 있고, n도 p의 배수이므로, GCD를 적용하면 p를 찾아낼 수 있다. (p는 소수니까)

p를 찾아내면 N = pq로 부터 q를 간단히 계산해낼 수 있고, p, q와 e를 알게 되었으므로 d도 알아낼 수 있다.

찾아낸 d를 이용해서 암호화 된 flag를 복호화 하면 원래의 flag 값을 확인 가능하다.

아래는 a를 2로 하여 flag를 계산해내는 코드이다.

import re

enc_flag = (...)
n = (...)
h = (...)
g = (...)
e = 65537

def xgcd(b, n):
    x0, x1, y0, y1 = 1, 0, 0, 1
    while n != 0:
        q, b, n = b // n, n, b % n
        x0, x1 = x1, x0 - q * x1
        y0, y1 = y1, y0 - q * y1
    return  b, x0, y0

def mulinv(b, n):
    g, x, _ = xgcd(b, n)
    if g == 1:
        return x % n

kp = pow(2, e*g, n) * pow(2, 0xdeadbeef - 1, n) - 1
p, _, _ = xgcd(kp, n)
q = n / p

pi_n = (p-1)*(q-1)
d = mulinv(e, pi_n)

flag = pow(enc_flag, d, n)

print re.sub('(..)', lambda x: chr(int(x.group(1), 16)), hex(flag)[2:])

Flag : Whatever you do, the Basics are the most important :-D

'writeups > Crypto' 카테고리의 다른 글

Simple Logic  (0) 2019.11.26
OTP  (0) 2019.11.26
real-baby-rsa  (0) 2019.11.26
Ez Pz  (0) 2019.11.26
Noki  (0) 2019.11.26

Description

Category: Crypto

Source: TokyoWesterns CTF 5th 2019

Points: 103

Description:

Simple cipher is always strong.

Download: simple_logic.7z

Write-up

압축을 풀어보면 encrypt.rb와 output 파일을 찾을 수 있다.

output 파일에는 암호화된 flag와 plaintext, ciphertext 쌍 6개가 주어져 있다.

ruby 코드도 살펴보자.

{% raw %}
[생략]

ROUNDS = 765
BITS = 128
PAIRS = 6

def encrypt(msg, key)
    enc = msg
    mask = (1 << BITS) - 1
    ROUNDS.times do
        enc = (enc + key) & mask
        enc = enc ^ key
    end
    enc
end

[생략]

flag = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
key = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)

STDERR.puts "The flag: TWCTF{%x}" % flag
STDERR.puts "Key=%x" % key
STDOUT.puts "Encrypted flag: %x" % encrypt(flag, key)
fail unless decrypt(encrypt(flag, key), key) == flag # Decryption Check

PAIRS.times do |i|
    plain = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
    enc = encrypt(plain, key)
    STDOUT.puts "Pair %d: plain=%x enc=%x" % [-~i, plain, enc]
end
{% endraw %}

차사하게 flag와 Key는 빼고 줬나보다.

암호화 루틴을 보면 간단히 덧셈과 xor을 반복하는 것을 알 수 있다. 덧셈 연산을 하면 하위 바이트의 연산 결과에 상위 바이트가 영향을 받을 수 있으므로 하위 바이트부터 한 바이트씩 역산해가면 key를 복구 할 수 있을 것 같다.

주어진 샘플 암호문 중에서 하나를 골라 최하위 1 byte에 대해서 brute force를 시도하였더니 동일한 암호문을 만드는 key 값 여러 개를 찾을 수 있었다. 그래서 샘플을 하나가 아니라 6개를 준 것 같다.

최하위 byte 부터 각 pair에 대해 가능한 key의 조합을 뽑아서 교집합을 만들었더니 key 값을 찾을 수 있었고, 이를 이용하여 flag를 알아내었다. (코드)

주어진 샘플값 모두를 만족시키는 key는 두 개를 찾을 수 있었는데, 이 중 어느 것을 사용하여도 동일한 flag를 얻을 수 있었다.

Flag: TWCTF{ade4850ad48b8d21fa7dae86b842466d}

'writeups > Crypto' 카테고리의 다른 글

RSAbaby  (0) 2019.11.26
OTP  (0) 2019.11.26
real-baby-rsa  (0) 2019.11.26
Ez Pz  (0) 2019.11.26
Noki  (0) 2019.11.26

Description

Category: Crypto

Source: TokyoWesterns CTF 5th 2019

Points: 42

Author: Jisoon Park(js00n.park)

Description:

Download: real-baby-rsa.7z

Write-up

압축을 풀어보면 problem.py와 output 파일을 찾을 수 있다.

먼저, python 코드를 살펴보자.

flag = 'TWCTF{CENSORED}'

# Public Parameters
N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

# Encrypt the flag!
for char in flag:
    print(pow(ord(char), e, N))

꽤 큰 수로 보이는 N이 public modulus e와 함께 주어져 있고, RSA의 암호화 방식으로 flag를 암호화 하여 출력하는 간단한 코드이다.

일단 기계적으로 N에 대한 factorization을 시도해 보았는데 쉽게 되지 않았다.

그 와중에도 solving 수가 계속 올라가고 있어서, 코드를 다시 한번 자세히 보았더니, 암호화 구문에서 암호화를 flag 전체에 대해서 한번 하는게 아니라 각 글자마다 암호화를 하고 있었다 ㅡ_ㅡ;;

printable한 각 문자에 대해서 암호화를 시도하여 (암호문, 문자)로 구성된 dictionary를 만들고 이를 이용하여 output의 각 line에 적혀있는 숫자를 하나의 문자로 대체하여 모았더니 flag를 획득할 수 있었다.

import string

N = 36239973541558932215768154398027510542999295460598793991863043974317503405132258743580804101986195705838099875086956063357178601077684772324064096356684008573295186622116931603804539480260180369510754948354952843990891989516977978839158915835381010468654190434058825525303974958222956513586121683284362090515808508044283236502801777575604829177236616682941566165356433922623572630453807517714014758581695760621278985339321003215237271785789328502527807304614754314937458797885837846005142762002103727753034387997014140695908371141458803486809615038309524628617159265412467046813293232560959236865127539835290549091
e = 65537

d = dict()
for c in string.printable:
    d[pow(ord(c), e, N)] = c

flag_data = map(lambda x: int(x.strip()), open("output").readlines())

r = ""
for i in flag_data:
    r += d[i]

print r

Flag: TWCTF{padding_is_important}

'writeups > Crypto' 카테고리의 다른 글

RSAbaby  (0) 2019.11.26
Simple Logic  (0) 2019.11.26
OTP  (0) 2019.11.26
Ez Pz  (0) 2019.11.26
Noki  (0) 2019.11.26

Description

Category: Crypto

Source: HackCON CTF 2019

Points: 100

Author: Jisoon Park(js00n.park)

Description:

hackerman is so dank that he decided to play around with OTPs. he did the following: message1 ^ key = cipher1 message2 ^ key = cipher2

He gives you cipher1 and cipher2 and challenges you to find the concatenation of messages 1 and 2. Are you dank enough to find this? Oh and also, 'meme' is so popular that hackerman used the word in both his messages. cipher1 is '05F17121418010c0b4' cipher2 is '>1f00140807Q0e' Both without quotes

Write-up

cipher1과 cipher2가 각각 10자이니 flag는 20자이다.

flag 형식이 "d4rk{flag_text}c0de"라서 앞뒤로 5자씩 10자는 이미 알려져 있고,
plaintext 양쪽에 meme 가 들어간다고 하니 meme 가 두 번 들어가서 20자 중에 총 18자를 알 수 있다.

그냥 나머지 두 글자를 brute force로 알아내도 될 것 같지만, 알려진 부분으로 xor하는 것이 더 빠를 것 같다.

cipher1과 cipher2를 xor 하면, message1 ^ message2의 결과를 알아낼 수 있다. 여기에 "dark{}c0de"를 xor 하면 message1의 뒤 5자와 message2의 앞 5자를 알아낼 수 있다. (meme가 들어있는 것을 확인할 수 있다.)

flag format과 얻어낸 메세지를 5글자씩 조합하면 flag를 알아낼 수 있다. (코드)

Flag : **d4rk{meme__meme}c0de**

'writeups > Crypto' 카테고리의 다른 글

Simple Logic  (0) 2019.11.26
real-baby-rsa  (0) 2019.11.26
Ez Pz  (0) 2019.11.26
Noki  (0) 2019.11.26
R u SAd  (0) 2019.11.26
BOF, FSB

Description

Category: Pwnable

Source: HackCON CTF 2019

Points: 469

Author: Jisoon Park(js00n.park)

Description:

I have stack canaries enabled, Can you still B0f me ? Service : nc 68.183.158.95 8991

Download: q3 libc.so.6

Write-up

주어진 파일을 IDA로 디컴파일 해보자.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *stream; // ST08_8
  char s[8]; // [rsp+10h] [rbp-20h]
  __int64 v6; // [rsp+18h] [rbp-18h]
  unsigned __int64 v7; // [rsp+28h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  *(_QWORD *)s = 0LL;
  v6 = 0LL;
  stream = (FILE *)_bss_start;
  printf("Enter name : ", argv, envp);
  fgets(s, 16, stream);
  puts("Hello");
  printf(s, 16LL);
  printf("Enter sentence : ");
  fgets(s, 256, stream);
  return 0;
}

fgets()를 이용해서 두 번의 입력을 줄 수 있는데, 첫 번째 fgets()를 사용하면 fsb 공격이 가능하고, 두 번째 fgets()에서는 bof 공격이 가능하다.

libc를 줬으니 ROP 또는 Oneshot-gadget을 이용하면 될 것 같은데, 이를 위해서는 libc_base 주소를 알아내야 한다.

추가로, cananry가 적용되어 있으니 canary의 값도 겸사겸사 알아내 보자.

첫 번째 입력으로 %11lx:lx 을 던져주면 canary와 main 함수의 return address를 알아낼 수 있다.

main 함수는 libc.so의 __libc_start_main에 있는 main 함수 호출 주소 다음으로 복귀하도록 되어 있으니 이를 이용하여 libc_base의 주소를 계산해낼 수 있다.

주어진 libc.so를 분석해보면 main 함수가 0x20830 offset으로 리턴될 것을 알 수 있고, oneshot gadget은 0x45216 offset에 있는 것도 알아낼 수 있다.

oneshot gadget을 이용하기 위한 재료들이 다 갖추어 졌으니 실제 libc 주소를 알아내서 bof 공격을 시도하면 shell을 얻을 수 있다. (코드)

Flag : d4rk{H3ll0_R0p}c0de

'writeups > Pwnable' 카테고리의 다른 글

nothing more to say  (0) 2019.11.26
2 small 2 Pwn  (0) 2019.11.26
speedrun-002  (0) 2019.11.26
horcruxes  (0) 2019.11.26
shellql  (0) 2019.11.26

+ Recent posts