Top Banner
Norman Richards [email protected] @MaximoBurrito LOGIC programming A Ruby Perspective
51
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Logic programming a ruby perspective

Norman [email protected]

@MaximoBurrito

LOGIC programming A Ruby Perspective

Page 2: Logic programming a ruby perspective

logic programming?

http://xkcd.com/292/

Saving developers from imperative velociraptor attacks, one logic program at a time...

Page 3: Logic programming a ruby perspective

Ruby is many thingsRuby is a dynamic, reflective, object-oriented, general-

purpose programming language. [...] Ruby was influenced by Perl, Smalltalk, Eiffel, Ada, and Lisp. It supports multiple

programming paradigms, including functional, object-oriented, and imperative. It also has a dynamic type system

and automatic memory management.~Wikipedia

Page 4: Logic programming a ruby perspective

except...

Page 5: Logic programming a ruby perspective

I don't have the answer

42

Page 6: Logic programming a ruby perspective

This may not be very pragmatiC

I'm going to talk about something Ruby isn't good at...

I'm going to show you some libraries that are half baked...

But hopefully, I'll encourage you to explore logic programming more....

Page 7: Logic programming a ruby perspective

real world Logic programming

ThreatGRID uses logic programming (core.logic in Clojure) to process observations of malware

execution looking for behavioral indicators of compromise.

Page 8: Logic programming a ruby perspective

The interwebs

collect and run samples

Page 9: Logic programming a ruby perspective

Process activity

Network activity

Disk activity

Memory access

Pretty much everything

monitor behavior

Page 10: Logic programming a ruby perspective

(defobs process-modified-path [pid path] :doc "A pathname modified by a process, associated by the PID." :tags ["process" "file" "directory" "path"])

assert observations

Malware analysis generates analysis data, which in turn generates observation data that can be queried by core.logic. Some observations are exported to the database.

Page 11: Logic programming a ruby perspective

(defioc autoexec-bat-modified :title "Process Modified AUTOEXEC.BAT" :description "A process modified the AUTOEXEC.BAT file. ..." :category ["persistence" "weakening"] :tags ["process" "autorun" "removal"] :severity 80 :confidence 70 :variables [Path Process_Name Process_ID] :query [(process-modified-path Process_ID Path) (matches "(?i).*AUTOEXEC\\.BAT" Path) (process-name Process_ID Process_Name)])

Logic programs are queries

Security researchers write core.logic queries over the observations.

Declarative nature combined with abstraction make queries small and high level.

Page 12: Logic programming a ruby perspective

(defioc sinkholed-domain-detected :title "Domain Resolves to a Known DNS Sinkhole" :description "..." :category ["research" "defending"] :tags ["network" "dns" "sinkhole" "botnet"] :severity 100 :confidence 100 :variables [Answer_Data Answer_Type Query_Data Query_Type Network_Stream] :query [(fresh [qid] (dns-query Network_Stream qid (lvar) Query_Type Query_Data) (dns-answer Network_Stream qid (lvar) Answer_Type Answer_Data (lvar))) (sinkhole-servers Answer_Data)])

Logic programs are queries

We combine rules with internal knowledge bases.

Declarative queries combined with abstraction make queries small and high level.

Page 13: Logic programming a ruby perspective

Indicators produce data{:ioc autoexec-bat-modified :hits 1 :data ({Process_ID 1200 Process_Name "smss.exe" Path "\\AUTOEXEC.BAT"}) :confidence 70 :truncated false :title "Process Modified AUTOEXEC.BAT" :description "A process modified the AUTOEXEC.BAT ..." :severity 80 :category ["persistence" "weakening"] :tags ["process" "autorun" "removal"]}

Queries generate data that is used in reports.

Page 14: Logic programming a ruby perspective

Sample reportsReports show observations and matched indicators and their data.

We also correlate this data and mine the relationships between samples to create data feeds that customers can take action based on

Page 15: Logic programming a ruby perspective

minikanren

Minikanren is a relational programming environment, originally written in Scheme but ported to many other languages. It is described in the book The Reasoned Schemer.

The language is powerful, but deceptively simple, with only a few core language concepts.

http://minikanren.org/

Page 16: Logic programming a ruby perspective

ruby minikanren

One of two implementations, neither of which are currently being developed. (I would love to help someone fix this)

Doesn't have any advanced features you need for real world use, but it can be used for most of the examples in The Reasoned Schemer.

