MIT course "Security of computer systems". Lecture 10: “Symbolic Execution”, part 1

### Massachusetts Institute of Technology. Lecture course # ???. "Security of computer systems." Nikolai Zeldovich, James Mykens. 2014

3r3504. Computer Systems Security is a course on the development and implementation of secure computer systems. Lectures cover threat models, attacks that compromise security, and security methods based on the latest scientific work. Topics include operating system (OS) security, capabilities, information flow control, language security, network protocols, hardware protection and security in web applications.

3r3504.

3r3504. Lecture 1: “Introduction: Threat Models” 3r312. Part 1

/ Part 2 / Part 3

3r3504. Lecture 2: "Control of hacker attacks" Part 1 / Part 2 / Part 3

3r3504. Lecture 3: "Buffer overflow: exploits and protection" 3r-328. Part 1

/ Part 2 / Part 3

3r3504. Lecture 4: “Separation of Privileges” 3–3–336. Part 1

/ Part 2 / Part 3

3r3504. Lecture 5: “Where Security Errors Come From” Part 1 / Part 2

3r3504. Lecture 6: "Opportunities" 3r350. Part 1

/ Part 2 / Part 3

3r3504. Lecture 7: “Sandbox Native Client” Part 1 / Part 2 / Part 3

3r3504. Lecture 8: “Network Security Model” 3r3666. Part 1

/ Part 2 / Part 3

3r3504. Lecture 9: “Web application security” 3r374. Part 1

/ Part 2 / Part 3

3r3504. Lecture 10: “Symbolic Execution” 3r382. Part 1

/ Part 2 / Part 3 3r388.

3r3504.

3r3504. Good morning to everyone, I am Armando Solar-Lesama and today I will read a lecture on symbolic execution. Who among those present here is familiar with this term or have you heard about it before? I just want to get an idea of our audience. So, let's begin. I dropped my laptop several times, so it takes a long time to load.

3r3504.

3r3504. 3r3398.

3r3504.

3r3504. Symbolic execution is the workhorse of modern program analysis. This is one of the methods that grew out of research and then began to be used in many applications. For example, today Microsoft has a system called SAGE, which works with a large number of important Microsoft software, ranging from Power Point to Windows itself, in order to really find security problems and vulnerabilities.

3r3504.

3r3504. There are many academic projects that have had a major impact on the real world, for example, the detection of important bugs in open source software using symbolic execution. And the beauty of symbolic execution as a technique is that, compared to testing, it gives you the opportunity to imagine how your program will behave with a potentially infinite set of possible input data. This allows you to explore arrays of input data, which would be completely inappropriate and impractical to investigate, say, by random testing, even with a very large number of testers. On the other hand, compared with more traditional methods of static analysis, it has the following advantage. When investigating a problem, symbolic execution can create input and trace, a runtime path that can be run in a real program and execute this program based on this input. And after that, we can identify the real bug and start fixing it using traditional debugging mechanisms. And this is especially valuable when you're in an industrial development environment where you probably don't have time to take care of every little problem in your code.

3r3504.

3r3504. For example, you really want to be able to distinguish real problems from false positives. How does this work?

3r3504.

3r3504. To really understand how this works, it’s helpful to start with a regular implementation. If we think of symbolic execution as a generalization of traditional, simple execution, it makes sense to know what it looks like. Therefore, I am going to use this very simple program as an illustration for many things that we will talk about today.

3r3504.

3r3504. 3r3119.

3r3504.

3r3504. Here we have an excerpt of a very simple code from several branches and the statement that if under some condition the value of t is < х, то это ложное утверждение. Мы хотим узнать, может ли это утверждение когда-либо быть вызвано. Это возможно? Существуют ли какие-то входные данные, которые сделают так, чтобы это утверждение провалилось?

3r3504.

3r3504. One of the things I can do is to check the execution of this program using specific input values as an example. Suppose we use input data for which X = 4 and Y = 4. The value of T is zero, as declared at the beginning of the program.

3r3504.

3r3504. So, before we get down to normal execution, let's find out what is the important point here? We need to have some idea of the state of the program, right? Whether we do normal execution or whether we do symbolic execution, we need to have some way to characterize the state of the program. In this case, it is such a simple program that it does not use a bunch, does not use a stack, and there are no function calls here.

