Friday, June 26, 2015

What is a "good" memory corruption vulnerability?

Posted by Chris Evans, register whisperer. Part 1 of 4.


There are a lot of memory corruption vulnerabilities in software, but not all are created equal. To a certain degree, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “usefulness” of a given memory corruption vulnerability is determined by how reliably it might be exploited. In some favorable instances, a given bug might be exploitable with near 100% reliability.


In this series of blog posts, we’ll examine what types of memory corruption vulnerabilities have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 best potential to lead to 100% reliable exploits, using a few recent public bugs in our bug tracker as learning tools. By providing research and data of this nature to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 defensive community, we can guide defensive and mitigation efforts. The tools and techniques for reliable exploitation can be studied and dissected to both find new ideas for mitigations, and to improve existing mitigations. For example, we note below that type confusion vulnerabilities can be quite nasty and we’re investigating compiler-based mitigations for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se.


What do we mean by reliable?
At first this might sound like a silly question, but in fact that are a lot of facets to “reliable”. We’re not trying to give an authoritative definition of reliable here, and we’ll narrow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 scope below, but here are some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 problems under cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 umbrella of “reliability”:


  • Does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit ever crash? This is probably cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 worst outcome for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker: a crash is a noisy signal that may lead to detection.
  • Does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit ever bail out cleanly? We’ll call this “aborting”, and an abort is defined as a clean exit without successful exploitation but also without any crash or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r detectable signal.
  • Does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit work uniformly across an exhaustive test matrix of different patch levels?
  • Does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit work in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 presence of additional security software such as EMET, Grsecurity, Anti-Virus products, etc.?
  • How is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit likely to behave upon encountering an unusual environment? Will it succeed, crash or abort?
  • Is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit cross-platform and cross-version?
  • Does cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit have a robust “continuation of execution” story, i.e. no post-exploitation instability or ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r untoward effects?


Given all this complexity, we need to define what we mean by “100% reliable” in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of this post. A “100% reliable” exploit is one that is:

  1. Guaranteed to succeed against a specific version and environment, on account of comprising a series of deterministic and fully understood steps;
  2. Provides adequate control that at a minimum, all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 above sources of unreliability can be detected and lead to aborts, not crashes.


Bug class: stack corruptions
Despite modern compiler technologies such as stack cookies and stack variable re-ordering, we still see cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 occasional stack corruption that is interesting from an exploitation point of view. For example, bug 291 details a very interesting stack corruption in an open-source JPEG XR image decoder where an array indexing error on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack leads to a write that “jumps over” cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack cookie and causes a corruption that is not detected by stack protections. The PoC reliably  and dare we claim, deterministically, crashes with ebp==0xffffffef. That hints to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug’s potential reliability.


Stack corruptions do have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 potential to be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 basis for 100% reliable exploits, because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack is often laid out very consistently at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time a vulnerability triggers. The follow code sample illustrates cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 point nicely by popping a calculator due to a stack corruption. If it works for you cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first time, it’ll also work for you cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second!


// Fedora 20 x64: gcc -fstack-protector ./stack.c
void subfunc() {
   char buf[8];
   buf[16] = 1;
}


int main() {
   int run_calc = 0;
   subfunc();
   if (run_calc) execl("/bin/gnome-calculator", 0);
}


Bug class: inter-chunk heap overflow or corruption
Probably still cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most common vulnerability class we encounter, writing linearly off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of a heap allocation is usually readily exploitable. However, it is unusual for such a vulnerability to lead to a 100% reliable exploit. One case where we almost got cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re was this off-by-one heap overflow in glibc. That’s an unusual case because we were attacking a command-line binary, where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap does end up in a deterministic state at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit attempt. Far more common is where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker is attacking a heap which is in a completely unknown state -- perhaps in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 context of a remote service, a web browser renderer process or a kernel.


There does exist a well-used technique called “heap grooming” which attempts to take a heap from an unknown state into a state where heap chunks are lined up in a productive arrangement for exploitation. There are good examples from previous Project Zero blog posts, such as this Safari exploit, this Flash regex exploit or this Flash 4GB-out-of-bounds exploit. Although heap grooming can generate very reliable exploits, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is still usually a probabilistic element to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 technique, so we end up lacking cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 determinism needed to claim 100% reliable exploitation.


The following code sample illustrates some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 concepts of determinism vs. non-determinism:


// Fedora 20 x64: gcc ./interheap.c; n=0; while true; do ./a.out; done
int main(int argc, const char* argv[]) {
   void* ptrs[1024];
   int i;
   void* ptr1;
   int *run_calc;
   int seed = (argc > 1) ? atoi(argv[1]) : getpid();
   srandom(seed); printf("seed: %d\n", seed);  // ./a.out 21526 pops.
   for (i = 0; i < 1024; ++i) ptrs[i] = malloc(random() % 1024);
   for (i = 0; i < 1024; ++i) if (random() % 2) free(ptrs[i]);
   ptr1 = malloc(128); run_calc = malloc(128);
   *run_calc = 0;
   memset(ptr1, 'A', 4096);
   if (*run_calc) execl("/bin/gnome-calculator", 0);
}


For a given seed on a given machine, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap often ends up in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same state because a command-line binary starts with a fresh heap. (We’re going to stop shy of calling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 state deterministic because we haven’t studied all cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 potential influences. We also note that different installations of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same Linux OS will vary due to e.g. different malloc() patterns in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dynamic linker depending on installed libraries.) Some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap states will lead to a calculator and some will not.


Bug class: use-after-free
Use-after-free vulnerabilities can lead to very reliable exploits, particularly when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “free” and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 “use” are close togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r, and / or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attacker gets to trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free via a scripting language. A lot of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reliability comes from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 way modern heap allocators tend to work: if an object of size X is free’d, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n it will typically be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next free heap slot handed out for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next allocation of size X. This maximizes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use of “cache hot” memory locations. It is also extremely deterministic. For those wanting to study a good use-after-free exploit, one possibility is this Pinkie Pie exploit from 2013. Although cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re are very non-deterministic elements to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit, step 2) is where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d object is re-purposed and that does represent a fairly deterministic step.


But, use-after-free bugs are not a perfect basis for a 100% reliable exploit. Generic challenges include:
  • Threading. Depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap implementation, a secondary thread might grab cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 free’d slot that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 primary thread wanted.
  • Heap corner-cases. Depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap implementation, a free operation might well trigger some reshuffle of central structures. Whilst unlikely, it is hard to be sure this will not happen if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 heap is in an unknown state at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time of exploitation.
  • Sensitivity to object sizes changing. Although this will not affect reliability against a specific software version, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s always cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 chance of an exploit breaking (and perhaps even difficulty to repair it) if a patch comes out which makes important object sizes bigger or smaller.


This simple code sample should illustrate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 point that use-after-free bugs can be pretty nasty, though:


// Fedora 20 x64: gcc ./uaf.c
struct unicorn_counter { int num; };

int main() {
   struct unicorn_counter* p_unicorn_counter;
   int* run_calc = malloc(sizeof(int));
   *run_calc = 0;
   free(run_calc);
   p_unicorn_counter = malloc(sizeof(struct unicorn_counter));
   p_unicorn_counter->num = 42;
   if (*run_calc) execl("/bin/gnome-calculator", 0);
}


Bug class: intra-chunk heap overflow or relative write
Intra-chunk heap overflows or intra-chunk relative writes can provide a very powerful exploitation primitive. This time, we’ll start with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sample code:


// Fedora 20 x64: gcc ./intraheap.c
struct goaty { char name[8]; int should_run_calc; };

int main(int argc, const char* argv[]) {
   struct goaty* g = malloc(sizeof(struct goaty));
   g->should_run_calc = 0;
   strcpy(g->name, "projectzero");
   if (g->should_run_calc) execl("/bin/gnome-calculator", 0);
}


A bug like this is extremely powerful because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 memory corruption does not cross a heap chunk. Therefore, all of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 uncertainty and non-determinism arising from unknown heap state is eliminated. The heap can be in any state, yet cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same program data will always be corrupted in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same way. Bugs like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se are very capable of leading to 100% reliable exploits. Bug 251 is a real-world example of a linear buffer overflow within a heap chunk. Bug 265 is a rare but very interesting example of an indexing error leading to an out-of-bounds write but within cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 confines of a single heap chunk. The trigger is also interesting: a protocol where a virtual pen writes characters on to a virtual screen, and we use a protocol message to set cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 virtual pen location to co-ordinates that are off-screen! The PoC deterministically crashes whilst operating on a wild but constrant address, free(0x2000000000).


Bug class: type confusion
Type confusion bugs can be very powerful, with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 potential to form cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 basis of 100% reliable exploits. When triggering a type confusion vulnerability, a piece of code has a reference to an object which it believes to be of type A (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 API type), but really it is confused and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object is of type B (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-memory type). Depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-memory structure of type A vs. type B, very weird but usually fully deterministic side-effects can occur. Time for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code sample:


// Fedora 20 x64: gcc ./confused.cc -lstdc++
#include

class IShouldRunCalculator { public: virtual bool UWannaRun() = 0; };