https://github.com/spariev/mini_kanren

require 'mini_kanren' include MiniKanren::Extras

result = MiniKanren.exec do # your logic program goes here end

Page 17: Logic programming a ruby perspective

run

run([], succeed)

This is the simplest possible minikanren. There are no query variables, and the query always succeeds

run says "give me all the results" and in ruby minikanren is an array. This query returns one result, which matches the empty query.

[[]]

Page 18: Logic programming a ruby perspective

run

run([], fail)

This query fails. There are no matches, so the result is an empty array.

[]

Page 19: Logic programming a ruby perspective

FRESHfresh introduces logic variables. Logic variables are the things wewant to find the values of. Minikanren programs often use q to represent the query.

_.0 represents an unbound logic variable in the results. We are saying, the query succeeded and the result is anything.

["_.0"]

q = fresh run(q, succeed)

Page 20: Logic programming a ruby perspective

FRESH

This query has two logic variables, and we find one results, where both logic variables are unbound and different. (or at least not constrained to be the same) [["_.0", "_.0"]]

a, b = fresh 2 run([a, b], eq(a, b))

Page 21: Logic programming a ruby perspective

unification

run(q, eq(q, :hello))

The most fundamental operation on a logic variable is to unify it. unification is eq.

There is only one value of q that satisfies the relation. [:hello]

Page 22: Logic programming a ruby perspective

unification

run(q, eq(q, [:hello, :world]))

Logic variables can also be unified over non-primitive values

There is still only one value of q that satisfies the relation.

[[:hello, :world]]

Page 23: Logic programming a ruby perspective

all

run(q, all(eq(q, :helloworld), eq(:helloworld, q)))

All expresses that all conditions must be true.

A logic variable can unify with the same value multiple times. But the overall goal only succeeds once, so there is only one value of q that satisfies the relation.

[:helloworld]

Page 24: Logic programming a ruby perspective

all

run(q, all(eq(q, :hello), eq(q, :world)))

A logic variable cannot unify with two different values at the same time.

There are no values of q that satisfy the relation. []

Page 25: Logic programming a ruby perspective

conde

run(q, conde(eq(q, :hello), eq(q, :world)))

You can introduce alternative values with conde. Every conde clause that succeeds produces possible alternative values.

There are 2 values of q that satisfy the relation. [:hello, :world]

Page 26: Logic programming a ruby perspective

Ordering clauses