3r3504.

3r3504. Thus, the state can be fully characterized by these three variables, along with the knowledge of where I am in the program. Therefore, if I start execution with ? 4 and 0 and get to the end of the branch, then I check the expression: 4 is greater than 4? Obviously not.

3r3504. Now I am going to execute the program at T = Y, that is, T is no longer equal to ? but has a value of 4. This is the current state of my program, and now I can evaluate this branch.

3r3504.

3r3504. 3r3142.

3r3504.

3r3504. Is it true that T < X? Нет. Мы увернулись от пули, утверждение false не сработало. Не было никаких проблем в этом частном выполнении.

3r3504.

3r3504. But this does not tell us anything about any other implementation. We know that for X = 4 and Y = ? the program will not fail. But this tells us nothing about what will happen if the input values are 2 and 1.

3r3504.

3r3504. 3r3504.

3r3504. With these input values, execution will follow a different path. This time we see that T = X, and after executing this line, T will take a value equal to 2. Are there any problems with this implementation? Will there be an assertion error with such input data?

3r3504.

3r3504. Well, let's see. So, if T is 2 and X is ? then T is no less than X. It looks like we dodged a bullet again. Right? So, here we have two specific input values at which the program works without errors. But in reality, this does not tell us anything about any other input values.

3r3504.

3r3504. So, the idea of symbolic execution is that we want to go beyond the execution of a program with one set of input data. We want to be able to actually argue about the behavior of the program when using a very large data set, in some cases an infinite set of possible input values. The main idea of this is as follows.

3r3504.

3r3504. 3r3172.

3r3504.

3r3504. For a program like this, its state is determined by the value of these three different variables: X, Y, and T and the knowledge of where in the program I'm currently located. But now instead of specific values for X and Y, I will have a symbolic value, just a variable. A variable that allows me to name this value that the user uses as input. This means that the state of my program is no longer characterized by matching variable names to specific values. Now this is a mapping of variable names to these character values.

3r3504.

3r3504. Character value can be considered as a formula. In this case, the formula for X is equal to X and the formula for Y is simply Y, and for T it is actually equal to 0. We know that for each input value it does not matter what you are doing. The value of T after the first statement will be equal to 0.

3r3504.

3r3504. That's where it gets interesting now. We have reached this branch, which says that if X is greater than Y, we will go in one direction. If X is less than or equal to Y, we will go in a different direction.

3r3504.

3r3504. Do we know anything about X and Y? What do we know about them? At least, we know their type, we know that they will vary in the range from min int to max int, but that’s all we know about them. It turns out that the information we know about them is insufficient to say in which direction this branch can go. She can go in any direction

3r3504. There are many things we can do, but what can we do at the moment? Try to make the wildest assumption.

3r3504.

3r3504. 3r3195.

3r3504.

3r3504.

**Audience:**we can trace the execution of the program along both branches.

3r3504.

3r3504.

**Prof: 3r3496. Yes, we can trace the execution along both branches. Toss a coin, and depending on how it falls, choose one branch or another.**

3r3504.

3r3504. So if we want to follow both branches, we must follow one and then the other, right? Suppose we start with this branch - T = X. We know that if we get to this place, then T will have the same meaning as X. We do not know what this value is, but we have a name for it - this is the script X.

3r3504.

3r3504. 3r3504.

3r3504. If we take the opposite branch, what happens then? The value of T will be equal to something else, right? In this branch, the value of T will be the symbolic value of Y.

3r3504.

3r3504. 3r3504.

3r3504. So what does this value T mean when we get to this point in the program? Maybe this is X, maybe this is Y. We don’t know exactly what this value is, but why not give it a name? Let's call it t

3r3504.

3r3504. Essentially, we know that if X is greater than Y, then the variable is equal to X, and if X is less than or equal to Y, then the variable is equal to Y. Therefore, we have a value that we have defined, let's call it t

3r3504.

3r3504. 3r3504.

3r3504. So, at this point in the program, we have a name for the value of T, this is t

3r3504. Now it comes to the fact that we have to ask whether T can be less than X. Now the value of T is equal to t

3r3504.

3r3504. But having t

3r3504.

3r3504. 3r3504.