class CalculatorDecider final : public IShouldRunCalculator {
public:
   CalculatorDecider() : m_run(false) {}
   virtual bool UWannaRun() { return m_run; }
private: bool m_run;
};

class DelegatingCalculatorDecider final : public IShouldRunCalculator {
public:
   DelegatingCalculatorDecider(IShouldRunCalculator* delegate) : m_delegate(delegate) {}
   virtual bool UWannaRun() { return m_delegate->UWannaRun(); }
private: IShouldRunCalculator* m_delegate;
};

int main() {
   CalculatorDecider nonono;
   DelegatingCalculatorDecider isaidno(&nonono);
   IShouldRunCalculator* decider = &isaidno;
   CalculatorDecider* confused_decider = reinterpret_cast(decider);
   if (confused_decider->UWannaRun()) execl("/bin/gnome-calculator", 0);
}


As you can see from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a general attempt to say no to calculation, but a type confusion causes CalculatorDecider::UWannaRun() to perform a boolean check on an underlying piece of memory that is really a (non-null) pointer. So we’ll always end up calculating. (Or will we? It happens to calculate reliably on my machine but cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s a source of non-determinism here for readers that enjoy a thought exercise.)


A good study of a real type confusion bug and exploit is MWR Infosecurity’s blog post regarding cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365ir Pwn2Own 2013 entry against Google Chrome. Interestingly enough, this is one case where cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug does not easily lend itself to a 100% reliable exploit. In this instance, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re is a small in-memory type confused against a much larger API type. Therefore, when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 code is accessing most of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 raw fields, a heap boundary is crossed because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 offset of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 field in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 API type is larger than cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 size of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 in-memory type. As we have seen above, crossing heap boundaries is a no-no for reliability. The following diagram shows cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two main possibilities of type confusion member use. The object on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 left is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 compiler has emitted code for, on account of a bad cast. But at runtime, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object memory pointed to happens to be smaller because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 type in memory is different. Accesses that happen to be within bounds of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime object will behave deterministically -- such as an ASLR defeating pointer infoleak in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 GetSize() method. Accesses that cross a heap boundary are out-of-bounds and are unlikely to behave deterministically -- such as a memory corrupting write in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SetFlags() method.





Case study: ShaderParameter heap corruption, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 old school way
This post has been about bug reliability, so it may seem strange that we’re about to present an unreliable exploit. But this is a story about demonstrating conventional wisdom and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n challenging ourselves to produce something more reliable using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same bug primitive. We start with poor reliability and will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n improve until we have excellent reliability.


So, we end this post by exploiting a recently patched vulnerability in Adobe Flash. It’s bug 324, an out-of-bounds write relating to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ShaderParameter ActionScript class. The attacker gets to write a chosen 32-bit value at a bad index relative to a shader program. A large index will result in an out-of-bounds write off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of a heap chunk.


Given an out-of-bounds write primitive like this, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re’s now a highly standard way of exploiting Adobe Flash: simply use heap grooming to arrange for a Vector. buffer object to follow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 object which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 write goes off cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of. The errant write will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n clobber cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 length of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Vector, resulting in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to read and write arbitrary process memory past cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 end of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Vector.



A reasonably commented exploit for Linux x64 is attached to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug. No particular attempt has been made to make it reliable; it could probably be tidied up to be much more reliable. But we’re not going to get to 100% reliability with this particular exploit for reasons covered above, because we’ve blindly corrupted across a heap chunk boundary.


After Adobe released cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch to fix this bug, but well before we released details of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug, non-zero day exploits started showing up in exploit packs in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 wild. Perhaps cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 attackers are fast at binary diffing, or have a MAPP leak, or were already using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vulnerability in a more targeted manner; we may never know. However this happened, it did generate us anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r data point and anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r exploit to study -- which also uses cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 standard Vector exploitation tricks according to @HaifeiLi on Twitter.


We’ll conclude this post here with a promise that we’re not done with this specific bug. In cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next post in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 series, we’ll ask cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 question, “can we do something more reliable with this bug?” and as you might guess, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 answer will be yes.

5 comments:

  1. Good work, security colleagues! Thanks for interesting and professional overview. Alexander

    ReplyDelete
  2. It's a really long post but it's worth it to read. Learned a lot, thanks! -Jin

    ReplyDelete
  3. Forgot null pointer dereference and a few ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rs

    ReplyDelete
  4. Great article, though I couldn't understand everything in one go ... bookmarking for later read ... Thanks Paul

    ReplyDelete
  5. really nice to read! part 1 of 4... sooo i guess cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re will never be part 2,3 and 4 since Mr. Evans left google right?... saaaad

    ReplyDelete