run(q, fresh {|a,b| all(eq([a, :and, b], q), eq(a, :something), eq(:somethingelse, b)})

fresh can be used inside of a query.

Order does not matter for unification nor does the order of clauses matter. [[:something, :and, :somethingelse]]

Page 27: Logic programming a ruby perspective

rock paper scissors

def beats(move1, move2) conde(all(eq(move1, :rock), eq(move2, :scissors)), all(eq(move1, :scissors), eq(move2, :paper)), all(eq(move1, :paper), eq(move2, :rock))) end  

beats is a custom relation between two terms. It succeeds when the first players move beats the second players move.

More advanced implementations might have a prolog-style fact database, but we'll do this the hard way.

Page 28: Logic programming a ruby perspective

rock paper scissors

run(q, beats(:rock, :paper))beats fails because :rock does not beat :paper. No value of q makes this succeed.

[]

Page 29: Logic programming a ruby perspective

rock paper scissors

run(q, beats(:paper, :rock))

beats succeeds because :paper beats :rock. q remains fresh because no questions were asked of it.

["_.0"]

Page 30: Logic programming a ruby perspective

rock paper scissors

core.logiccore.logic

beats can answer in either direction.

[:scissors] [:rock]

run(q, beats(:rock, q))

run(q, beats(q, :scissors))

Page 31: Logic programming a ruby perspective

rock paper scissors

core.logic

winner, loser = fresh 2 run([winner, loser], beats(winner, loser)) This query asks for all the pairs

where winner beats loser.

[[:rock, :scissors], [:scissors, :paper], [:paper, :rock]]

Page 32: Logic programming a ruby perspective

... LIZARD SPOCKdef rpsls_beats(winner, loser) conde(all(eq(winner, :rock), conde(eq(loser, :scissors), eq(loser, :lizard))), all(eq(winner, scissors), conde(eq(loser, :paper), eq(loser, :lizard))), all(eq(winner, :paper), conde(eq(loser, :rock), eq(loser, :spock))), all(eq(winner, :spock), conde(eq(loser, :rock), eq(loser, :scissors))), all(eq(winner, :lizard), conde(eq(loser, :spock), eq(loser, :paper)))) end

Page 33: Logic programming a ruby perspective

SPOCK CHAINS

core.logiccore.logic

run(q, fresh{|m1, m2| all(eq(q, [:spock, m1, m2, :spock]), rpsls_beats(:spock, m1), rpsls_beats(m1, m2), rpsls_beats(m2, :spock))})

We can ask questions like: give me a 4-chain of dominated moves starting and ending with :spock. There are three solutions.

[[:spock, :rock, :lizard, :spock], [:spock, :scissors, :paper, :spock], [:spock, :scissors, :lizard, :spock]]

Page 34: Logic programming a ruby perspective

spock chainsdef chain(moves) fresh {|first, rest| all(caro(moves, first), cdro(moves, rest), rpsls(first), conde(nullo(rest), fresh {|second| all(caro(rest, first), rpsls_beats(first, second), defer(method(:chain), rest))}))} end

A winning chain is a single rpsls move either by itself or followed by a winning chain whose first move is beaten by the original move.

This example uses LISP-style list conventions. caro (first element) and cdro (the rest of the times) are relations on those lists.

Page 35: Logic programming a ruby perspective

how many chains?

run(q, all(eq(q, build_list([:spock] + fresh(10) +[:spock])), chain(q))).length

How many winning chains are there from :spock to :spock with 10 steps?

385

Page 36: Logic programming a ruby perspective

def edge(x,y) edgefact = -> (x1, y1) { all(eq(x,x1),eq(y,y1)) }

conde(edgefact[:g, :d], edgefact[:g, :h], edgefact[:e, :d], edgefact[:h, :f], edgefact[:e, :f], edgefact[:a, :e], edgefact[:a, :b], edgefact[:b, :f], edgefact[:b, :c], edgefact[:f, :c]) end

Path finding

D

A

E

B

GH

F

C

Page 37: Logic programming a ruby perspective

def path(x, y) z = fresh conde(eq(x, y), all(edge(x, z), defer(method(:path), z, y))) end def ispath(nodes) fresh {|first, second, rest| all(caro(nodes, first), cdro(nodes, rest), conde(nullo(rest), all(edge(first, second), caro(rest, second), defer(method(:ispath), rest))))} end

Path finding

D

A

E

B

GH

F

C

Page 38: Logic programming a ruby perspective

paths = run(q, all(caro(q,:e), ispath(q)))

paths.each{|path| puts path.join(' ') }

Path finding

D

A

E

B

GH

F

C

e e d e f e f c

Page 39: Logic programming a ruby perspective

Norman [email protected]

@maximoburrito

Page 40: Logic programming a ruby perspective
Page 41: Logic programming a ruby perspective

Bonus core.logic examples

Page 42: Logic programming a ruby perspective

Map coloring

core.logiccore.logichttp://pragprog.com/book/btlang/seven-languages-in-seven-weeks

(run 1 [q] (fresh [tn ms al ga fl] (everyg #(membero % [:red :blue :green]) [tn ms al ga fl]) (!= ms tn) (!= ms al) (!= al tn) (!= al ga) (!= al fl) (!= ga fl) (!= ga tn)   (== q {:tennesse tn :mississipi ms :alabama al :georgia ga :florida fl})))

({:tennesse :blue, :mississipi :red, :alabama :green, :georgia :red, :florida :blue})

Page 43: Logic programming a ruby perspective

FINITE DOMAINS

core.logiccore.logic

fd/interval declares a finite integer interval and fd/in contrains logic variables to a domain.

(defn two-plus-two-is-four [q] (fresh [t w o f u r TWO FOUR] (fd/in t w o f u r (fd/interval 0 9)) (fd/distinct [t w o f u r]) (fd/in TWO (fd/interval 100 999)) (fd/in FOUR (fd/interval 1000 9999))   ...

(== q [TWO TWO FOUR])))

T W O + T W O ------- F O U R

http://www.amazon.com/Crypt-arithmetic-Puzzles-in-PROLOG-ebook/dp/B006X9LY8O

Page 44: Logic programming a ruby perspective

FINITE DOMAINS

core.logiccore.logic

fd/eq translates simple math to constraints over finite domain logic variables.

(fd/eq (= TWO (+ (* 100 t) (* 10 w) o)))

(fd/eq (= FOUR (+ (* 1000 f) (* 100 o) (* 10 u) r))) (fd/eq (= (+ TWO TWO) FOUR))

T W O + T W O ------- F O U R

Page 45: Logic programming a ruby perspective

FINITE DOMAINS

core.logiccore.logic

There are 7 unique solutions to the problem.

(run* [q] (two-plus-two-is-four q))

T W O + T W O ------- F O U R

([734 734 1468] [765 765 1530] [836 836 1672] [846 846 1692] [867 867 1734] [928 928 1856] [938 938 1876])

Page 46: Logic programming a ruby perspective

USEless logic puzzle

core.logiccore.logic

‣ petey pig did not hand out the popcorn‣ pippin pig does not live in the wood house‣ the pig that lives in the straw house handed out

popcorn‣ Petunia pig handed out apples‣ The pig who handed out chocolate does not live in

the brick house.

Three little pigs, who each lived in a different type of house, handed out treats for Halloween. Use the clues to figure out which pig lived in each house, and what type of treat each pig handed out.

http://holidays.hobbyloco.com/halloween/logic1.html

Page 47: Logic programming a ruby perspective

USEless logic puzzle

core.logiccore.logic

(defn pigso [q] (fresh [h1 h2 h3 t1 t2 t3] (== q [[:petey h1 t1] [:pippin h2 t2] [:petunia h3 t3]]) (permuteo [t1 t2 t3] [:chocolate :popcorn :apple]) (permuteo [h1 h2 h3] [:wood :straw :brick])  ... ))

pigso starts by defining the solution space.

permuteo succeeds when the first list is permutation of the second.

Page 48: Logic programming a ruby perspective

USEless logic puzzle

core.logiccore.logic

(fresh [notpopcorn _] (!= notpopcorn :popcorn) (membero [:petey _ notpopcorn] q))

(fresh [notwood _] (!= notwood :wood) (membero [:pippin notwood _] q))

(fresh [_] (membero [_ :straw :popcorn] q))

(fresh [_] (membero [:petunia _ :apple] q))

(fresh [notbrick _] (!= notbrick :brick) (membero [_ notbrick :chocolate] q))

The clues translate cleanly to goals constraining the solution space.

membero has a solution when the first item is a member of the second.

Page 49: Logic programming a ruby perspective

FACTS and RELATIONS

core.logiccore.logic

(run* [q] (pigso q))

pigso finds the only solution.

([[:petey :wood :chocolate] [:pippin :straw :popcorn] [:petunia :brick :apple]])

Page 50: Logic programming a ruby perspective

sudoku made easier

core.logic

After setting up the logic variables and initializing state, the solution simply requires every row, column and square on the board to have distinct values.

(defn solve [puzzle] (let [sd-num (fd/domain 1 2 3 4 5 6 7 8 9) board (repeatedly 81 lvar)

rows (into [] (map vec (partition 9 board))) cols (apply map vector rows) squares (for [x (range 0 9 3) y (range 0 9 3)] (get-square rows x y))]   (run* [q] (== q board) (everyg #(fd/in % sd-num) board) (init-board board puzzle)

(everyg fd/distinct rows) (everyg fd/distinct cols) (everyg fd/distinct squares))))

https://gist.github.com/swannodette/3217582

Page 51: Logic programming a ruby perspective

sudoku

core.logiccore.logic

(def puzzle1 [0 0 0 0 0 9 0 6 0 0 3 8 0 0 5 0 0 4 0 2 0 0 6 0 0 7 0 0 0 0 0 0 0 3 9 0 0 0 0 9 2 6 0 0 0 0 9 7 0 0 0 0 0 0 0 4 0 0 7 0 0 3 0 5 0 0 4 0 0 2 1 0 0 7 0 8 0 0 0 0 0])

(partition 9 (first (solve puzzle1)))

((7 1 4 2 8 9 5 6 3) (6 3 8 7 1 5 9 2 4) (9 2 5 3 6 4 1 7 8) (8 6 1 5 4 7 3 9 2) (4 5 3 9 2 6 7 8 1) (2 9 7 1 3 8 4 5 6) (1 4 9 6 7 2 8 3 5) (5 8 6 4 9 3 2 1 7) (3 7 2 8 5 1 6 4 9))