3r3504. So, we have an equation. If it has a solution, if you can find the value of t

3r3504.

3r3504. So what have we done here? We ran the program, but instead of matching the variable names to specific values, we gave these variable names symbolic values. In fact, they gave them other variable names. And in this case, our other variable names are script X, script Y, t

3r3504.

3r3504. The solution of this equation allows us to answer the question whether this branch can be executed or not. Let's look at the equation - is it possible to take this branch or not? It seems that no, because we are looking for cases where t

3r3504. Thus, this means that when X> Y, this cannot happen, because t

3r3504.

3r3504. And what happens if t

3r3504.

3r3504. No, definitely not, because we know that X < Y. Так что если t

3r3504.

3r3504. You cannot solve it, and it tells us that no matter what input data X and Y we pass to the program, it will not go down the if t branch < x.

3r3504.

3r3504. Now notice that, while creating this argument here, I basically hinted at your intuition about integers, about mathematical integers. In practice, we know that machine int behave differently than mathematical int. There are cases when the laws that apply to mathematical integer data types are not applicable to software int.

3r3504.

3r3504. Therefore, we must be very careful when solving these equations, because we need to remember that these are not the integers that we were told in elementary school. These are 32-bit integers used by the machine. And there are many cases of errors that have arisen because programmers thought about their code withterms of mathematical integers, not realizing that there are such things as overflows that can cause different program behaviors for mathematical inputs.

3r3504.

3r3504. Another thing that I described here is a purely intuitive argument. I lead you through the process, showing you how to do it manually, but this is by no means an algorithm. The beauty of this idea of symbolic execution, however, lies in the fact that it can be encoded into an algorithm. And you can solve it mechanically, which allows you to do this not just for a program of 10 lines, but for millions of programs. This allows you to use the same intuitive reasoning that we used in this case, and talk about what happens when we run this program with different values of input. And these reasonings can be scaled and extended to very large programs.

3r3504.

3r3504. 3r33333.

3r3504.

3r3504. [b] Audience:what if the program does not support inputting a certain type of variable?

3r3504.

3r3504. So if we want to follow both branches, we must follow one and then the other, right? Suppose we start with this branch - T = X. We know that if we get to this place, then T will have the same meaning as X. We do not know what this value is, but we have a name for it - this is the script X.

3r3504.

3r3504. 3r3504.

3r3504. If we take the opposite branch, what happens then? The value of T will be equal to something else, right? In this branch, the value of T will be the symbolic value of Y.

3r3504.

3r3504. 3r3504.

3r3504. So what does this value T mean when we get to this point in the program? Maybe this is X, maybe this is Y. We don’t know exactly what this value is, but why not give it a name? Let's call it t

_{ 0 }. And what we know about t_{ 0 }? In what cases t_{ 0 }will be X?3r3504.

3r3504. Essentially, we know that if X is greater than Y, then the variable is equal to X, and if X is less than or equal to Y, then the variable is equal to Y. Therefore, we have a value that we have defined, let's call it t

_{ 0 }and it has these logical properties.3r3504.

3r3504. 3r3504.

3r3504. So, at this point in the program, we have a name for the value of T, this is t

_{ 0 }. What did we do here? We took both branches of the if statement, and then we calculated the symbolic value, looking at the conditions under which one branch of the program would be executed, and under which conditions the other.3r3504. Now it comes to the fact that we have to ask whether T can be less than X. Now the value of T is equal to t

_{ 0 }and we want to know if it is possible that t_{ 0 }would be less than X? Remember the first branch that we looked at - we asked the question about X and Y and did not know anything about them, except that they had type int.3r3504.

3r3504. But having t

_{ 0 }we really know a lot about it. We know that in some cases it will be equal to X, and in some cases it will be equal to Y. So now it gives us a set of equations that we can solve. So we can say if it is possible that t_{ 0 }less than X, knowing that t_{ 0 }satisfies all these conditions? Thus, it can be expressed as a constraint, indicating whether it is possible for t_{ 0 }was less than X. And if X is greater than Y, then t is_{ 0 }is equal to X, and if X is less than or equal to Y, this means that t_{ 0 }= Y.3r3504.

3r3504. 3r3504.

