Firmware Bug Hunting
With Taint Analysis
My Internship Experience
About me
++ Reverse Engineering
++ Binary Exploitation
++ Tooling
About Akash
He will be sharing next week :D
Tooling // GEF
Tooling // Instrumentation
void foo(...)
{
// ... do something
}
void goo(...)
{
// ... do something
}
int main()
{
foo(...);
goo(...);
}
Tooling // Instrumentation
void foo(...)
{
// ... do something
}
void goo(...)
{
// ... do something
}
int main()
{
foo(...);
goo(...);
}
void foo(...)
{
printf("Calling foo");
// ... do something
}
void goo(...)
{
printf("Calling goo");
// ... do something
}
int main()
{
foo(...);
goo(...);
}
Tooling // Instrumentation
Do this on compiled programs/apps that we don’t have source code for.
❯ frida-trace -i "recv*" -i "read*" twitter
recv: Auto-generated handler: …/recv.js
...
recvfrom: Auto-generated handler: …/recvfrom.js
Started tracing 21 functions. Press Ctrl+C to stop.
39 ms recv()
112 ms recvfrom()
128 ms recvfrom()
129 ms recvfrom()
...
8098 ms recvfrom(socket=70,
buffer=0x32cc018, length=65536,
flags=0x0,
address=0xb0420bd8, address_len=16)
Tooling // Instrumentation
Use cases:
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
n: BitVec(32)
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
n: BitVec(32)
x: n * 2
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
n: BitVec(32)
x: n * 2
y: x + 4 // n * 2 + 4
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
n: BitVec(32)
x: n * 2
y: x + 4 // n * 2 + 4
z: y << 3 // (n * 2 + 4) << 3
Tooling // Symbolic Execution
int main()
{
int n = read_int();
int x = n * 2;
int y = x + 4;
int z = y >> 3;
if (z == 1234)
puts("Correct PIN!");
}
n: BitVec(32)
x: n * 2
y: x + 4 // n * 2 + 4
z: y << 3 // (n * 2 + 4) << 3
check> z == 1234 // (n * 2 + 4) << 3 == 1234
Tooling // Symbolic Execution
Python 3.9.0 (default, Feb 20 2021, 21:41:45)
>>> from z3 import *
>>> n = BitVec('n', 32)
>>> x = n * 2
>>> y = x + 4
>>> z = y >> 3
n: BitVec(32)
x: n * 2
y: x + 4 // n * 2 + 4
z: y << 3 // (n * 2 + 4) << 3
check> z == 1234 // (n * 2 + 4) << 3 == 1234
Tooling // Symbolic Execution
Python 3.9.0 (default, Feb 20 2021, 21:41:45)
>>> from z3 import *
>>> n = BitVec('n', 32)
>>> x = n * 2
>>> y = x + 4
>>> z = y >> 3
>>> s = Solver()
>>> s.add(z == 1234)
>>> s.check()
sat
>>> s.model()
[n = 4936]
Tooling // Symbolic Execution
Many such tools out there these days
(mostly as binary analysis frameworks)
Firmware Bug Hunting
With Taint Analysis
My Internship Experience
Target // Router
Target // Router
❯ binwalk -eM PROLiNK_WN552K1_V1.0.25_210722.bin
Scan Time: 2021-09-08 06:57:22
Target File: /tmp/PROLiNK_WN552K1_V1.0.25_210722.bin
MD5 Checksum: 70333e3d706bc121bef2a88cab4c4ae7
Signatures: 391
...
Target // Router
Bug // Command Injection
void ping()
{
system("ping google.com");
}
❯ ./a.out
PING google.com (74.125.24.138): 56 data bytes
64 bytes from 74.125.24.138: icmp_seq=0 ttl=106 time=4.234 ms
64 bytes from 74.125.24.138: icmp_seq=1 ttl=106 time=10.934 ms
64 bytes from 74.125.24.138: icmp_seq=2 ttl=106 time=12.126 ms
64 bytes from 74.125.24.138: icmp_seq=3 ttl=106 time=4.567 ms
Bug // Command Injection
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
C functions:
❯ ./a.out google.com
PING google.com (74.125.24.138): 56 data bytes
64 bytes from 74.125.24.138: icmp_seq=0 ttl=106 time=4.234 ms
64 bytes from 74.125.24.138: icmp_seq=1 ttl=106 time=10.934 ms
64 bytes from 74.125.24.138: icmp_seq=2 ttl=106 time=12.126 ms
64 bytes from 74.125.24.138: icmp_seq=3 ttl=106 time=4.567 ms
system("ping google.com");
Bug // Command Injection
❯ ping google.com
PING google.com (74.125.24.138): 56 data bytes
64 bytes from 74.125.24.138: icmp_seq=0 ttl=106 time=4.234 ms
64 bytes from 74.125.24.138: icmp_seq=1 ttl=106 time=10.934 ms
64 bytes from 74.125.24.138: icmp_seq=2 ttl=106 time=12.126 ms
64 bytes from 74.125.24.138: icmp_seq=3 ttl=106 time=4.567 ms
Bug // Command Injection
❯ ping google.com; whoami
PING google.com (74.125.24.138): 56 data bytes
64 bytes from 74.125.24.138: icmp_seq=0 ttl=106 time=4.234 ms
64 bytes from 74.125.24.138: icmp_seq=1 ttl=106 time=10.934 ms
64 bytes from 74.125.24.138: icmp_seq=2 ttl=106 time=12.126 ms
64 bytes from 74.125.24.138: icmp_seq=3 ttl=106 time=4.567 ms
--- google.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 4.267/6.151/11.203/2.925 ms
daniellimws
Bug // Command Injection
❯ ./a.out "google.com; whoami"
PING google.com (74.125.24.138): 56 data bytes
64 bytes from 74.125.24.138: icmp_seq=0 ttl=106 time=4.234 ms
64 bytes from 74.125.24.138: icmp_seq=1 ttl=106 time=10.934 ms
64 bytes from 74.125.24.138: icmp_seq=2 ttl=106 time=12.126 ms
64 bytes from 74.125.24.138: icmp_seq=3 ttl=106 time=4.567 ms
--- google.com ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 4.267/6.151/11.203/2.925 ms
daniellimws
system("ping google.com; whoami");
Bug // Command Injection
This bug is somewhat common in routers.
Bug Hunting // Manual
Bug Hunting // Automated
Taint Analysis
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
Taint Analysis
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
Approach
Considerations:
Approach // Simulation
Approach // Simulation
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
target: BitVec(1024*8) // 1024 bytes
command: BitVec(256*8) // 1024 bytes
Approach // Simulation
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
target: BitVec(1024*8) // 1024 bytes
command: BitVec(256*8) // 1024 bytes
target <- taint
Approach // Simulation
Go through the program
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
target: BitVec(1024*8) // 1024 bytes
command: BitVec(256*8) // 1024 bytes
target <- taint
command <- taint
Approach // Simulation
Go through the program
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
target: BitVec(1024*8) // 1024 bytes
command: BitVec(256*8) // 1024 bytes
target <- taint
command <- taint
check> is command tainted?
Approach // Simulation
If there is if, which path to consider?
void some_handler(char* target)
{
if (...)
// do something
else
// do something else
}
Approach // Simulation
If there is if, which path to consider?
void some_handler(char* target)
{
...
if (...)
// do something
else
// do something else
}
if
else
Approach // Simulation
How about loops?
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stay
leave
Approach // Simulation
How about loops?
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stayed
leave
stay
Approach // Simulation
How about loops?
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stayed
leave
stay
Approach // Simulation
How about loops?
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stayed
leave
stay
Approach // Simulation
How about loops?
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stayed
leave
stay
Approach // Simulation
How about loops?
However, angr does follow some strategies to prioritize certain states
void some_handler(char* target)
{
...
for (...)
{
// do something
}
}
stayed
leave
stay
Approach // Simulation
Implementation
Approach // Simulation
Results
Tested on some MIPS router firmware
Approach // Simulation
Results
Tested on some MIPS router firmware
Approach // Simulation
Results
Tested on some MIPS router firmware
Approach // Feel sad
5 stages of grief
Approach // More research
After doing some more research on the Internet
Approach // More research
Approach // Reaching Definitions
def-use/use-def graph of a function
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
Approach // Reaching Definitions
def-use/use-def graph of a function
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
ping:target
Approach // Reaching Definitions
def-use/use-def graph of a function
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
sprintf:target
ping:target
sprintf:format
("ping %s")
Approach // Reaching Definitions
def-use/use-def graph of a function
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
sprintf:target
ping:target
sprintf:format
("ping %s")
sprintf:command
Approach // Reaching Definitions
def-use/use-def graph of a function
void ping(char* target)
{
char command[256];
sprintf(command, "ping %s", target);
system(command);
}
sprintf:target
ping:target
sprintf:format
("ping %s")
sprintf:command
system:command
Approach // Reaching Definitions
Thanks to heavy-lifting done by angr
A program analysis problem becomes a graph traversal problem
Source flows to sink
sprintf:target
ping:target
sprintf:format
("ping %s")
sprintf:command
system:command
Approach // Reaching Definitions
Implementation
Approach // Reaching Definitions
Results
Tested on same MIPS firmware as before
Approach // Reaching Definitions
Results
Tested on DIR-878 prog.cgi (2019-8312 to CVE-2019-8319)
Approach // Reaching Definitions
Results
Tested on D-Link DIR-878 prog.cgi (2019-8312 to CVE-2019-8319)
Same firmware is used on D-Link DIR-1960
Approach // Reaching Definitions
Results
Tested on PROLiNK PRC2402M
Approach // Reaching Definitions
Results
Tested on D-Link DIR-X1560
Demo
Takeaway
Thanks!
Questions?
References/Further readings
Credits