Hey, let’s go back to binary exploitation after a quick hiatus. I’ll talk about sigreturn-oriented programming today or SROP in short. I managed to exploit a challenge using that technique today and this post will be more or less a summary of what the technique does and what I did learn along the challenge resolution.
What’s a SROP? Can I drink it?
A Sigreturn-oriented programming is a technique of exploitation similar to return-oriented programming, which will grand full control to the registers to the attacker. Basically, all you have to get is the control of rax (you need to set it to 0xf or 15 in x64) then call syscall, with a frame containing your registers context set into the stack.
[buffer][rop pop rax;][15][rop syscall][srop frame]
Using pwnlib, you can define it as such:
The “issue” with that attack is that if you want to execute something a little bit more lengthy than a simple syscall, you’ll need to keep control of rsp (the stack pointer) and rip (the instruction pointer).
General method to exploit SROP vulnerabilities
Since we can do what we want using SROP, I found out that generally, people tend to do two stage of exploitation, first they’ll call SYS_read to write arbitrary data where they want in the memory (generally a /bin/sh in the .data section), set a ROP chain to do another SROP in the .data after that, then set rip to a syscall; ret; gadget and rsp to data+8;
stack [buffer][SROP1 -> syscall then rsp = .data+8] .data [/bin/sh\x00][SROP2 -> syscall: execve, rdi = .data]
I saw other exploits using mprotect or mmap to change the privileges of the .data section or somewhere else, and setup a shellcode to jump to it. I didn’t try that method but it should work without an issue.
The challenge
I won’t post my solution to the challenge because it was a public challenge website and sharing solution publicly is not allowed, but I can talk about how I did my exploitation and the challenges I encountered. The challenge was written by hand using asm and was pretty small so I couldn’t find a lot of interesting gadgets. It had ASLR and NX.
Here’s a rough write-up of my mindset for this exploitation, I know it will be hard to follow but it is meant as a memo for myself rather than something to be read:
- I first needed to find a way to reset rax to a lower value, I did that by calling the main program twice, allowing to still have control over the stack with the first overflow and overwriting saved-eip, but sending a low input on the second one, the smallest rax I could have was 17
- I searched for a long time how to control rax so that I could set it to 15 but I couldn’t find syncfs (306) and used pread64 (17) instead. What it did was that it set up my rax to a negative value.
- My rax to a negative value, I knew I could jump to the end of the program multiple time to increase its value, first to 5, then increment it 2 by 2, 5 times to obtain the value of 15.
- Once I was able to syscall I had an issue and I couldn’t know if I could write enough information into .data (I first thought that the .data section was limited to the size set beforehand by the program)
- I had some ideas, such as leaking a stack address or using mmap to control part of the memory but I fixed my previous issue first
Full exploitation chain:
stack [buffer][reset eax][syscall (pread64)][set rax to 5][add rax, 2;][add rax, 2;][add rax, 2;][add rax, 2;][add rax, 2;][syscall (sigreturn)][SROP1: sys_read, rsi: data, rsp: data+8, rip: syscall] .data [/bin/sh\x00][reset eax][padding][syscall (readv)][set rax to 5][add rax, 2;][add rax, 2;][add rax, 2;][add rax, 2;][add rax, 2;][syscall (sigreturn)][SROP2: sys_execve, rdi: data, rsi: 0, rdx: 0]
In my exploitation, /bin/sh didn’t grant me the setuid privileges so I replaced it with /tmp/ri and copied /bin/dash (could’ve done it with /bash I think) to that location and /bin/dash would drop me the correct privilege.
What did I learn today?
- Had a functional SROP for the first time today and learned where to put the SROP frame
- How to do a two-stage SROP exploitation using read
- Writing to .data is not limited to the initial size, you can write well beyond the limit even if the binary had a small section at the start
- You can use pread64 (17) and readv (19) to change the rax value to something more usable
- syncfs (306) can be used to reset the rax value to 0 (which can lead to read, allowing to read a value of 15, then another syscall to call sigreturn)