3r3504. So, we have an equation. If it has a solution, if you can find the value of t

_{ 0 }, the value of X and the value of Y, which satisfy this equation, then we will know these values, and when we enter them into the program, when executed, it will go along this branch if t < x и «взорвется», когда попадет в assert false.3r3504.

3r3504. So what have we done here? We ran the program, but instead of matching the variable names to specific values, we gave these variable names symbolic values. In fact, they gave them other variable names. And in this case, our other variable names are script X, script Y, t

_{ 0 }and besides, we have a set of equations that show how these values are related. We have an equation that tells us how t_{ 0 }associated with X and Y in this case.3r3504.

3r3504. The solution of this equation allows us to answer the question whether this branch can be executed or not. Let's look at the equation - is it possible to take this branch or not? It seems that no, because we are looking for cases where t

_{ 0 }less than X, but if in the first condition t_{ 0 }= X, then the expression t_{ 0 }3r3304. 3r3504.3r3504. Thus, this means that when X> Y, this cannot happen, because t

_{ 0 }= X and it cannot simultaneously be equal and smaller than X.3r3504.

3r3504. And what happens if t

_{ 0 }= Y? Can t_{ 0 }be less than X in this case?3r3504.

3r3504. No, definitely not, because we know that X < Y. Так что если t

_{ 0 }will be less than X, then it will also be less than Y. But we know that in this case t_{ 0 }= Y. And therefore, again, this condition cannot be satisfied. So here we have an equation that has no solution, and it does not matter what values you include in this equation.3r3504.

3r3504. You cannot solve it, and it tells us that no matter what input data X and Y we pass to the program, it will not go down the if t branch < x.

3r3504.

3r3504. Now notice that, while creating this argument here, I basically hinted at your intuition about integers, about mathematical integers. In practice, we know that machine int behave differently than mathematical int. There are cases when the laws that apply to mathematical integer data types are not applicable to software int.

3r3504.

3r3504. Therefore, we must be very careful when solving these equations, because we need to remember that these are not the integers that we were told in elementary school. These are 32-bit integers used by the machine. And there are many cases of errors that have arisen because programmers thought about their code withterms of mathematical integers, not realizing that there are such things as overflows that can cause different program behaviors for mathematical inputs.

3r3504.

3r3504. Another thing that I described here is a purely intuitive argument. I lead you through the process, showing you how to do it manually, but this is by no means an algorithm. The beauty of this idea of symbolic execution, however, lies in the fact that it can be encoded into an algorithm. And you can solve it mechanically, which allows you to do this not just for a program of 10 lines, but for millions of programs. This allows you to use the same intuitive reasoning that we used in this case, and talk about what happens when we run this program with different values of input. And these reasonings can be scaled and extended to very large programs.

3r3504.

3r3504. 3r33333.

3r3504.

3r3504. [b] Audience:

3r3504.

3r3504.

**Prof: 3r3496. This is a very good question! Suppose we have the same program, but instead of t = x we will have t = (x-1). Then, intuitively, we can imagine that now this program can “explode”, isn't it?**

3r3504.

3r3504. 3r33333.

3r3504.

3r3504. Because when the program goes this way, t will really be less than x. What happens with such a program? What will our character state look like? What will be equal to t

3r3504.

3r3504. The developer looks at it and says: “Oh, I forgot to tell you - in fact, this function will never be called with parameters where x is greater than y. I just wrote it for some historical reasons, so don’t worry, I wouldn’t even remember about it if you hadn’t told me. ”

3r3504.

3r3504. Suppose we have an assumption that x will be less than or equal to y.

3r3504.

3r3504. 3r33382.

3r3504.

3r3504. This is a prerequisite or agreement for our function. The function promises to do something, but only if the value satisfies this assumption. But if it is not satisfied, the function says: “I don’t care what happens. I promise that there will be no error only when this assumption is fulfilled. ”

3r3504.

3r3504. So, how do we encode this constraint when solving equations? Essentially, we have a set of constraints that tell us whether this branch is feasible. And besides the limitations, we must also make sure that the precondition, or assumption, is fulfilled.

3r3504.

3r3504. The question is, can I find x and y that satisfy all these constraints and at the same time have the required properties? It can be seen that this restriction X ≤ Y represents the difference between the case when this restriction is satisfied and the case when it is not satisfied.

3r3504.

3r3504. This is a very important issue when working with analysis, especially when you want to do it simultaneously at the level of individual functions. It is advisable to know what the programmer meant when writing this function. Because if you have no idea about these assumptions, you might think that there are some input data for which the program will fail.

3r3504.

3r3504. How to make it mechanically? There are two aspects to this problem. Aspect number one - how did you actually come up with these formulas?

3r3504.

3r3504. In this case, it is intuitively clear how we arrived at these formulas, we simply compiled them manually. But how to create these formulas mechanically?

3r3504.

3r3504. And the second aspect - how do you solve these formulas after you have them? Is it possible to actually solve these formulas that describe whether your program crashes or not?

3r3504. Let's start with the second question. We can reduce our problem with these formulas, which include integer arguments and bit vectors. Creating programs, you care about arrays, about functions and as a result receive huge formulas. Is it possible to solve them mechanically?

3r3504.

3r3504. 3r33417.

3r3504.

3r3504. The many technologies we are talking about today are practical tools related to the tremendous advances in the development of solvers for logical questions. In particular, there is a very important class of solvers, called SMT, or "the solver of the feasibility of modular theories." An SMT solver is a problem of solvability of logical formulas, taking into account the theories underlying them.

3r3504.

3r3504. Many people claim that this name is not particularly good, but it stuck as the most frequently used.

3r3504.

3r3504. An SMT solver is an algorithm by which a given logical formula at the output will give you one of two options: either it satisfies its purpose, or it does not. The second case means that in this formula it is impossible to use the values of variables that meet the requirements of the restrictions you set.

3r3504.

3r3504. In practice, this sounds a bit scary and a little magical. Most of the problems that SMT must solve are NP-complete problems, that is, problems with the answer “yes” or “no”.

3r3504. Can we have a system that relies, as its main building block, on solving NP-complete problems? Or do we need something else that works in practice? The fact is that many SMT solvers also have a third possible answer: “I don't know.”

3r3504.

3r3504. 3r33440.

3r3504.

3r3504. And so part of the beauty of these solvers is that, even solving very large and complex practical problems, they are capable of more than just answering: “I don't know.” They are able to give you a guarantee that this set of restrictions is unsatisfactory or fully satisfies its purpose, that is, they give you a very accurate answer.

3r3504.

3r3504. 27:30 min.

3r3504.

3r3504. MIT course "Security of computer systems". Lecture 10: “Symbolic Execution”, part 2

3r3504.

3r3504.

3r33460. 3rr3461. 3r33462. 3r33434.

3r3504. The full version of the course is available here .

3r3504.

3r3504. Thank you for staying with us. Do you like our articles? Want to see more interesting materials? Support us by placing an order or recommending a friend, [b] 30% discount for users of Habr for a unique analogue of entry-level servers, which we invented for you:3r37777. The whole truth about VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR???GB SSD 1Gbps from $ 20 or how to share the server?

3r3504.

3r3504. 3r33333.

3r3504.

3r3504. Because when the program goes this way, t will really be less than x. What happens with such a program? What will our character state look like? What will be equal to t

_{ 0 }when x is greater than y? We correct the lines in our equations in accordance with a different value when t = (x-1). Now the program may fail, and you go to the developer and say to him: “hey, this function may explode when x is greater than y”!3r3504.

3r3504. The developer looks at it and says: “Oh, I forgot to tell you - in fact, this function will never be called with parameters where x is greater than y. I just wrote it for some historical reasons, so don’t worry, I wouldn’t even remember about it if you hadn’t told me. ”

3r3504.

3r3504. Suppose we have an assumption that x will be less than or equal to y.

3r3504.

3r3504. 3r33382.

3r3504.

3r3504. This is a prerequisite or agreement for our function. The function promises to do something, but only if the value satisfies this assumption. But if it is not satisfied, the function says: “I don’t care what happens. I promise that there will be no error only when this assumption is fulfilled. ”

3r3504.

3r3504. So, how do we encode this constraint when solving equations? Essentially, we have a set of constraints that tell us whether this branch is feasible. And besides the limitations, we must also make sure that the precondition, or assumption, is fulfilled.

3r3504.

3r3504. The question is, can I find x and y that satisfy all these constraints and at the same time have the required properties? It can be seen that this restriction X ≤ Y represents the difference between the case when this restriction is satisfied and the case when it is not satisfied.

3r3504.

3r3504. This is a very important issue when working with analysis, especially when you want to do it simultaneously at the level of individual functions. It is advisable to know what the programmer meant when writing this function. Because if you have no idea about these assumptions, you might think that there are some input data for which the program will fail.

3r3504.

3r3504. How to make it mechanically? There are two aspects to this problem. Aspect number one - how did you actually come up with these formulas?

3r3504.

3r3504. In this case, it is intuitively clear how we arrived at these formulas, we simply compiled them manually. But how to create these formulas mechanically?

3r3504.

3r3504. And the second aspect - how do you solve these formulas after you have them? Is it possible to actually solve these formulas that describe whether your program crashes or not?

3r3504. Let's start with the second question. We can reduce our problem with these formulas, which include integer arguments and bit vectors. Creating programs, you care about arrays, about functions and as a result receive huge formulas. Is it possible to solve them mechanically?

3r3504.

3r3504. 3r33417.

3r3504.

3r3504. The many technologies we are talking about today are practical tools related to the tremendous advances in the development of solvers for logical questions. In particular, there is a very important class of solvers, called SMT, or "the solver of the feasibility of modular theories." An SMT solver is a problem of solvability of logical formulas, taking into account the theories underlying them.

3r3504.

3r3504. Many people claim that this name is not particularly good, but it stuck as the most frequently used.

3r3504.

3r3504. An SMT solver is an algorithm by which a given logical formula at the output will give you one of two options: either it satisfies its purpose, or it does not. The second case means that in this formula it is impossible to use the values of variables that meet the requirements of the restrictions you set.

3r3504.

3r3504. In practice, this sounds a bit scary and a little magical. Most of the problems that SMT must solve are NP-complete problems, that is, problems with the answer “yes” or “no”.

3r3504. Can we have a system that relies, as its main building block, on solving NP-complete problems? Or do we need something else that works in practice? The fact is that many SMT solvers also have a third possible answer: “I don't know.”

3r3504.

3r3504. 3r33440.

3r3504.

3r3504. And so part of the beauty of these solvers is that, even solving very large and complex practical problems, they are capable of more than just answering: “I don't know.” They are able to give you a guarantee that this set of restrictions is unsatisfactory or fully satisfies its purpose, that is, they give you a very accurate answer.

3r3504.

3r3504. 27:30 min.

3r3504.

3r3504. MIT course "Security of computer systems". Lecture 10: “Symbolic Execution”, part 2

3r3504.

3r3504.

3r33460. 3rr3461. 3r33462. 3r33434.

3r3504. The full version of the course is available here .

3r3504.

3r3504. Thank you for staying with us. Do you like our articles? Want to see more interesting materials? Support us by placing an order or recommending a friend, [b] 30% discount for users of Habr for a unique analogue of entry-level servers, which we invented for you:

(Available options with RAID1 and RAID1? up to 24 cores and up to 40GB DDR4).

3r3504.

3r3504.

**VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR???GB SSD 1Gbps until December for free 3r3496. when paying for a period of six months, you can order here 3r3498. .**

3r3504.

3r3504. [b] Dell R730xd 2 times cheaper?Only we have

3r3504.

3r3504. [b] Dell R730xd 2 times cheaper?

**3r3494. 2 x Intel Dodeca-Core Xeon E5-2650v???GB DDR4 6x480GB SSD 1Gbps 100 TV from $ 249**

**in the Netherlands and the USA!**Read about How to build the infrastructure of the building. class c using servers Dell R730xd E5-2650 v4 worth 9000 euros for a penny?

3r3504. 3r3504. 3r3502. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () ();

3r3504.

It may be interesting

This publication has no comments.

#### weber

Author**6-10-2018, 17:19**

Publication Date
#### Programming / Information Security / IT Infrastructure

Category- Comments: 0
- Views: 318

Comments

Visit Our website If You Need Custom thanksgiving couple shirts, Shirts For Your Company, Family Or Friends & We’ll Cook Something Special for you!

Inursing test bank was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post.